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