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