1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. 23 * Copyright 2018 Nexenta Systems, Inc. All rights reserved. 24 * Copyright 2023 RackTop Systems, Inc. 25 * Copyright 2025 Bill Sommerfeld 26 */ 27 28 29 /* 30 * main() of idmapd(8) 31 */ 32 33 #include "idmapd.h" 34 #include <atomic.h> 35 #include <signal.h> 36 #include <rpc/pmap_clnt.h> /* for pmap_unset */ 37 #include <string.h> /* strcmp */ 38 #include <unistd.h> /* setsid */ 39 #include <sys/types.h> 40 #include <memory.h> 41 #include <stropts.h> 42 #include <netconfig.h> 43 #include <sys/resource.h> /* rlimit */ 44 #include <rpcsvc/daemon_utils.h> /* DAEMON_UID and DAEMON_GID */ 45 #include <priv_utils.h> /* privileges */ 46 #include <locale.h> 47 #include <sys/systeminfo.h> 48 #include <errno.h> 49 #include <sys/wait.h> 50 #include <sys/time.h> 51 #include <zone.h> 52 #include <door.h> 53 #include <port.h> 54 #include <tsol/label.h> 55 #include <sys/resource.h> 56 #include <sys/sid.h> 57 #include <sys/idmap.h> 58 #include <pthread.h> 59 #include <stdarg.h> 60 #include <assert.h> 61 #include <note.h> 62 63 #define CBUFSIZ 26 /* ctime(3c) */ 64 65 static void term_handler(int); 66 static void init_idmapd(); 67 static void fini_idmapd(); 68 69 /* The DC Locator lives inside idmap (for now). */ 70 extern void init_dc_locator(void); 71 extern void fini_dc_locator(void); 72 73 idmapd_state_t _idmapdstate; 74 mutex_t _svcstate_lock = ERRORCHECKMUTEX; 75 76 SVCXPRT *xprt = NULL; 77 78 static int dfd = -1; /* our door server fildes, for unregistration */ 79 static boolean_t degraded = B_FALSE; 80 81 82 static uint32_t num_threads = 0; 83 static pthread_key_t create_threads_key; 84 static uint32_t max_threads = 40; 85 86 /* 87 * Server door thread start routine. 88 * 89 * Set a TSD value to the door thread. This enables the destructor to 90 * be called when this thread exits. Note that we need a non-NULL 91 * value for this or the TSD destructor is not called. 92 */ 93 /*ARGSUSED*/ 94 static void * 95 idmapd_door_thread_start(void *arg) 96 { 97 static void *value = "NON-NULL TSD"; 98 99 /* 100 * Disable cancellation to avoid memory leaks from not running 101 * the thread cleanup code. 102 */ 103 (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); 104 (void) pthread_setspecific(create_threads_key, value); 105 (void) door_return(NULL, 0, NULL, 0); 106 107 /* make lint happy */ 108 return (NULL); 109 } 110 111 /* 112 * Server door threads creation 113 */ 114 /*ARGSUSED*/ 115 static void 116 idmapd_door_thread_create(door_info_t *dip) 117 { 118 int num; 119 pthread_t thread_id; 120 121 if ((num = atomic_inc_32_nv(&num_threads)) > max_threads) { 122 atomic_dec_32(&num_threads); 123 idmapdlog(LOG_DEBUG, 124 "thread creation refused - %d threads currently active", 125 num - 1); 126 return; 127 } 128 (void) pthread_create(&thread_id, NULL, idmapd_door_thread_start, NULL); 129 idmapdlog(LOG_DEBUG, 130 "created thread ID %d - %d threads currently active", 131 thread_id, num); 132 } 133 134 /* 135 * Server door thread cleanup 136 */ 137 /*ARGSUSED*/ 138 static void 139 idmapd_door_thread_cleanup(void *arg) 140 { 141 int num; 142 143 /* set TSD to NULL so we don't loop infinitely */ 144 (void) pthread_setspecific(create_threads_key, NULL); 145 num = atomic_dec_32_nv(&num_threads); 146 idmapdlog(LOG_DEBUG, 147 "exiting thread ID %d - %d threads currently active", 148 pthread_self(), num); 149 } 150 151 /* 152 * This is needed for mech_krb5 -- we run as daemon, yes, but we want 153 * mech_krb5 to think we're root so it can get host/nodename.fqdn 154 * tickets for us so we can authenticate to AD as the machine account 155 * that we are. For more details look at the entry point in mech_krb5 156 * corresponding to gss_init_sec_context(). 157 * 158 * As a side effect of faking our effective UID to mech_krb5 we will use 159 * root's default ccache (/tmp/krb5cc_0). But if that's created by 160 * another process then we won't have access to it: we run as daemon and 161 * keep PRIV_FILE_DAC_READ, which is insufficient to share the ccache 162 * with others. We putenv("KRB5CCNAME=/var/run/idmap/ccache") in main() 163 * to avoid this issue; see main(). 164 * 165 * Someday we'll have gss/mech_krb5 extensions for acquiring initiator 166 * creds with keytabs/raw keys, and someday we'll have extensions to 167 * libsasl to specify creds/name to use on the initiator side, and 168 * someday we'll have extensions to libldap to pass those through to 169 * libsasl. Until then this interposer will have to do. 170 * 171 * Also, we have to tell lint to shut up: it thinks app_krb5_user_uid() 172 * is defined but not used. 173 */ 174 /*LINTLIBRARY*/ 175 uid_t 176 app_krb5_user_uid(void) 177 { 178 return (0); 179 } 180 181 /*ARGSUSED*/ 182 static void 183 term_handler(int sig) 184 { 185 idmapdlog(LOG_INFO, "Terminating."); 186 fini_dc_locator(); 187 fini_idmapd(); 188 _exit(0); 189 } 190 191 /*ARGSUSED*/ 192 static void 193 usr1_handler(int sig) 194 { 195 NOTE(ARGUNUSED(sig)) 196 print_idmapdstate(); 197 } 198 199 static int pipe_fd = -1; 200 201 static void 202 daemonize_ready(void) 203 { 204 char data = '\0'; 205 /* 206 * wake the parent 207 */ 208 (void) write(pipe_fd, &data, 1); 209 (void) close(pipe_fd); 210 } 211 212 static int 213 daemonize_start(void) 214 { 215 char data; 216 int status; 217 int devnull; 218 int filedes[2]; 219 pid_t pid; 220 221 (void) sigset(SIGPIPE, SIG_IGN); 222 devnull = open("/dev/null", O_RDONLY); 223 if (devnull < 0) 224 return (-1); 225 (void) dup2(devnull, 0); 226 (void) dup2(2, 1); /* stderr only */ 227 if (pipe(filedes) < 0) 228 return (-1); 229 if ((pid = fork1()) < 0) 230 return (-1); 231 if (pid != 0) { 232 /* 233 * parent 234 */ 235 (void) close(filedes[1]); 236 if (read(filedes[0], &data, 1) == 1) { 237 /* presume success */ 238 _exit(0); 239 } 240 status = -1; 241 (void) wait4(pid, &status, 0, NULL); 242 if (WIFEXITED(status)) 243 _exit(WEXITSTATUS(status)); 244 else 245 _exit(-1); 246 } 247 248 /* 249 * child 250 */ 251 pipe_fd = filedes[1]; 252 (void) close(filedes[0]); 253 (void) setsid(); 254 (void) umask(0077); 255 openlog("idmap", LOG_PID, LOG_DAEMON); 256 257 return (0); 258 } 259 260 261 int 262 main(int argc, char **argv) 263 { 264 int c; 265 struct rlimit rl; 266 267 if (rwlock_init(&_idmapdstate.rwlk_cfg, USYNC_THREAD, NULL) != 0) 268 return (-1); 269 if (mutex_init(&_idmapdstate.addisc_lk, USYNC_THREAD, NULL) != 0) 270 return (-1); 271 if (cond_init(&_idmapdstate.addisc_cv, USYNC_THREAD, NULL) != 0) 272 return (-1); 273 274 _idmapdstate.daemon_mode = TRUE; 275 while ((c = getopt(argc, argv, "d")) != -1) { 276 switch (c) { 277 case 'd': 278 _idmapdstate.daemon_mode = FALSE; 279 break; 280 default: 281 (void) fprintf(stderr, 282 "Usage: /usr/lib/idmapd [-d]\n"); 283 return (SMF_EXIT_ERR_CONFIG); 284 } 285 } 286 287 /* set locale and domain for internationalization */ 288 (void) setlocale(LC_ALL, ""); 289 (void) textdomain(TEXT_DOMAIN); 290 291 idmap_set_logger(idmapdlog); 292 adutils_set_logger(idmapdlog); 293 294 if (is_system_labeled() && getzoneid() != GLOBAL_ZONEID) { 295 idmapdlog(LOG_ERR, 296 "with Trusted Extensions idmapd runs only in the " 297 "global zone"); 298 exit(1); 299 } 300 301 /* 302 * Raise the fd limit to max 303 */ 304 if (getrlimit(RLIMIT_NOFILE, &rl) != 0) { 305 idmapdlog(LOG_ERR, "getrlimit failed"); 306 } else if (rl.rlim_cur < rl.rlim_max) { 307 rl.rlim_cur = rl.rlim_max; 308 if (setrlimit(RLIMIT_NOFILE, &rl) != 0) 309 idmapdlog(LOG_ERR, 310 "Unable to raise RLIMIT_NOFILE to %d", 311 rl.rlim_cur); 312 } 313 314 if (_idmapdstate.daemon_mode == TRUE) { 315 if (daemonize_start() < 0) { 316 idmapdlog(LOG_ERR, "unable to daemonize"); 317 exit(-1); 318 } 319 } else 320 (void) umask(0077); 321 322 idmap_init_tsd_key(); 323 324 init_idmapd(); 325 init_dc_locator(); 326 327 /* signal handlers that should run only after we're initialized */ 328 (void) sigset(SIGTERM, term_handler); 329 (void) sigset(SIGUSR1, usr1_handler); 330 (void) sigset(SIGHUP, idmap_cfg_hup_handler); 331 332 if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, 333 DAEMON_UID, DAEMON_GID, 334 PRIV_PROC_AUDIT, PRIV_FILE_DAC_READ, 335 (char *)NULL) == -1) { 336 idmapdlog(LOG_ERR, "unable to drop privileges"); 337 exit(1); 338 } 339 340 __fini_daemon_priv(PRIV_PROC_FORK, PRIV_PROC_EXEC, PRIV_PROC_SESSION, 341 PRIV_FILE_LINK_ANY, PRIV_PROC_INFO, (char *)NULL); 342 343 if (_idmapdstate.daemon_mode == TRUE) 344 daemonize_ready(); 345 346 /* With doors RPC this just wastes this thread, oh well */ 347 svc_run(); 348 return (0); 349 } 350 351 static void 352 init_idmapd() 353 { 354 int error; 355 int connmaxrec = IDMAP_MAX_DOOR_RPC; 356 357 358 /* create directories as root and chown to daemon uid */ 359 if (create_directory(IDMAP_DBDIR, DAEMON_UID, DAEMON_GID) < 0) 360 exit(1); 361 if (create_directory(IDMAP_CACHEDIR, DAEMON_UID, DAEMON_GID) < 0) 362 exit(1); 363 364 /* 365 * Set KRB5CCNAME in the environment. See app_krb5_user_uid() 366 * for more details. We blow away the existing one, if there is 367 * one. 368 */ 369 (void) unlink(IDMAP_CACHEDIR "/ccache"); 370 (void) putenv("KRB5CCNAME=" IDMAP_CACHEDIR "/ccache"); 371 (void) putenv("MS_INTEROP=1"); 372 373 if (sysinfo(SI_HOSTNAME, _idmapdstate.hostname, 374 sizeof (_idmapdstate.hostname)) == -1) { 375 error = errno; 376 idmapdlog(LOG_ERR, "unable to determine hostname, error: %d", 377 error); 378 exit(1); 379 } 380 381 if ((error = init_mapping_system()) < 0) { 382 idmapdlog(LOG_ERR, "unable to initialize mapping system"); 383 exit(error < -2 ? SMF_EXIT_ERR_CONFIG : 1); 384 } 385 386 /* 387 * This means max_threads can't be updated without restarting idmap. 388 */ 389 RDLOCK_CONFIG(); 390 max_threads = _idmapdstate.cfg->pgcfg.max_threads; 391 UNLOCK_CONFIG(); 392 393 (void) door_server_create(idmapd_door_thread_create); 394 if ((error = pthread_key_create(&create_threads_key, 395 idmapd_door_thread_cleanup)) != 0) { 396 idmapdlog(LOG_ERR, "unable to create threads key (%s)", 397 strerror(error)); 398 goto errout; 399 } 400 401 xprt = svc_door_create(idmap_prog_1, IDMAP_PROG, IDMAP_V1, connmaxrec); 402 if (xprt == NULL) { 403 idmapdlog(LOG_ERR, "unable to create door RPC service"); 404 goto errout; 405 } 406 407 if (!svc_control(xprt, SVCSET_CONNMAXREC, &connmaxrec)) { 408 idmapdlog(LOG_ERR, "unable to limit RPC request size"); 409 goto errout; 410 } 411 412 dfd = xprt->xp_fd; 413 414 if (dfd == -1) { 415 idmapdlog(LOG_ERR, "unable to register door"); 416 goto errout; 417 } 418 if ((error = __idmap_reg(dfd)) != 0) { 419 idmapdlog(LOG_ERR, "unable to register door (%s)", 420 strerror(errno)); 421 goto errout; 422 } 423 424 pthread_mutex_init(&_idmapdstate.id_lock, NULL); 425 426 if ((error = allocids(_idmapdstate.new_eph_db, 427 8192, &_idmapdstate.next_uid, 428 8192, &_idmapdstate.next_gid)) != 0) { 429 idmapdlog(LOG_ERR, "unable to allocate ephemeral IDs (%s)", 430 strerror(errno)); 431 _idmapdstate.next_uid = IDMAP_SENTINEL_PID; 432 _idmapdstate.limit_uid = IDMAP_SENTINEL_PID; 433 _idmapdstate.next_gid = IDMAP_SENTINEL_PID; 434 _idmapdstate.limit_gid = IDMAP_SENTINEL_PID; 435 } else { 436 _idmapdstate.limit_uid = _idmapdstate.next_uid + 8192; 437 _idmapdstate.limit_gid = _idmapdstate.next_gid + 8192; 438 } 439 440 if (DBG(CONFIG, 1)) 441 print_idmapdstate(); 442 443 return; 444 445 errout: 446 fini_idmapd(); 447 exit(1); 448 } 449 450 static void 451 fini_idmapd() 452 { 453 (void) __idmap_unreg(dfd); 454 fini_mapping_system(); 455 if (xprt != NULL) 456 svc_destroy(xprt); 457 } 458 459 static 460 const char * 461 get_fmri(void) 462 { 463 static char *fmri = NULL; 464 static char buf[60]; 465 char *s; 466 467 membar_consumer(); 468 s = fmri; 469 if (s != NULL && *s == '\0') 470 return (NULL); 471 else if (s != NULL) 472 return (s); 473 474 if ((s = getenv("SMF_FMRI")) == NULL || strlen(s) >= sizeof (buf)) 475 buf[0] = '\0'; 476 else 477 (void) strlcpy(buf, s, sizeof (buf)); 478 479 membar_producer(); 480 fmri = buf; 481 482 return (get_fmri()); 483 } 484 485 /* 486 * Wrappers for smf_degrade/restore_instance() 487 * 488 * smf_restore_instance() is too heavy duty to be calling every time we 489 * have a successful AD name<->SID lookup. 490 */ 491 void 492 degrade_svc(int poke_discovery, const char *reason) 493 { 494 const char *fmri; 495 496 membar_consumer(); 497 if (degraded) 498 return; 499 500 idmapdlog(LOG_ERR, "Degraded operation (%s).", reason); 501 502 membar_producer(); 503 degraded = B_TRUE; 504 505 if ((fmri = get_fmri()) != NULL) 506 (void) smf_degrade_instance(fmri, 0); 507 508 /* 509 * If the config update thread is in a state where auto-discovery could 510 * be re-tried, then this will make it try it -- a sort of auto-refresh. 511 */ 512 if (poke_discovery) 513 idmap_cfg_poke_updates(); 514 } 515 516 void 517 restore_svc(void) 518 { 519 const char *fmri; 520 521 membar_consumer(); 522 if (!degraded) 523 return; 524 525 if ((fmri = get_fmri()) == NULL) 526 (void) smf_restore_instance(fmri); 527 528 membar_producer(); 529 degraded = B_FALSE; 530 531 idmapdlog(LOG_NOTICE, "Normal operation restored"); 532 } 533 534 535 /* printflike */ 536 void 537 idmapdlog(int pri, const char *format, ...) 538 { 539 static time_t prev_ts; 540 va_list args; 541 char cbuf[CBUFSIZ]; 542 time_t ts; 543 544 ts = time(NULL); 545 if (prev_ts != ts) { 546 prev_ts = ts; 547 /* NB: cbuf has \n */ 548 (void) fprintf(stderr, "@ %s", 549 ctime_r(&ts, cbuf, sizeof (cbuf))); 550 } 551 552 va_start(args, format); 553 (void) vfprintf(stderr, format, args); 554 (void) fprintf(stderr, "\n"); 555 va_end(args); 556 557 /* 558 * We don't want to fill up the logs with useless messages when 559 * we're degraded, but we still want to log. 560 */ 561 if (degraded) 562 pri = LOG_DEBUG; 563 564 va_start(args, format); 565 vsyslog(pri, format, args); 566 va_end(args); 567 } 568 569 static void 570 trace_str(nvlist_t *entry, char *n1, char *n2, char *str) 571 { 572 char name[IDMAP_TRACE_NAME_MAX+1]; /* Max used is only about 11 */ 573 574 (void) strlcpy(name, n1, sizeof (name)); 575 if (n2 != NULL) 576 (void) strlcat(name, n2, sizeof (name)); 577 578 (void) nvlist_add_string(entry, name, str); 579 } 580 581 static void 582 trace_int(nvlist_t *entry, char *n1, char *n2, int64_t i) 583 { 584 char name[IDMAP_TRACE_NAME_MAX+1]; /* Max used is only about 11 */ 585 586 (void) strlcpy(name, n1, sizeof (name)); 587 if (n2 != NULL) 588 (void) strlcat(name, n2, sizeof (name)); 589 590 (void) nvlist_add_int64(entry, name, i); 591 } 592 593 static void 594 trace_sid(nvlist_t *entry, char *n1, char *n2, idmap_sid *sid) 595 { 596 char *str; 597 598 (void) asprintf(&str, "%s-%u", sid->prefix, sid->rid); 599 if (str == NULL) 600 return; 601 602 trace_str(entry, n1, n2, str); 603 free(str); 604 } 605 606 static void 607 trace_id(nvlist_t *entry, char *fromto, idmap_id *id, char *name, char *domain) 608 { 609 trace_int(entry, fromto, IDMAP_TRACE_TYPE, (int64_t)id->idtype); 610 if (IS_ID_SID(*id)) { 611 if (name != NULL) { 612 char *str; 613 614 (void) asprintf(&str, "%s%s%s", name, 615 domain == NULL ? "" : "@", 616 domain == NULL ? "" : domain); 617 if (str != NULL) { 618 trace_str(entry, fromto, IDMAP_TRACE_NAME, str); 619 free(str); 620 } 621 } 622 if (id->idmap_id_u.sid.prefix != NULL) { 623 trace_sid(entry, fromto, IDMAP_TRACE_SID, 624 &id->idmap_id_u.sid); 625 } 626 } else if (IS_ID_POSIX(*id)) { 627 if (name != NULL) 628 trace_str(entry, fromto, IDMAP_TRACE_NAME, name); 629 if (id->idmap_id_u.uid != IDMAP_SENTINEL_PID) { 630 trace_int(entry, fromto, IDMAP_TRACE_UNIXID, 631 (int64_t)id->idmap_id_u.uid); 632 } 633 } 634 } 635 636 /* 637 * Record a trace event. TRACE() has already decided whether or not 638 * tracing is required; what we do here is collect the data and send it 639 * to its destination - to the trace log in the response, if 640 * IDMAP_REQ_FLG_TRACE is set, and to the SMF service log, if debug/mapping 641 * is greater than zero. 642 */ 643 int 644 trace(idmap_mapping *req, idmap_id_res *res, char *fmt, ...) 645 { 646 va_list va; 647 char *buf; 648 int err; 649 nvlist_t *entry; 650 651 assert(req != NULL); 652 assert(res != NULL); 653 654 err = nvlist_alloc(&entry, NV_UNIQUE_NAME, 0); 655 if (err != 0) { 656 (void) fprintf(stderr, "trace nvlist_alloc(entry): %s\n", 657 strerror(err)); 658 return (0); 659 } 660 661 trace_id(entry, "from", &req->id1, req->id1name, req->id1domain); 662 trace_id(entry, "to", &res->id, req->id2name, req->id2domain); 663 664 if (IDMAP_ERROR(res->retcode)) { 665 trace_int(entry, IDMAP_TRACE_ERROR, NULL, 666 (int64_t)res->retcode); 667 } 668 669 va_start(va, fmt); 670 (void) vasprintf(&buf, fmt, va); 671 va_end(va); 672 if (buf != NULL) { 673 trace_str(entry, IDMAP_TRACE_MESSAGE, NULL, buf); 674 free(buf); 675 } 676 677 if (DBG(MAPPING, 1)) 678 idmap_trace_print_1(stderr, "", entry); 679 680 if (req->flag & IDMAP_REQ_FLG_TRACE) { 681 /* Lazily allocate the trace list */ 682 if (res->info.trace == NULL) { 683 err = nvlist_alloc(&res->info.trace, 0, 0); 684 if (err != 0) { 685 res->info.trace = NULL; /* just in case */ 686 (void) fprintf(stderr, 687 "trace nvlist_alloc(trace): %s\n", 688 strerror(err)); 689 nvlist_free(entry); 690 return (0); 691 } 692 } 693 (void) nvlist_add_nvlist(res->info.trace, "", entry); 694 /* Note that entry is copied, so we must still free our copy */ 695 } 696 697 nvlist_free(entry); 698 699 return (0); 700 } 701 702 /* 703 * Enable libumem debugging by default on DEBUG builds. 704 * idmapd uses rpcgen, so we can't use #ifdef DEBUG without causing 705 * undesirable behavior. 706 */ 707 #ifdef IDMAPD_DEBUG 708 const char * 709 _umem_debug_init(void) 710 { 711 return ("default,verbose"); /* $UMEM_DEBUG setting */ 712 } 713 714 const char * 715 _umem_logging_init(void) 716 { 717 return ("fail,contents"); /* $UMEM_LOGGING setting */ 718 } 719 #endif 720