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