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