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 *
idmapd_door_thread_start(void * arg)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
idmapd_door_thread_create(door_info_t * dip)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
idmapd_door_thread_cleanup(void * arg)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
app_krb5_user_uid(void)176 app_krb5_user_uid(void)
177 {
178 return (0);
179 }
180
181 /*ARGSUSED*/
182 static void
term_handler(int sig)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
usr1_handler(int sig)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
daemonize_ready(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
daemonize_start(void)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
main(int argc,char ** argv)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
init_idmapd()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
fini_idmapd()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 *
get_fmri(void)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
degrade_svc(int poke_discovery,const char * reason)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
restore_svc(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
idmapdlog(int pri,const char * format,...)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
trace_str(nvlist_t * entry,char * n1,char * n2,char * str)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
trace_int(nvlist_t * entry,char * n1,char * n2,int64_t i)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
trace_sid(nvlist_t * entry,char * n1,char * n2,idmap_sid * sid)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
trace_id(nvlist_t * entry,char * fromto,idmap_id * id,char * name,char * domain)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
trace(idmap_mapping * req,idmap_id_res * res,char * fmt,...)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 *
_umem_debug_init(void)709 _umem_debug_init(void)
710 {
711 return ("default,verbose"); /* $UMEM_DEBUG setting */
712 }
713
714 const char *
_umem_logging_init(void)715 _umem_logging_init(void)
716 {
717 return ("fail,contents"); /* $UMEM_LOGGING setting */
718 }
719 #endif
720