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