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