1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2008 Isilon Inc http://www.isilon.com/ 5 * Authors: Doug Rabson <dfr@rabson.org> 6 * Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org> 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 __FBSDID("$FreeBSD$"); 32 33 #include <sys/param.h> 34 #include <sys/stat.h> 35 #include <sys/linker.h> 36 #include <sys/module.h> 37 #include <sys/queue.h> 38 #include <sys/syslog.h> 39 #include <ctype.h> 40 #include <dirent.h> 41 #include <err.h> 42 #include <errno.h> 43 #ifndef WITHOUT_KERBEROS 44 #include <krb5.h> 45 #endif 46 #include <pwd.h> 47 #include <signal.h> 48 #include <stdarg.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <unistd.h> 53 #include <gssapi/gssapi.h> 54 #include <rpc/rpc.h> 55 #include <rpc/rpc_com.h> 56 57 #include "gssd.h" 58 59 #ifndef _PATH_GSS_MECH 60 #define _PATH_GSS_MECH "/etc/gss/mech" 61 #endif 62 #ifndef _PATH_GSSDSOCK 63 #define _PATH_GSSDSOCK "/var/run/gssd.sock" 64 #endif 65 #define GSSD_CREDENTIAL_CACHE_FILE "/tmp/krb5cc_gssd" 66 67 struct gss_resource { 68 LIST_ENTRY(gss_resource) gr_link; 69 uint64_t gr_id; /* identifier exported to kernel */ 70 void* gr_res; /* GSS-API resource pointer */ 71 }; 72 LIST_HEAD(gss_resource_list, gss_resource) gss_resources; 73 int gss_resource_count; 74 uint32_t gss_next_id; 75 uint32_t gss_start_time; 76 int debug_level; 77 static char ccfile_dirlist[PATH_MAX + 1], ccfile_substring[NAME_MAX + 1]; 78 static char pref_realm[1024]; 79 static int verbose; 80 static int use_old_des; 81 static int hostbased_initiator_cred; 82 #ifndef WITHOUT_KERBEROS 83 /* 1.2.752.43.13.14 */ 84 static gss_OID_desc gss_krb5_set_allowable_enctypes_x_desc = 85 {6, (void *) "\x2a\x85\x70\x2b\x0d\x0e"}; 86 static gss_OID GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X = 87 &gss_krb5_set_allowable_enctypes_x_desc; 88 static gss_OID_desc gss_krb5_mech_oid_x_desc = 89 {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; 90 static gss_OID GSS_KRB5_MECH_OID_X = 91 &gss_krb5_mech_oid_x_desc; 92 #endif 93 94 static void gssd_load_mech(void); 95 static int find_ccache_file(const char *, uid_t, char *); 96 static int is_a_valid_tgt_cache(const char *, uid_t, int *, time_t *); 97 static void gssd_verbose_out(const char *, ...); 98 #ifndef WITHOUT_KERBEROS 99 static krb5_error_code gssd_get_cc_from_keytab(const char *); 100 static OM_uint32 gssd_get_user_cred(OM_uint32 *, uid_t, gss_cred_id_t *); 101 #endif 102 void gssd_terminate(int); 103 104 extern void gssd_1(struct svc_req *rqstp, SVCXPRT *transp); 105 extern int gssd_syscall(char *path); 106 107 int 108 main(int argc, char **argv) 109 { 110 /* 111 * We provide an RPC service on a local-domain socket. The 112 * kernel's GSS-API code will pass what it can't handle 113 * directly to us. 114 */ 115 struct sockaddr_un sun; 116 int fd, oldmask, ch, debug; 117 SVCXPRT *xprt; 118 119 /* 120 * Initialize the credential cache file name substring and the 121 * search directory list. 122 */ 123 strlcpy(ccfile_substring, "krb5cc_", sizeof(ccfile_substring)); 124 ccfile_dirlist[0] = '\0'; 125 pref_realm[0] = '\0'; 126 debug = 0; 127 verbose = 0; 128 while ((ch = getopt(argc, argv, "dhovs:c:r:")) != -1) { 129 switch (ch) { 130 case 'd': 131 debug_level++; 132 break; 133 case 'h': 134 #ifndef WITHOUT_KERBEROS 135 /* 136 * Enable use of a host based initiator credential 137 * in the default keytab file. 138 */ 139 hostbased_initiator_cred = 1; 140 #else 141 errx(1, "This option not available when built" 142 " without MK_KERBEROS\n"); 143 #endif 144 break; 145 case 'o': 146 #ifndef WITHOUT_KERBEROS 147 /* 148 * Force use of DES and the old type of GSSAPI token. 149 */ 150 use_old_des = 1; 151 #else 152 errx(1, "This option not available when built" 153 " without MK_KERBEROS\n"); 154 #endif 155 break; 156 case 'v': 157 verbose = 1; 158 break; 159 case 's': 160 #ifndef WITHOUT_KERBEROS 161 /* 162 * Set the directory search list. This enables use of 163 * find_ccache_file() to search the directories for a 164 * suitable credentials cache file. 165 */ 166 strlcpy(ccfile_dirlist, optarg, sizeof(ccfile_dirlist)); 167 #else 168 errx(1, "This option not available when built" 169 " without MK_KERBEROS\n"); 170 #endif 171 break; 172 case 'c': 173 /* 174 * Specify a non-default credential cache file 175 * substring. 176 */ 177 strlcpy(ccfile_substring, optarg, 178 sizeof(ccfile_substring)); 179 break; 180 case 'r': 181 /* 182 * Set the preferred realm for the credential cache tgt. 183 */ 184 strlcpy(pref_realm, optarg, sizeof(pref_realm)); 185 break; 186 default: 187 fprintf(stderr, 188 "usage: %s [-d] [-s dir-list] [-c file-substring]" 189 " [-r preferred-realm]\n", argv[0]); 190 exit(1); 191 break; 192 } 193 } 194 195 gssd_load_mech(); 196 197 if (!debug_level) { 198 if (daemon(0, 0) != 0) 199 err(1, "Can't daemonize"); 200 signal(SIGINT, SIG_IGN); 201 signal(SIGQUIT, SIG_IGN); 202 signal(SIGHUP, SIG_IGN); 203 } 204 signal(SIGTERM, gssd_terminate); 205 206 memset(&sun, 0, sizeof sun); 207 sun.sun_family = AF_LOCAL; 208 unlink(_PATH_GSSDSOCK); 209 strcpy(sun.sun_path, _PATH_GSSDSOCK); 210 sun.sun_len = SUN_LEN(&sun); 211 fd = socket(AF_LOCAL, SOCK_STREAM, 0); 212 if (fd < 0) { 213 if (debug_level == 0) { 214 syslog(LOG_ERR, "Can't create local gssd socket"); 215 exit(1); 216 } 217 err(1, "Can't create local gssd socket"); 218 } 219 oldmask = umask(S_IXUSR|S_IRWXG|S_IRWXO); 220 if (bind(fd, (struct sockaddr *) &sun, sun.sun_len) < 0) { 221 if (debug_level == 0) { 222 syslog(LOG_ERR, "Can't bind local gssd socket"); 223 exit(1); 224 } 225 err(1, "Can't bind local gssd socket"); 226 } 227 umask(oldmask); 228 if (listen(fd, SOMAXCONN) < 0) { 229 if (debug_level == 0) { 230 syslog(LOG_ERR, "Can't listen on local gssd socket"); 231 exit(1); 232 } 233 err(1, "Can't listen on local gssd socket"); 234 } 235 xprt = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE); 236 if (!xprt) { 237 if (debug_level == 0) { 238 syslog(LOG_ERR, 239 "Can't create transport for local gssd socket"); 240 exit(1); 241 } 242 err(1, "Can't create transport for local gssd socket"); 243 } 244 if (!svc_reg(xprt, GSSD, GSSDVERS, gssd_1, NULL)) { 245 if (debug_level == 0) { 246 syslog(LOG_ERR, 247 "Can't register service for local gssd socket"); 248 exit(1); 249 } 250 err(1, "Can't register service for local gssd socket"); 251 } 252 253 LIST_INIT(&gss_resources); 254 gss_next_id = 1; 255 gss_start_time = time(0); 256 257 gssd_syscall(_PATH_GSSDSOCK); 258 svc_run(); 259 gssd_syscall(""); 260 261 return (0); 262 } 263 264 static void 265 gssd_load_mech(void) 266 { 267 FILE *fp; 268 char buf[256]; 269 char *p; 270 char *name, *oid, *lib, *kobj; 271 272 fp = fopen(_PATH_GSS_MECH, "r"); 273 if (!fp) 274 return; 275 276 while (fgets(buf, sizeof(buf), fp)) { 277 if (*buf == '#') 278 continue; 279 p = buf; 280 name = strsep(&p, "\t\n "); 281 if (p) while (isspace(*p)) p++; 282 oid = strsep(&p, "\t\n "); 283 if (p) while (isspace(*p)) p++; 284 lib = strsep(&p, "\t\n "); 285 if (p) while (isspace(*p)) p++; 286 kobj = strsep(&p, "\t\n "); 287 if (!name || !oid || !lib || !kobj) 288 continue; 289 290 if (strcmp(kobj, "-")) { 291 /* 292 * Attempt to load the kernel module if its 293 * not already present. 294 */ 295 if (modfind(kobj) < 0) { 296 if (kldload(kobj) < 0) { 297 fprintf(stderr, 298 "%s: can't find or load kernel module %s for %s\n", 299 getprogname(), kobj, name); 300 } 301 } 302 } 303 } 304 fclose(fp); 305 } 306 307 static void * 308 gssd_find_resource(uint64_t id) 309 { 310 struct gss_resource *gr; 311 312 if (!id) 313 return (NULL); 314 315 LIST_FOREACH(gr, &gss_resources, gr_link) 316 if (gr->gr_id == id) 317 return (gr->gr_res); 318 319 return (NULL); 320 } 321 322 static uint64_t 323 gssd_make_resource(void *res) 324 { 325 struct gss_resource *gr; 326 327 if (!res) 328 return (0); 329 330 gr = malloc(sizeof(struct gss_resource)); 331 if (!gr) 332 return (0); 333 gr->gr_id = (gss_next_id++) + ((uint64_t) gss_start_time << 32); 334 gr->gr_res = res; 335 LIST_INSERT_HEAD(&gss_resources, gr, gr_link); 336 gss_resource_count++; 337 if (debug_level > 1) 338 printf("%d resources allocated\n", gss_resource_count); 339 340 return (gr->gr_id); 341 } 342 343 static void 344 gssd_delete_resource(uint64_t id) 345 { 346 struct gss_resource *gr; 347 348 LIST_FOREACH(gr, &gss_resources, gr_link) { 349 if (gr->gr_id == id) { 350 LIST_REMOVE(gr, gr_link); 351 free(gr); 352 gss_resource_count--; 353 if (debug_level > 1) 354 printf("%d resources allocated\n", 355 gss_resource_count); 356 return; 357 } 358 } 359 } 360 361 static void 362 gssd_verbose_out(const char *fmt, ...) 363 { 364 va_list ap; 365 366 if (verbose != 0) { 367 va_start(ap, fmt); 368 if (debug_level == 0) 369 vsyslog(LOG_INFO | LOG_DAEMON, fmt, ap); 370 else 371 vfprintf(stderr, fmt, ap); 372 va_end(ap); 373 } 374 } 375 376 bool_t 377 gssd_null_1_svc(void *argp, void *result, struct svc_req *rqstp) 378 { 379 380 gssd_verbose_out("gssd_null: done\n"); 381 return (TRUE); 382 } 383 384 bool_t 385 gssd_init_sec_context_1_svc(init_sec_context_args *argp, init_sec_context_res *result, struct svc_req *rqstp) 386 { 387 gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; 388 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; 389 gss_name_t name = GSS_C_NO_NAME; 390 char ccname[PATH_MAX + 5 + 1], *cp, *cp2; 391 int gotone, gotcred; 392 OM_uint32 min_stat; 393 #ifndef WITHOUT_KERBEROS 394 gss_buffer_desc principal_desc; 395 char enctype[sizeof(uint32_t)]; 396 int key_enctype; 397 OM_uint32 maj_stat; 398 #endif 399 400 memset(result, 0, sizeof(*result)); 401 if (hostbased_initiator_cred != 0 && argp->cred != 0 && 402 argp->uid == 0) { 403 /* 404 * These credentials are for a host based initiator name 405 * in a keytab file, which should now have credentials 406 * in /tmp/krb5cc_gssd, because gss_acquire_cred() did 407 * the equivalent of "kinit -k". 408 */ 409 snprintf(ccname, sizeof(ccname), "FILE:%s", 410 GSSD_CREDENTIAL_CACHE_FILE); 411 } else if (ccfile_dirlist[0] != '\0' && argp->cred == 0) { 412 /* 413 * For the "-s" case and no credentials provided as an 414 * argument, search the directory list for an appropriate 415 * credential cache file. If the search fails, return failure. 416 */ 417 gotone = 0; 418 cp = ccfile_dirlist; 419 do { 420 cp2 = strchr(cp, ':'); 421 if (cp2 != NULL) 422 *cp2 = '\0'; 423 gotone = find_ccache_file(cp, argp->uid, ccname); 424 if (gotone != 0) 425 break; 426 if (cp2 != NULL) 427 *cp2++ = ':'; 428 cp = cp2; 429 } while (cp != NULL && *cp != '\0'); 430 if (gotone == 0) { 431 result->major_status = GSS_S_CREDENTIALS_EXPIRED; 432 gssd_verbose_out("gssd_init_sec_context: -s no" 433 " credential cache file found for uid=%d\n", 434 (int)argp->uid); 435 return (TRUE); 436 } 437 } else { 438 /* 439 * If there wasn't a "-s" option or the credentials have 440 * been provided as an argument, do it the old way. 441 * When credentials are provided, the uid should be root. 442 */ 443 if (argp->cred != 0 && argp->uid != 0) { 444 if (debug_level == 0) 445 syslog(LOG_ERR, "gss_init_sec_context:" 446 " cred for non-root"); 447 else 448 fprintf(stderr, "gss_init_sec_context:" 449 " cred for non-root\n"); 450 } 451 snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d", 452 (int) argp->uid); 453 } 454 setenv("KRB5CCNAME", ccname, TRUE); 455 456 if (argp->cred) { 457 cred = gssd_find_resource(argp->cred); 458 if (!cred) { 459 result->major_status = GSS_S_CREDENTIALS_EXPIRED; 460 gssd_verbose_out("gssd_init_sec_context: cred" 461 " resource not found\n"); 462 return (TRUE); 463 } 464 } 465 if (argp->ctx) { 466 ctx = gssd_find_resource(argp->ctx); 467 if (!ctx) { 468 result->major_status = GSS_S_CONTEXT_EXPIRED; 469 gssd_verbose_out("gssd_init_sec_context: context" 470 " resource not found\n"); 471 return (TRUE); 472 } 473 } 474 if (argp->name) { 475 name = gssd_find_resource(argp->name); 476 if (!name) { 477 result->major_status = GSS_S_BAD_NAME; 478 gssd_verbose_out("gssd_init_sec_context: name" 479 " resource not found\n"); 480 return (TRUE); 481 } 482 } 483 gotcred = 0; 484 485 #ifndef WITHOUT_KERBEROS 486 if (use_old_des != 0) { 487 if (cred == GSS_C_NO_CREDENTIAL) { 488 /* Acquire a credential for the uid. */ 489 maj_stat = gssd_get_user_cred(&min_stat, argp->uid, 490 &cred); 491 if (maj_stat == GSS_S_COMPLETE) 492 gotcred = 1; 493 else 494 gssd_verbose_out("gssd_init_sec_context: " 495 "get user cred failed uid=%d major=0x%x " 496 "minor=%d\n", (int)argp->uid, 497 (unsigned int)maj_stat, (int)min_stat); 498 } 499 if (cred != GSS_C_NO_CREDENTIAL) { 500 key_enctype = ETYPE_DES_CBC_CRC; 501 enctype[0] = (key_enctype >> 24) & 0xff; 502 enctype[1] = (key_enctype >> 16) & 0xff; 503 enctype[2] = (key_enctype >> 8) & 0xff; 504 enctype[3] = key_enctype & 0xff; 505 principal_desc.length = sizeof(enctype); 506 principal_desc.value = enctype; 507 result->major_status = gss_set_cred_option( 508 &result->minor_status, &cred, 509 GSS_KRB5_SET_ALLOWABLE_ENCTYPES_X, 510 &principal_desc); 511 gssd_verbose_out("gssd_init_sec_context: set allowable " 512 "enctype major=0x%x minor=%d\n", 513 (unsigned int)result->major_status, 514 (int)result->minor_status); 515 if (result->major_status != GSS_S_COMPLETE) { 516 if (gotcred != 0) 517 gss_release_cred(&min_stat, &cred); 518 return (TRUE); 519 } 520 } 521 } 522 #endif 523 result->major_status = gss_init_sec_context(&result->minor_status, 524 cred, &ctx, name, argp->mech_type, 525 argp->req_flags, argp->time_req, argp->input_chan_bindings, 526 &argp->input_token, &result->actual_mech_type, 527 &result->output_token, &result->ret_flags, &result->time_rec); 528 gssd_verbose_out("gssd_init_sec_context: done major=0x%x minor=%d" 529 " uid=%d\n", (unsigned int)result->major_status, 530 (int)result->minor_status, (int)argp->uid); 531 if (gotcred != 0) 532 gss_release_cred(&min_stat, &cred); 533 534 if (result->major_status == GSS_S_COMPLETE 535 || result->major_status == GSS_S_CONTINUE_NEEDED) { 536 if (argp->ctx) 537 result->ctx = argp->ctx; 538 else 539 result->ctx = gssd_make_resource(ctx); 540 } 541 542 return (TRUE); 543 } 544 545 bool_t 546 gssd_accept_sec_context_1_svc(accept_sec_context_args *argp, accept_sec_context_res *result, struct svc_req *rqstp) 547 { 548 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; 549 gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; 550 gss_name_t src_name; 551 gss_cred_id_t delegated_cred_handle; 552 553 memset(result, 0, sizeof(*result)); 554 if (argp->ctx) { 555 ctx = gssd_find_resource(argp->ctx); 556 if (!ctx) { 557 result->major_status = GSS_S_CONTEXT_EXPIRED; 558 gssd_verbose_out("gssd_accept_sec_context: ctx" 559 " resource not found\n"); 560 return (TRUE); 561 } 562 } 563 if (argp->cred) { 564 cred = gssd_find_resource(argp->cred); 565 if (!cred) { 566 result->major_status = GSS_S_CREDENTIALS_EXPIRED; 567 gssd_verbose_out("gssd_accept_sec_context: cred" 568 " resource not found\n"); 569 return (TRUE); 570 } 571 } 572 573 memset(result, 0, sizeof(*result)); 574 result->major_status = gss_accept_sec_context(&result->minor_status, 575 &ctx, cred, &argp->input_token, argp->input_chan_bindings, 576 &src_name, &result->mech_type, &result->output_token, 577 &result->ret_flags, &result->time_rec, 578 &delegated_cred_handle); 579 gssd_verbose_out("gssd_accept_sec_context: done major=0x%x minor=%d\n", 580 (unsigned int)result->major_status, (int)result->minor_status); 581 582 if (result->major_status == GSS_S_COMPLETE 583 || result->major_status == GSS_S_CONTINUE_NEEDED) { 584 if (argp->ctx) 585 result->ctx = argp->ctx; 586 else 587 result->ctx = gssd_make_resource(ctx); 588 result->src_name = gssd_make_resource(src_name); 589 result->delegated_cred_handle = 590 gssd_make_resource(delegated_cred_handle); 591 } 592 593 return (TRUE); 594 } 595 596 bool_t 597 gssd_delete_sec_context_1_svc(delete_sec_context_args *argp, delete_sec_context_res *result, struct svc_req *rqstp) 598 { 599 gss_ctx_id_t ctx = gssd_find_resource(argp->ctx); 600 601 if (ctx) { 602 result->major_status = gss_delete_sec_context( 603 &result->minor_status, &ctx, &result->output_token); 604 gssd_delete_resource(argp->ctx); 605 } else { 606 result->major_status = GSS_S_COMPLETE; 607 result->minor_status = 0; 608 } 609 gssd_verbose_out("gssd_delete_sec_context: done major=0x%x minor=%d\n", 610 (unsigned int)result->major_status, (int)result->minor_status); 611 612 return (TRUE); 613 } 614 615 bool_t 616 gssd_export_sec_context_1_svc(export_sec_context_args *argp, export_sec_context_res *result, struct svc_req *rqstp) 617 { 618 gss_ctx_id_t ctx = gssd_find_resource(argp->ctx); 619 620 if (ctx) { 621 result->major_status = gss_export_sec_context( 622 &result->minor_status, &ctx, 623 &result->interprocess_token); 624 result->format = KGSS_HEIMDAL_1_1; 625 gssd_delete_resource(argp->ctx); 626 } else { 627 result->major_status = GSS_S_FAILURE; 628 result->minor_status = 0; 629 result->interprocess_token.length = 0; 630 result->interprocess_token.value = NULL; 631 } 632 gssd_verbose_out("gssd_export_sec_context: done major=0x%x minor=%d\n", 633 (unsigned int)result->major_status, (int)result->minor_status); 634 635 return (TRUE); 636 } 637 638 bool_t 639 gssd_import_name_1_svc(import_name_args *argp, import_name_res *result, struct svc_req *rqstp) 640 { 641 gss_name_t name; 642 643 result->major_status = gss_import_name(&result->minor_status, 644 &argp->input_name_buffer, argp->input_name_type, &name); 645 gssd_verbose_out("gssd_import_name: done major=0x%x minor=%d\n", 646 (unsigned int)result->major_status, (int)result->minor_status); 647 648 if (result->major_status == GSS_S_COMPLETE) 649 result->output_name = gssd_make_resource(name); 650 else 651 result->output_name = 0; 652 653 return (TRUE); 654 } 655 656 bool_t 657 gssd_canonicalize_name_1_svc(canonicalize_name_args *argp, canonicalize_name_res *result, struct svc_req *rqstp) 658 { 659 gss_name_t name = gssd_find_resource(argp->input_name); 660 gss_name_t output_name; 661 662 memset(result, 0, sizeof(*result)); 663 if (!name) { 664 result->major_status = GSS_S_BAD_NAME; 665 return (TRUE); 666 } 667 668 result->major_status = gss_canonicalize_name(&result->minor_status, 669 name, argp->mech_type, &output_name); 670 gssd_verbose_out("gssd_canonicalize_name: done major=0x%x minor=%d\n", 671 (unsigned int)result->major_status, (int)result->minor_status); 672 673 if (result->major_status == GSS_S_COMPLETE) 674 result->output_name = gssd_make_resource(output_name); 675 else 676 result->output_name = 0; 677 678 return (TRUE); 679 } 680 681 bool_t 682 gssd_export_name_1_svc(export_name_args *argp, export_name_res *result, struct svc_req *rqstp) 683 { 684 gss_name_t name = gssd_find_resource(argp->input_name); 685 686 memset(result, 0, sizeof(*result)); 687 if (!name) { 688 result->major_status = GSS_S_BAD_NAME; 689 gssd_verbose_out("gssd_export_name: name resource not found\n"); 690 return (TRUE); 691 } 692 693 result->major_status = gss_export_name(&result->minor_status, 694 name, &result->exported_name); 695 gssd_verbose_out("gssd_export_name: done major=0x%x minor=%d\n", 696 (unsigned int)result->major_status, (int)result->minor_status); 697 698 return (TRUE); 699 } 700 701 bool_t 702 gssd_release_name_1_svc(release_name_args *argp, release_name_res *result, struct svc_req *rqstp) 703 { 704 gss_name_t name = gssd_find_resource(argp->input_name); 705 706 if (name) { 707 result->major_status = gss_release_name(&result->minor_status, 708 &name); 709 gssd_delete_resource(argp->input_name); 710 } else { 711 result->major_status = GSS_S_COMPLETE; 712 result->minor_status = 0; 713 } 714 gssd_verbose_out("gssd_release_name: done major=0x%x minor=%d\n", 715 (unsigned int)result->major_status, (int)result->minor_status); 716 717 return (TRUE); 718 } 719 720 bool_t 721 gssd_pname_to_uid_1_svc(pname_to_uid_args *argp, pname_to_uid_res *result, struct svc_req *rqstp) 722 { 723 gss_name_t name = gssd_find_resource(argp->pname); 724 uid_t uid; 725 char buf[1024], *bufp; 726 struct passwd pwd, *pw; 727 size_t buflen; 728 int error; 729 static size_t buflen_hint = 1024; 730 731 memset(result, 0, sizeof(*result)); 732 if (name) { 733 result->major_status = 734 gss_pname_to_uid(&result->minor_status, 735 name, argp->mech, &uid); 736 if (result->major_status == GSS_S_COMPLETE) { 737 result->uid = uid; 738 buflen = buflen_hint; 739 for (;;) { 740 pw = NULL; 741 bufp = buf; 742 if (buflen > sizeof(buf)) 743 bufp = malloc(buflen); 744 if (bufp == NULL) 745 break; 746 error = getpwuid_r(uid, &pwd, bufp, buflen, 747 &pw); 748 if (error != ERANGE) 749 break; 750 if (buflen > sizeof(buf)) 751 free(bufp); 752 buflen += 1024; 753 if (buflen > buflen_hint) 754 buflen_hint = buflen; 755 } 756 if (pw) { 757 int len = NGROUPS; 758 int groups[NGROUPS]; 759 result->gid = pw->pw_gid; 760 getgrouplist(pw->pw_name, pw->pw_gid, 761 groups, &len); 762 result->gidlist.gidlist_len = len; 763 result->gidlist.gidlist_val = 764 mem_alloc(len * sizeof(int)); 765 memcpy(result->gidlist.gidlist_val, groups, 766 len * sizeof(int)); 767 gssd_verbose_out("gssd_pname_to_uid: mapped" 768 " to uid=%d, gid=%d\n", (int)result->uid, 769 (int)result->gid); 770 } else { 771 result->gid = 65534; 772 result->gidlist.gidlist_len = 0; 773 result->gidlist.gidlist_val = NULL; 774 gssd_verbose_out("gssd_pname_to_uid: mapped" 775 " to uid=%d, but no groups\n", 776 (int)result->uid); 777 } 778 if (bufp != NULL && buflen > sizeof(buf)) 779 free(bufp); 780 } else 781 gssd_verbose_out("gssd_pname_to_uid: failed major=0x%x" 782 " minor=%d\n", (unsigned int)result->major_status, 783 (int)result->minor_status); 784 } else { 785 result->major_status = GSS_S_BAD_NAME; 786 result->minor_status = 0; 787 gssd_verbose_out("gssd_pname_to_uid: no name\n"); 788 } 789 790 return (TRUE); 791 } 792 793 bool_t 794 gssd_acquire_cred_1_svc(acquire_cred_args *argp, acquire_cred_res *result, struct svc_req *rqstp) 795 { 796 gss_name_t desired_name = GSS_C_NO_NAME; 797 gss_cred_id_t cred; 798 char ccname[PATH_MAX + 5 + 1], *cp, *cp2; 799 int gotone; 800 #ifndef WITHOUT_KERBEROS 801 gss_buffer_desc namebuf; 802 uint32_t minstat; 803 krb5_error_code kret; 804 #endif 805 806 memset(result, 0, sizeof(*result)); 807 if (argp->desired_name) { 808 desired_name = gssd_find_resource(argp->desired_name); 809 if (!desired_name) { 810 result->major_status = GSS_S_BAD_NAME; 811 gssd_verbose_out("gssd_acquire_cred: no desired name" 812 " found\n"); 813 return (TRUE); 814 } 815 } 816 817 #ifndef WITHOUT_KERBEROS 818 if (hostbased_initiator_cred != 0 && argp->desired_name != 0 && 819 argp->uid == 0 && argp->cred_usage == GSS_C_INITIATE) { 820 /* This is a host based initiator name in the keytab file. */ 821 snprintf(ccname, sizeof(ccname), "FILE:%s", 822 GSSD_CREDENTIAL_CACHE_FILE); 823 setenv("KRB5CCNAME", ccname, TRUE); 824 result->major_status = gss_display_name(&result->minor_status, 825 desired_name, &namebuf, NULL); 826 gssd_verbose_out("gssd_acquire_cred: desired name for host " 827 "based initiator cred major=0x%x minor=%d\n", 828 (unsigned int)result->major_status, 829 (int)result->minor_status); 830 if (result->major_status != GSS_S_COMPLETE) 831 return (TRUE); 832 if (namebuf.length > PATH_MAX + 5) { 833 result->minor_status = 0; 834 result->major_status = GSS_S_FAILURE; 835 return (TRUE); 836 } 837 memcpy(ccname, namebuf.value, namebuf.length); 838 ccname[namebuf.length] = '\0'; 839 if ((cp = strchr(ccname, '@')) != NULL) 840 *cp = '/'; 841 kret = gssd_get_cc_from_keytab(ccname); 842 gssd_verbose_out("gssd_acquire_cred: using keytab entry for " 843 "%s, kerberos ret=%d\n", ccname, (int)kret); 844 gss_release_buffer(&minstat, &namebuf); 845 if (kret != 0) { 846 result->minor_status = kret; 847 result->major_status = GSS_S_FAILURE; 848 return (TRUE); 849 } 850 } else 851 #endif /* !WITHOUT_KERBEROS */ 852 if (ccfile_dirlist[0] != '\0' && argp->desired_name == 0) { 853 /* 854 * For the "-s" case and no name provided as an 855 * argument, search the directory list for an appropriate 856 * credential cache file. If the search fails, return failure. 857 */ 858 gotone = 0; 859 cp = ccfile_dirlist; 860 do { 861 cp2 = strchr(cp, ':'); 862 if (cp2 != NULL) 863 *cp2 = '\0'; 864 gotone = find_ccache_file(cp, argp->uid, ccname); 865 if (gotone != 0) 866 break; 867 if (cp2 != NULL) 868 *cp2++ = ':'; 869 cp = cp2; 870 } while (cp != NULL && *cp != '\0'); 871 if (gotone == 0) { 872 result->major_status = GSS_S_CREDENTIALS_EXPIRED; 873 gssd_verbose_out("gssd_acquire_cred: no cred cache" 874 " file found\n"); 875 return (TRUE); 876 } 877 setenv("KRB5CCNAME", ccname, TRUE); 878 } else { 879 /* 880 * If there wasn't a "-s" option or the name has 881 * been provided as an argument, do it the old way. 882 * When a name is provided, it will normally exist in the 883 * default keytab file and the uid will be root. 884 */ 885 if (argp->desired_name != 0 && argp->uid != 0) { 886 if (debug_level == 0) 887 syslog(LOG_ERR, "gss_acquire_cred:" 888 " principal_name for non-root"); 889 else 890 fprintf(stderr, "gss_acquire_cred:" 891 " principal_name for non-root\n"); 892 } 893 snprintf(ccname, sizeof(ccname), "FILE:/tmp/krb5cc_%d", 894 (int) argp->uid); 895 setenv("KRB5CCNAME", ccname, TRUE); 896 } 897 898 result->major_status = gss_acquire_cred(&result->minor_status, 899 desired_name, argp->time_req, argp->desired_mechs, 900 argp->cred_usage, &cred, &result->actual_mechs, &result->time_rec); 901 gssd_verbose_out("gssd_acquire_cred: done major=0x%x minor=%d\n", 902 (unsigned int)result->major_status, (int)result->minor_status); 903 904 if (result->major_status == GSS_S_COMPLETE) 905 result->output_cred = gssd_make_resource(cred); 906 else 907 result->output_cred = 0; 908 909 return (TRUE); 910 } 911 912 bool_t 913 gssd_set_cred_option_1_svc(set_cred_option_args *argp, set_cred_option_res *result, struct svc_req *rqstp) 914 { 915 gss_cred_id_t cred = gssd_find_resource(argp->cred); 916 917 memset(result, 0, sizeof(*result)); 918 if (!cred) { 919 result->major_status = GSS_S_CREDENTIALS_EXPIRED; 920 gssd_verbose_out("gssd_set_cred: no credentials\n"); 921 return (TRUE); 922 } 923 924 result->major_status = gss_set_cred_option(&result->minor_status, 925 &cred, argp->option_name, &argp->option_value); 926 gssd_verbose_out("gssd_set_cred: done major=0x%x minor=%d\n", 927 (unsigned int)result->major_status, (int)result->minor_status); 928 929 return (TRUE); 930 } 931 932 bool_t 933 gssd_release_cred_1_svc(release_cred_args *argp, release_cred_res *result, struct svc_req *rqstp) 934 { 935 gss_cred_id_t cred = gssd_find_resource(argp->cred); 936 937 if (cred) { 938 result->major_status = gss_release_cred(&result->minor_status, 939 &cred); 940 gssd_delete_resource(argp->cred); 941 } else { 942 result->major_status = GSS_S_COMPLETE; 943 result->minor_status = 0; 944 } 945 gssd_verbose_out("gssd_release_cred: done major=0x%x minor=%d\n", 946 (unsigned int)result->major_status, (int)result->minor_status); 947 948 return (TRUE); 949 } 950 951 bool_t 952 gssd_display_status_1_svc(display_status_args *argp, display_status_res *result, struct svc_req *rqstp) 953 { 954 955 result->message_context = argp->message_context; 956 result->major_status = gss_display_status(&result->minor_status, 957 argp->status_value, argp->status_type, argp->mech_type, 958 &result->message_context, &result->status_string); 959 gssd_verbose_out("gssd_display_status: done major=0x%x minor=%d\n", 960 (unsigned int)result->major_status, (int)result->minor_status); 961 962 return (TRUE); 963 } 964 965 int 966 gssd_1_freeresult(SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result) 967 { 968 /* 969 * We don't use XDR to free the results - anything which was 970 * allocated came from GSS-API. We use xdr_result to figure 971 * out what to do. 972 */ 973 OM_uint32 junk; 974 975 if (xdr_result == (xdrproc_t) xdr_init_sec_context_res) { 976 init_sec_context_res *p = (init_sec_context_res *) result; 977 gss_release_buffer(&junk, &p->output_token); 978 } else if (xdr_result == (xdrproc_t) xdr_accept_sec_context_res) { 979 accept_sec_context_res *p = (accept_sec_context_res *) result; 980 gss_release_buffer(&junk, &p->output_token); 981 } else if (xdr_result == (xdrproc_t) xdr_delete_sec_context_res) { 982 delete_sec_context_res *p = (delete_sec_context_res *) result; 983 gss_release_buffer(&junk, &p->output_token); 984 } else if (xdr_result == (xdrproc_t) xdr_export_sec_context_res) { 985 export_sec_context_res *p = (export_sec_context_res *) result; 986 if (p->interprocess_token.length) 987 memset(p->interprocess_token.value, 0, 988 p->interprocess_token.length); 989 gss_release_buffer(&junk, &p->interprocess_token); 990 } else if (xdr_result == (xdrproc_t) xdr_export_name_res) { 991 export_name_res *p = (export_name_res *) result; 992 gss_release_buffer(&junk, &p->exported_name); 993 } else if (xdr_result == (xdrproc_t) xdr_acquire_cred_res) { 994 acquire_cred_res *p = (acquire_cred_res *) result; 995 gss_release_oid_set(&junk, &p->actual_mechs); 996 } else if (xdr_result == (xdrproc_t) xdr_pname_to_uid_res) { 997 pname_to_uid_res *p = (pname_to_uid_res *) result; 998 if (p->gidlist.gidlist_val) 999 free(p->gidlist.gidlist_val); 1000 } else if (xdr_result == (xdrproc_t) xdr_display_status_res) { 1001 display_status_res *p = (display_status_res *) result; 1002 gss_release_buffer(&junk, &p->status_string); 1003 } 1004 1005 return (TRUE); 1006 } 1007 1008 /* 1009 * Search a directory for the most likely candidate to be used as the 1010 * credential cache for a uid. If successful, return 1 and fill the 1011 * file's path id into "rpath". Otherwise, return 0. 1012 */ 1013 static int 1014 find_ccache_file(const char *dirpath, uid_t uid, char *rpath) 1015 { 1016 DIR *dirp; 1017 struct dirent *dp; 1018 struct stat sb; 1019 time_t exptime, oexptime; 1020 int gotone, len, rating, orating; 1021 char namepath[PATH_MAX + 5 + 1]; 1022 char retpath[PATH_MAX + 5 + 1]; 1023 1024 dirp = opendir(dirpath); 1025 if (dirp == NULL) 1026 return (0); 1027 gotone = 0; 1028 orating = 0; 1029 oexptime = 0; 1030 while ((dp = readdir(dirp)) != NULL) { 1031 len = snprintf(namepath, sizeof(namepath), "%s/%s", dirpath, 1032 dp->d_name); 1033 if (len < sizeof(namepath) && 1034 (hostbased_initiator_cred == 0 || strcmp(namepath, 1035 GSSD_CREDENTIAL_CACHE_FILE) != 0) && 1036 strstr(dp->d_name, ccfile_substring) != NULL && 1037 lstat(namepath, &sb) >= 0 && 1038 sb.st_uid == uid && 1039 S_ISREG(sb.st_mode)) { 1040 len = snprintf(namepath, sizeof(namepath), "FILE:%s/%s", 1041 dirpath, dp->d_name); 1042 if (len < sizeof(namepath) && 1043 is_a_valid_tgt_cache(namepath, uid, &rating, 1044 &exptime) != 0) { 1045 if (gotone == 0 || rating > orating || 1046 (rating == orating && exptime > oexptime)) { 1047 orating = rating; 1048 oexptime = exptime; 1049 strcpy(retpath, namepath); 1050 gotone = 1; 1051 } 1052 } 1053 } 1054 } 1055 closedir(dirp); 1056 if (gotone != 0) { 1057 strcpy(rpath, retpath); 1058 return (1); 1059 } 1060 return (0); 1061 } 1062 1063 /* 1064 * Try to determine if the file is a valid tgt cache file. 1065 * Check that the file has a valid tgt for a principal. 1066 * If it does, return 1, otherwise return 0. 1067 * It also returns a "rating" and the expiry time for the TGT, when found. 1068 * This "rating" is higher based on heuristics that make it more 1069 * likely to be the correct credential cache file to use. It can 1070 * be used by the caller, along with expiry time, to select from 1071 * multiple credential cache files. 1072 */ 1073 static int 1074 is_a_valid_tgt_cache(const char *filepath, uid_t uid, int *retrating, 1075 time_t *retexptime) 1076 { 1077 #ifndef WITHOUT_KERBEROS 1078 krb5_context context; 1079 krb5_principal princ; 1080 krb5_ccache ccache; 1081 krb5_error_code retval; 1082 krb5_cc_cursor curse; 1083 krb5_creds krbcred; 1084 int gotone, orating, rating, ret; 1085 struct passwd *pw; 1086 char *cp, *cp2, *pname; 1087 time_t exptime; 1088 1089 /* Find a likely name for the uid principal. */ 1090 pw = getpwuid(uid); 1091 1092 /* 1093 * Do a bunch of krb5 library stuff to try and determine if 1094 * this file is a credentials cache with an appropriate TGT 1095 * in it. 1096 */ 1097 retval = krb5_init_context(&context); 1098 if (retval != 0) 1099 return (0); 1100 retval = krb5_cc_resolve(context, filepath, &ccache); 1101 if (retval != 0) { 1102 krb5_free_context(context); 1103 return (0); 1104 } 1105 ret = 0; 1106 orating = 0; 1107 exptime = 0; 1108 retval = krb5_cc_start_seq_get(context, ccache, &curse); 1109 if (retval == 0) { 1110 while ((retval = krb5_cc_next_cred(context, ccache, &curse, 1111 &krbcred)) == 0) { 1112 gotone = 0; 1113 rating = 0; 1114 retval = krb5_unparse_name(context, krbcred.server, 1115 &pname); 1116 if (retval == 0) { 1117 cp = strchr(pname, '/'); 1118 if (cp != NULL) { 1119 *cp++ = '\0'; 1120 if (strcmp(pname, "krbtgt") == 0 && 1121 krbcred.times.endtime > time(NULL) 1122 ) { 1123 gotone = 1; 1124 /* 1125 * Test to see if this is a 1126 * tgt for cross-realm auth. 1127 * Rate it higher, if it is not. 1128 */ 1129 cp2 = strchr(cp, '@'); 1130 if (cp2 != NULL) { 1131 *cp2++ = '\0'; 1132 if (strcmp(cp, cp2) == 1133 0) 1134 rating++; 1135 } 1136 } 1137 } 1138 free(pname); 1139 } 1140 if (gotone != 0) { 1141 retval = krb5_unparse_name(context, 1142 krbcred.client, &pname); 1143 if (retval == 0) { 1144 cp = strchr(pname, '@'); 1145 if (cp != NULL) { 1146 *cp++ = '\0'; 1147 if (pw != NULL && strcmp(pname, 1148 pw->pw_name) == 0) 1149 rating++; 1150 if (strchr(pname, '/') == NULL) 1151 rating++; 1152 if (pref_realm[0] != '\0' && 1153 strcmp(cp, pref_realm) == 0) 1154 rating++; 1155 } 1156 } 1157 free(pname); 1158 if (rating > orating) { 1159 orating = rating; 1160 exptime = krbcred.times.endtime; 1161 } else if (rating == orating && 1162 krbcred.times.endtime > exptime) 1163 exptime = krbcred.times.endtime; 1164 ret = 1; 1165 } 1166 krb5_free_cred_contents(context, &krbcred); 1167 } 1168 krb5_cc_end_seq_get(context, ccache, &curse); 1169 } 1170 krb5_cc_close(context, ccache); 1171 krb5_free_context(context); 1172 if (ret != 0) { 1173 *retrating = orating; 1174 *retexptime = exptime; 1175 } 1176 return (ret); 1177 #else /* WITHOUT_KERBEROS */ 1178 return (0); 1179 #endif /* !WITHOUT_KERBEROS */ 1180 } 1181 1182 #ifndef WITHOUT_KERBEROS 1183 /* 1184 * This function attempts to do essentially a "kinit -k" for the principal 1185 * name provided as the argument, so that there will be a TGT in the 1186 * credential cache. 1187 */ 1188 static krb5_error_code 1189 gssd_get_cc_from_keytab(const char *name) 1190 { 1191 krb5_error_code ret, opt_ret, princ_ret, cc_ret, kt_ret, cred_ret; 1192 krb5_context context; 1193 krb5_principal principal; 1194 krb5_keytab kt; 1195 krb5_creds cred; 1196 krb5_get_init_creds_opt *opt; 1197 krb5_deltat start_time = 0; 1198 krb5_ccache ccache; 1199 1200 ret = krb5_init_context(&context); 1201 if (ret != 0) 1202 return (ret); 1203 opt_ret = cc_ret = kt_ret = cred_ret = 1; /* anything non-zero */ 1204 princ_ret = ret = krb5_parse_name(context, name, &principal); 1205 if (ret == 0) 1206 opt_ret = ret = krb5_get_init_creds_opt_alloc(context, &opt); 1207 if (ret == 0) 1208 cc_ret = ret = krb5_cc_default(context, &ccache); 1209 if (ret == 0) 1210 ret = krb5_cc_initialize(context, ccache, principal); 1211 if (ret == 0) { 1212 krb5_get_init_creds_opt_set_default_flags(context, "gssd", 1213 krb5_principal_get_realm(context, principal), opt); 1214 kt_ret = ret = krb5_kt_default(context, &kt); 1215 } 1216 if (ret == 0) 1217 cred_ret = ret = krb5_get_init_creds_keytab(context, &cred, 1218 principal, kt, start_time, NULL, opt); 1219 if (ret == 0) 1220 ret = krb5_cc_store_cred(context, ccache, &cred); 1221 if (kt_ret == 0) 1222 krb5_kt_close(context, kt); 1223 if (cc_ret == 0) 1224 krb5_cc_close(context, ccache); 1225 if (opt_ret == 0) 1226 krb5_get_init_creds_opt_free(context, opt); 1227 if (princ_ret == 0) 1228 krb5_free_principal(context, principal); 1229 if (cred_ret == 0) 1230 krb5_free_cred_contents(context, &cred); 1231 krb5_free_context(context); 1232 return (ret); 1233 } 1234 1235 /* 1236 * Acquire a gss credential for a uid. 1237 */ 1238 static OM_uint32 1239 gssd_get_user_cred(OM_uint32 *min_statp, uid_t uid, gss_cred_id_t *credp) 1240 { 1241 gss_buffer_desc principal_desc; 1242 gss_name_t name; 1243 OM_uint32 maj_stat, min_stat; 1244 gss_OID_set mechlist; 1245 struct passwd *pw; 1246 1247 pw = getpwuid(uid); 1248 if (pw == NULL) { 1249 *min_statp = 0; 1250 return (GSS_S_FAILURE); 1251 } 1252 1253 /* 1254 * The mechanism must be set to KerberosV for acquisition 1255 * of credentials to work reliably. 1256 */ 1257 maj_stat = gss_create_empty_oid_set(min_statp, &mechlist); 1258 if (maj_stat != GSS_S_COMPLETE) 1259 return (maj_stat); 1260 maj_stat = gss_add_oid_set_member(min_statp, GSS_KRB5_MECH_OID_X, 1261 &mechlist); 1262 if (maj_stat != GSS_S_COMPLETE) { 1263 gss_release_oid_set(&min_stat, &mechlist); 1264 return (maj_stat); 1265 } 1266 1267 principal_desc.value = (void *)pw->pw_name; 1268 principal_desc.length = strlen(pw->pw_name); 1269 maj_stat = gss_import_name(min_statp, &principal_desc, 1270 GSS_C_NT_USER_NAME, &name); 1271 if (maj_stat != GSS_S_COMPLETE) { 1272 gss_release_oid_set(&min_stat, &mechlist); 1273 return (maj_stat); 1274 } 1275 /* Acquire the credentials. */ 1276 maj_stat = gss_acquire_cred(min_statp, name, 0, mechlist, 1277 GSS_C_INITIATE, credp, NULL, NULL); 1278 gss_release_name(&min_stat, &name); 1279 gss_release_oid_set(&min_stat, &mechlist); 1280 return (maj_stat); 1281 } 1282 #endif /* !WITHOUT_KERBEROS */ 1283 1284 void gssd_terminate(int sig __unused) 1285 { 1286 1287 #ifndef WITHOUT_KERBEROS 1288 if (hostbased_initiator_cred != 0) 1289 unlink(GSSD_CREDENTIAL_CACHE_FILE); 1290 #endif 1291 gssd_syscall(""); 1292 exit(0); 1293 } 1294 1295