comm: add -Wall
[unleashed.git] / usr / src / cmd / idmap / idmapd / idmapd.c
blob8433cbadb3b11d14528ce63602ac7cde882288f9
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2014 Nexenta Systems, Inc. All rights reserved.
28 * main() of idmapd(8)
31 #include "idmapd.h"
32 #include <atomic.h>
33 #include <signal.h>
34 #include <rpc/pmap_clnt.h> /* for pmap_unset */
35 #include <string.h> /* strcmp */
36 #include <unistd.h> /* setsid */
37 #include <sys/types.h>
38 #include <memory.h>
39 #include <stropts.h>
40 #include <netconfig.h>
41 #include <sys/resource.h> /* rlimit */
42 #include <rpcsvc/daemon_utils.h> /* DAEMON_UID and DAEMON_GID */
43 #include <priv_utils.h> /* privileges */
44 #include <locale.h>
45 #include <sys/systeminfo.h>
46 #include <errno.h>
47 #include <sys/wait.h>
48 #include <sys/time.h>
49 #include <zone.h>
50 #include <door.h>
51 #include <port.h>
52 #include <sys/resource.h>
53 #include <sys/sid.h>
54 #include <sys/idmap.h>
55 #include <pthread.h>
56 #include <stdarg.h>
57 #include <assert.h>
58 #include <note.h>
60 #define CBUFSIZ 26 /* ctime(3c) */
62 static void term_handler(int);
63 static void init_idmapd();
64 static void fini_idmapd();
66 /* The DC Locator lives inside idmap (for now). */
67 extern void init_dc_locator(void);
68 extern void fini_dc_locator(void);
70 idmapd_state_t _idmapdstate;
72 SVCXPRT *xprt = NULL;
74 static int dfd = -1; /* our door server fildes, for unregistration */
75 static boolean_t degraded = B_FALSE;
78 static uint32_t num_threads = 0;
79 static pthread_key_t create_threads_key;
80 static uint32_t max_threads = 40;
83 * Server door thread start routine.
85 * Set a TSD value to the door thread. This enables the destructor to
86 * be called when this thread exits.
88 /*ARGSUSED*/
89 static void *
90 idmapd_door_thread_start(void *arg)
92 static void *value = 0;
95 * Disable cancellation to avoid memory leaks from not running
96 * the thread cleanup code.
98 (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
99 (void) pthread_setspecific(create_threads_key, value);
100 (void) door_return(NULL, 0, NULL, 0);
102 /* make lint happy */
103 return (NULL);
107 * Server door threads creation
109 /*ARGSUSED*/
110 static void
111 idmapd_door_thread_create(door_info_t *dip)
113 int num;
114 pthread_t thread_id;
116 if ((num = atomic_inc_32_nv(&num_threads)) > max_threads) {
117 atomic_dec_32(&num_threads);
118 idmapdlog(LOG_DEBUG,
119 "thread creation refused - %d threads currently active",
120 num - 1);
121 return;
123 (void) pthread_create(&thread_id, NULL, idmapd_door_thread_start, NULL);
124 idmapdlog(LOG_DEBUG,
125 "created thread ID %d - %d threads currently active",
126 thread_id, num);
130 * Server door thread cleanup
132 /*ARGSUSED*/
133 static void
134 idmapd_door_thread_cleanup(void *arg)
136 int num;
138 num = atomic_dec_32_nv(&num_threads);
139 idmapdlog(LOG_DEBUG,
140 "exiting thread ID %d - %d threads currently active",
141 pthread_self(), num);
145 * This is needed for mech_krb5 -- we run as daemon, yes, but we want
146 * mech_krb5 to think we're root so it can get host/nodename.fqdn
147 * tickets for us so we can authenticate to AD as the machine account
148 * that we are. For more details look at the entry point in mech_krb5
149 * corresponding to gss_init_sec_context().
151 * As a side effect of faking our effective UID to mech_krb5 we will use
152 * root's default ccache (/tmp/krb5cc_0). But if that's created by
153 * another process then we won't have access to it: we run as daemon and
154 * keep PRIV_FILE_DAC_READ, which is insufficient to share the ccache
155 * with others. We putenv("KRB5CCNAME=/var/run/idmap/ccache") in main()
156 * to avoid this issue; see main().
158 * Someday we'll have gss/mech_krb5 extensions for acquiring initiator
159 * creds with keytabs/raw keys, and someday we'll have extensions to
160 * libsasl to specify creds/name to use on the initiator side, and
161 * someday we'll have extensions to libldap to pass those through to
162 * libsasl. Until then this interposer will have to do.
164 * Also, we have to tell lint to shut up: it thinks app_krb5_user_uid()
165 * is defined but not used.
167 /*LINTLIBRARY*/
168 uid_t
169 app_krb5_user_uid(void)
171 return (0);
174 /*ARGSUSED*/
175 static void
176 term_handler(int sig)
178 idmapdlog(LOG_INFO, "Terminating.");
179 fini_dc_locator();
180 fini_idmapd();
181 _exit(0);
184 /*ARGSUSED*/
185 static void
186 usr1_handler(int sig)
188 NOTE(ARGUNUSED(sig))
189 print_idmapdstate();
192 static int pipe_fd = -1;
194 static void
195 daemonize_ready(void)
197 char data = '\0';
199 * wake the parent
201 (void) write(pipe_fd, &data, 1);
202 (void) close(pipe_fd);
205 static int
206 daemonize_start(void)
208 char data;
209 int status;
210 int devnull;
211 int filedes[2];
212 pid_t pid;
214 (void) sigset(SIGPIPE, SIG_IGN);
215 devnull = open("/dev/null", O_RDONLY);
216 if (devnull < 0)
217 return (-1);
218 (void) dup2(devnull, 0);
219 (void) dup2(2, 1); /* stderr only */
220 if (pipe(filedes) < 0)
221 return (-1);
222 if ((pid = fork1()) < 0)
223 return (-1);
224 if (pid != 0) {
226 * parent
228 (void) close(filedes[1]);
229 if (read(filedes[0], &data, 1) == 1) {
230 /* presume success */
231 _exit(0);
233 status = -1;
234 (void) wait4(pid, &status, 0, NULL);
235 if (WIFEXITED(status))
236 _exit(WEXITSTATUS(status));
237 else
238 _exit(-1);
242 * child
244 pipe_fd = filedes[1];
245 (void) close(filedes[0]);
246 (void) setsid();
247 (void) umask(0077);
248 openlog("idmap", LOG_PID, LOG_DAEMON);
250 return (0);
255 main(int argc, char **argv)
257 int c;
258 struct rlimit rl;
260 if (rwlock_init(&_idmapdstate.rwlk_cfg, USYNC_THREAD, NULL) != 0)
261 return (-1);
262 if (mutex_init(&_idmapdstate.addisc_lk, USYNC_THREAD, NULL) != 0)
263 return (-1);
264 if (cond_init(&_idmapdstate.addisc_cv, USYNC_THREAD, NULL) != 0)
265 return (-1);
267 _idmapdstate.daemon_mode = TRUE;
268 while ((c = getopt(argc, argv, "d")) != -1) {
269 switch (c) {
270 case 'd':
271 _idmapdstate.daemon_mode = FALSE;
272 break;
273 default:
274 (void) fprintf(stderr,
275 "Usage: /usr/lib/idmapd [-d]\n");
276 return (SMF_EXIT_ERR_CONFIG);
280 /* set locale and domain for internationalization */
281 (void) setlocale(LC_ALL, "");
282 (void) textdomain(TEXT_DOMAIN);
284 idmap_set_logger(idmapdlog);
285 adutils_set_logger(idmapdlog);
288 * Raise the fd limit to max
290 if (getrlimit(RLIMIT_NOFILE, &rl) != 0) {
291 idmapdlog(LOG_ERR, "getrlimit failed");
292 } else if (rl.rlim_cur < rl.rlim_max) {
293 rl.rlim_cur = rl.rlim_max;
294 if (setrlimit(RLIMIT_NOFILE, &rl) != 0)
295 idmapdlog(LOG_ERR,
296 "Unable to raise RLIMIT_NOFILE to %d",
297 rl.rlim_cur);
300 (void) mutex_init(&_svcstate_lock, USYNC_THREAD, NULL);
302 if (_idmapdstate.daemon_mode == TRUE) {
303 if (daemonize_start() < 0) {
304 idmapdlog(LOG_ERR, "unable to daemonize");
305 exit(-1);
307 } else
308 (void) umask(0077);
310 idmap_init_tsd_key();
312 init_idmapd();
313 init_dc_locator();
315 /* signal handlers that should run only after we're initialized */
316 (void) sigset(SIGTERM, term_handler);
317 (void) sigset(SIGUSR1, usr1_handler);
318 (void) sigset(SIGHUP, idmap_cfg_hup_handler);
320 if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, DAEMON_UID,
321 DAEMON_GID, PRIV_PROC_AUDIT, PRIV_FILE_DAC_READ, NULL) == -1) {
322 idmapdlog(LOG_ERR, "unable to drop privileges");
323 exit(1);
326 __fini_daemon_priv(PRIV_PROC_FORK, PRIV_PROC_EXEC, PRIV_PROC_SESSION,
327 PRIV_FILE_LINK_ANY, PRIV_PROC_INFO, NULL);
329 if (_idmapdstate.daemon_mode == TRUE)
330 daemonize_ready();
332 /* With doors RPC this just wastes this thread, oh well */
333 svc_run();
334 return (0);
337 static void
338 init_idmapd()
340 int error;
341 int connmaxrec = IDMAP_MAX_DOOR_RPC;
344 /* create directories as root and chown to daemon uid */
345 if (create_directory(IDMAP_DBDIR, DAEMON_UID, DAEMON_GID) < 0)
346 exit(1);
347 if (create_directory(IDMAP_CACHEDIR, DAEMON_UID, DAEMON_GID) < 0)
348 exit(1);
351 * Set KRB5CCNAME in the environment. See app_krb5_user_uid()
352 * for more details. We blow away the existing one, if there is
353 * one.
355 (void) unlink(IDMAP_CACHEDIR "/ccache");
356 (void) putenv("KRB5CCNAME=" IDMAP_CACHEDIR "/ccache");
357 (void) putenv("MS_INTEROP=1");
359 if (sysinfo(SI_HOSTNAME, _idmapdstate.hostname,
360 sizeof (_idmapdstate.hostname)) == -1) {
361 error = errno;
362 idmapdlog(LOG_ERR, "unable to determine hostname, error: %d",
363 error);
364 exit(1);
367 if ((error = init_mapping_system()) < 0) {
368 idmapdlog(LOG_ERR, "unable to initialize mapping system");
369 exit(error < -2 ? SMF_EXIT_ERR_CONFIG : 1);
372 (void) door_server_create(idmapd_door_thread_create);
373 if ((error = pthread_key_create(&create_threads_key,
374 idmapd_door_thread_cleanup)) != 0) {
375 idmapdlog(LOG_ERR, "unable to create threads key (%s)",
376 strerror(error));
377 goto errout;
380 xprt = svc_door_create(idmap_prog_1, IDMAP_PROG, IDMAP_V1, connmaxrec);
381 if (xprt == NULL) {
382 idmapdlog(LOG_ERR, "unable to create door RPC service");
383 goto errout;
386 if (!svc_control(xprt, SVCSET_CONNMAXREC, &connmaxrec)) {
387 idmapdlog(LOG_ERR, "unable to limit RPC request size");
388 goto errout;
391 dfd = xprt->xp_fd;
393 if (dfd == -1) {
394 idmapdlog(LOG_ERR, "unable to register door");
395 goto errout;
397 if ((error = __idmap_reg(dfd)) != 0) {
398 idmapdlog(LOG_ERR, "unable to register door (%s)",
399 strerror(errno));
400 goto errout;
403 if ((error = allocids(_idmapdstate.new_eph_db,
404 8192, &_idmapdstate.next_uid,
405 8192, &_idmapdstate.next_gid)) != 0) {
406 idmapdlog(LOG_ERR, "unable to allocate ephemeral IDs (%s)",
407 strerror(errno));
408 _idmapdstate.next_uid = IDMAP_SENTINEL_PID;
409 _idmapdstate.limit_uid = IDMAP_SENTINEL_PID;
410 _idmapdstate.next_gid = IDMAP_SENTINEL_PID;
411 _idmapdstate.limit_gid = IDMAP_SENTINEL_PID;
412 } else {
413 _idmapdstate.limit_uid = _idmapdstate.next_uid + 8192;
414 _idmapdstate.limit_gid = _idmapdstate.next_gid + 8192;
417 if (DBG(CONFIG, 1))
418 print_idmapdstate();
420 return;
422 errout:
423 fini_idmapd();
424 exit(1);
427 static void
428 fini_idmapd()
430 (void) __idmap_unreg(dfd);
431 fini_mapping_system();
432 if (xprt != NULL)
433 svc_destroy(xprt);
436 static
437 const char *
438 get_fmri(void)
440 static char *fmri = NULL;
441 static char buf[60];
442 char *s;
444 membar_consumer();
445 s = fmri;
446 if (s != NULL && *s == '\0')
447 return (NULL);
448 else if (s != NULL)
449 return (s);
451 if ((s = getenv("SMF_FMRI")) == NULL || strlen(s) >= sizeof (buf))
452 buf[0] = '\0';
453 else
454 (void) strlcpy(buf, s, sizeof (buf));
456 membar_producer();
457 fmri = buf;
459 return (get_fmri());
463 * Wrappers for smf_degrade/restore_instance()
465 * smf_restore_instance() is too heavy duty to be calling every time we
466 * have a successful AD name<->SID lookup.
468 void
469 degrade_svc(int poke_discovery, const char *reason)
471 const char *fmri;
473 membar_consumer();
474 if (degraded)
475 return;
477 idmapdlog(LOG_ERR, "Degraded operation (%s).", reason);
479 membar_producer();
480 degraded = B_TRUE;
482 if ((fmri = get_fmri()) != NULL)
483 (void) smf_degrade_instance(fmri, 0);
486 * If the config update thread is in a state where auto-discovery could
487 * be re-tried, then this will make it try it -- a sort of auto-refresh.
489 if (poke_discovery)
490 idmap_cfg_poke_updates();
493 void
494 restore_svc(void)
496 const char *fmri;
498 membar_consumer();
499 if (!degraded)
500 return;
502 if ((fmri = get_fmri()) == NULL)
503 (void) smf_restore_instance(fmri);
505 membar_producer();
506 degraded = B_FALSE;
508 idmapdlog(LOG_NOTICE, "Normal operation restored");
512 /* printflike */
513 void
514 idmapdlog(int pri, const char *format, ...) {
515 static time_t prev_ts;
516 va_list args;
517 char cbuf[CBUFSIZ];
518 time_t ts;
520 ts = time(NULL);
521 if (prev_ts != ts) {
522 prev_ts = ts;
523 /* NB: cbuf has \n */
524 (void) fprintf(stderr, "@ %s",
525 ctime_r(&ts, cbuf));
528 va_start(args, format);
529 (void) vfprintf(stderr, format, args);
530 (void) fprintf(stderr, "\n");
531 va_end(args);
534 * We don't want to fill up the logs with useless messages when
535 * we're degraded, but we still want to log.
537 if (degraded)
538 pri = LOG_DEBUG;
540 va_start(args, format);
541 vsyslog(pri, format, args);
542 va_end(args);
545 static void
546 trace_str(nvlist_t *entry, char *n1, char *n2, char *str)
548 char name[IDMAP_TRACE_NAME_MAX+1]; /* Max used is only about 11 */
550 (void) strlcpy(name, n1, sizeof (name));
551 if (n2 != NULL)
552 (void) strlcat(name, n2, sizeof (name));
554 (void) nvlist_add_string(entry, name, str);
557 static void
558 trace_int(nvlist_t *entry, char *n1, char *n2, int64_t i)
560 char name[IDMAP_TRACE_NAME_MAX+1]; /* Max used is only about 11 */
562 (void) strlcpy(name, n1, sizeof (name));
563 if (n2 != NULL)
564 (void) strlcat(name, n2, sizeof (name));
566 (void) nvlist_add_int64(entry, name, i);
569 static void
570 trace_sid(nvlist_t *entry, char *n1, char *n2, idmap_sid *sid)
572 char *str;
574 (void) asprintf(&str, "%s-%u", sid->prefix, sid->rid);
575 if (str == NULL)
576 return;
578 trace_str(entry, n1, n2, str);
579 free(str);
582 static void
583 trace_id(nvlist_t *entry, char *fromto, idmap_id *id, char *name, char *domain)
585 trace_int(entry, fromto, IDMAP_TRACE_TYPE, (int64_t)id->idtype);
586 if (IS_ID_SID(*id)) {
587 if (name != NULL) {
588 char *str;
590 (void) asprintf(&str, "%s%s%s", name,
591 domain == NULL ? "" : "@",
592 domain == NULL ? "" : domain);
593 if (str != NULL) {
594 trace_str(entry, fromto, IDMAP_TRACE_NAME, str);
595 free(str);
598 if (id->idmap_id_u.sid.prefix != NULL) {
599 trace_sid(entry, fromto, IDMAP_TRACE_SID,
600 &id->idmap_id_u.sid);
602 } else if (IS_ID_POSIX(*id)) {
603 if (name != NULL)
604 trace_str(entry, fromto, IDMAP_TRACE_NAME, name);
605 if (id->idmap_id_u.uid != IDMAP_SENTINEL_PID) {
606 trace_int(entry, fromto, IDMAP_TRACE_UNIXID,
607 (int64_t)id->idmap_id_u.uid);
613 * Record a trace event. TRACE() has already decided whether or not
614 * tracing is required; what we do here is collect the data and send it
615 * to its destination - to the trace log in the response, if
616 * IDMAP_REQ_FLG_TRACE is set, and to the SMF service log, if debug/mapping
617 * is greater than zero.
620 trace(idmap_mapping *req, idmap_id_res *res, char *fmt, ...)
622 va_list va;
623 char *buf;
624 int err;
625 nvlist_t *entry;
627 assert(req != NULL);
628 assert(res != NULL);
630 err = nvlist_alloc(&entry, NV_UNIQUE_NAME, 0);
631 if (err != 0) {
632 (void) fprintf(stderr, "trace nvlist_alloc(entry): %s\n",
633 strerror(err));
634 return (0);
637 trace_id(entry, "from", &req->id1, req->id1name, req->id1domain);
638 trace_id(entry, "to", &res->id, req->id2name, req->id2domain);
640 if (IDMAP_ERROR(res->retcode)) {
641 trace_int(entry, IDMAP_TRACE_ERROR, NULL,
642 (int64_t)res->retcode);
645 va_start(va, fmt);
646 (void) vasprintf(&buf, fmt, va);
647 va_end(va);
648 if (buf != NULL) {
649 trace_str(entry, IDMAP_TRACE_MESSAGE, NULL, buf);
650 free(buf);
653 if (DBG(MAPPING, 1))
654 idmap_trace_print_1(stderr, "", entry);
656 if (req->flag & IDMAP_REQ_FLG_TRACE) {
657 /* Lazily allocate the trace list */
658 if (res->info.trace == NULL) {
659 err = nvlist_alloc(&res->info.trace, 0, 0);
660 if (err != 0) {
661 res->info.trace = NULL; /* just in case */
662 (void) fprintf(stderr,
663 "trace nvlist_alloc(trace): %s\n",
664 strerror(err));
665 nvlist_free(entry);
666 return (0);
669 (void) nvlist_add_nvlist(res->info.trace, "", entry);
670 /* Note that entry is copied, so we must still free our copy */
673 nvlist_free(entry);
675 return (0);