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