xref: /illumos-gate/usr/src/cmd/syseventd/daemons/syseventconfd/syseventconfd.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * syseventconfd - The sysevent conf daemon
31  *
32  * This daemon is a companion to the sysevent_conf_mod module.
33  *
34  * The sysevent_conf_mod module receives events from syseventd,
35  * and compares those events against event specs in the
36  * sysevent.conf files.  For each matching event spec, the
37  * specified command is invoked.
38  *
39  * This daemon manages the fork/exec's on behalf of sysevent_conf_mod.
40  * The events and associated nvlist are delivered via a door upcall
41  * from sysevent_conf_mod.  Arriving events are queued, and the
42  * main thread of this daemon dequeues events one by one, and
43  * builds the necessary arguments to fork/exec the command.
44  *
45  * Since sysevent_conf_mod is running in the context of syseventd,
46  * invoking the fork/exec from that module blocks the door upcalls
47  * from the kernel delivering events to syseventd.  We avoid a
48  * major performance bottleneck in this fashion.
49  */
50 
51 #include <stdio.h>
52 #include <stdarg.h>
53 #include <stddef.h>
54 #include <stdlib.h>
55 #include <errno.h>
56 #include <fcntl.h>
57 #include <signal.h>
58 #include <strings.h>
59 #include <unistd.h>
60 #include <synch.h>
61 #include <syslog.h>
62 #include <pthread.h>
63 #include <door.h>
64 #include <libsysevent.h>
65 #include <limits.h>
66 #include <locale.h>
67 #include <sys/modctl.h>
68 #include <sys/stat.h>
69 #include <sys/systeminfo.h>
70 #include <sys/wait.h>
71 
72 #include "syseventconfd.h"
73 #include "syseventconfd_door.h"
74 #include "message_confd.h"
75 
76 
77 
78 static int	debug_level	= 0;
79 static char	*root_dir	= "";	/* Relative root for lock and door */
80 static char	*prog;
81 
82 static struct cmd	*cmd_list;
83 static struct cmd	*cmd_tail;
84 
85 static mutex_t		cmd_list_lock;
86 static cond_t		cmd_list_cv;
87 
88 extern char *optarg;
89 
90 /*
91  * Support for door server thread handling
92  */
93 #define	MAX_SERVER_THREADS	1
94 
95 static mutex_t create_cnt_lock;
96 static int cnt_servers = 0;
97 
98 
99 static void
100 usage() {
101 	(void) fprintf(stderr, "usage: syseventconfd [-d <debug_level>]\n");
102 	exit(2);
103 }
104 
105 
106 static void
107 set_root_dir(char *dir)
108 {
109 	root_dir = malloc(strlen(dir) + 1);
110 	if (root_dir == NULL) {
111 		syserrmsg(INIT_ROOT_DIR_ERR, strerror(errno));
112 		exit(2);
113 	}
114 	(void) strcpy(root_dir, dir);
115 }
116 
117 
118 int
119 main(int argc, char **argv)
120 {
121 	int c;
122 	int fd;
123 	sigset_t set;
124 	struct cmd *cmd;
125 
126 	(void) setlocale(LC_ALL, "");
127 	(void) textdomain(TEXT_DOMAIN);
128 
129 	if (getuid() != 0) {
130 		(void) fprintf(stderr, "Must be root to run syseventconfd\n");
131 		exit(1);
132 	}
133 
134 	if ((prog = strrchr(argv[0], '/')) == NULL) {
135 		prog = argv[0];
136 	} else {
137 		prog++;
138 	}
139 
140 	if ((c = getopt(argc, argv, "d:r:")) != EOF) {
141 		switch (c) {
142 		case 'd':
143 			debug_level = atoi(optarg);
144 			break;
145 		case 'r':
146 			/*
147 			 * Private flag for suninstall to run
148 			 * daemon during install.
149 			 */
150 			set_root_dir(optarg);
151 			break;
152 		case '?':
153 		default:
154 			usage();
155 		}
156 	}
157 
158 
159 	if (fork()) {
160 		exit(0);
161 	}
162 
163 	(void) chdir("/");
164 
165 	(void) setsid();
166 	if (debug_level <= 1) {
167 		closefrom(0);
168 		fd = open("/dev/null", 0);
169 		(void) dup2(fd, 1);
170 		(void) dup2(fd, 2);
171 	}
172 
173 	openlog("syseventconfd", LOG_PID, LOG_DAEMON);
174 
175 	printmsg(1,
176 	    "syseventconfd started, debug level = %d\n", debug_level);
177 
178 	/*
179 	 * Block all signals to all threads include the main thread.
180 	 * The sigwait_thr thread will catch and process all signals.
181 	 */
182 	(void) sigfillset(&set);
183 	(void) thr_sigsetmask(SIG_BLOCK, &set, NULL);
184 
185 	/* Create signal catching thread */
186 	if (thr_create(NULL, NULL, (void *(*)(void *))sigwait_thr,
187 		(void *)NULL, 0, NULL) < 0) {
188 		syserrmsg(INIT_THR_CREATE_ERR, strerror(errno));
189 		exit(2);
190 	}
191 
192 	/*
193 	 * Init mutex and list of cmds to be fork/exec'ed
194 	 * This is multi-threaded so the fork/exec can be
195 	 * done without blocking the door upcall.
196 	 */
197 	cmd_list = NULL;
198 	cmd_tail = NULL;
199 
200 	(void) mutex_init(&create_cnt_lock, USYNC_THREAD, NULL);
201 	(void) mutex_init(&cmd_list_lock, USYNC_THREAD, NULL);
202 	(void) cond_init(&cmd_list_cv, USYNC_THREAD, NULL);
203 
204 	/*
205 	 * Open communication channel from sysevent_conf_mod
206 	 */
207 	if (open_channel() == NULL) {
208 		exit(1);
209 	}
210 
211 	/*
212 	 * main thread to wait for events to arrive and be placed
213 	 * on the queue.  As events are queued, dequeue them
214 	 * here and invoke the associated fork/exec.
215 	 */
216 	(void) mutex_lock(&cmd_list_lock);
217 	for (;;) {
218 		while (cmd_list == NULL)
219 			(void) cond_wait(&cmd_list_cv, &cmd_list_lock);
220 
221 		cmd = cmd_list;
222 		cmd_list = cmd->cmd_next;
223 		if (cmd_list == NULL)
224 			cmd_tail = NULL;
225 
226 		(void) mutex_unlock(&cmd_list_lock);
227 		exec_cmd(cmd);
228 		free_cmd(cmd);
229 		(void) mutex_lock(&cmd_list_lock);
230 	}
231 	/* NOTREACHED */
232 	return (0);
233 }
234 
235 /*
236  * Events sent via the door call from sysevent_conf_mod arrive
237  * here.  Queue each event for the main thread to invoke, and
238  * return.  We want to avoid doing the fork/exec while in the
239  * context of the door call.
240  */
241 /*ARGSUSED*/
242 static void
243 event_handler(sysevent_t *event)
244 {
245 	nvlist_t	*nvlist;
246 	struct cmd	*cmd;
247 
248 	nvlist = NULL;
249 	if (sysevent_get_attr_list(event, &nvlist) != 0) {
250 		syslog(LOG_ERR, NO_NVLIST_ERR);
251 		return;
252 	}
253 
254 	if ((cmd = alloc_cmd(nvlist)) != NULL) {
255 		(void) mutex_lock(&cmd_list_lock);
256 		if (cmd_list == NULL) {
257 			cmd_list = cmd;
258 			cmd_tail = cmd;
259 		} else {
260 			cmd_tail->cmd_next = cmd;
261 			cmd_tail = cmd;
262 		}
263 		cmd->cmd_next = NULL;
264 		(void) cond_signal(&cmd_list_cv);
265 		(void) mutex_unlock(&cmd_list_lock);
266 	}
267 
268 	nvlist_free(nvlist);
269 }
270 
271 
272 /*
273  * Decode the command, build the exec args and fork/exec the command
274  * All command attributes are packed into the nvlist bundled with
275  * the delivered event.
276  */
277 static void
278 exec_cmd(struct cmd *cmd)
279 {
280 	char		*path;
281 	char		*cmdline;
282 	uid_t		uid;
283 	gid_t		gid;
284 	char		*file;
285 	int		line;
286 	char		*user;
287 	arg_t		*args;
288 	pid_t		pid;
289 	char		*lp;
290 	char 		*p;
291 	int		i;
292 	sigset_t	set, prior_set;
293 
294 	if (nvlist_lookup_string(cmd->cmd_nvlist, "user", &user) != 0) {
295 		syslog(LOG_ERR, NVLIST_FORMAT_ERR, "user");
296 		return;
297 	}
298 	if (nvlist_lookup_string(cmd->cmd_nvlist, "file", &file) != 0) {
299 		syslog(LOG_ERR, NVLIST_FORMAT_ERR, "file");
300 		return;
301 	}
302 
303 	if (nvlist_lookup_string(cmd->cmd_nvlist, "path", &path) != 0) {
304 		syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "path");
305 		return;
306 	}
307 	if (nvlist_lookup_string(cmd->cmd_nvlist, "cmd", &cmdline) != 0) {
308 		syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "cmd");
309 		return;
310 	}
311 	if (nvlist_lookup_int32(cmd->cmd_nvlist, "line", &line) != 0) {
312 		syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "line");
313 		return;
314 	}
315 	if (nvlist_lookup_int32(cmd->cmd_nvlist, "uid", (int *)&uid) == 0) {
316 		if (nvlist_lookup_int32(cmd->cmd_nvlist,
317 		    "gid", (int *)&gid) != 0) {
318 			syslog(LOG_ERR, NVLIST_FILE_LINE_FORMAT_ERR, "gid");
319 			return;
320 		}
321 	} else {
322 		uid = 0;
323 		gid = 0;
324 	}
325 
326 	args = init_arglist(32);
327 
328 	lp = cmdline;
329 	while ((p = next_arg(&lp)) != NULL) {
330 		if (add_arg(args, p)) {
331 			free_arglist(args);
332 			return;
333 		}
334 	}
335 
336 	if (debug_level >= DBG_EXEC) {
337 		printmsg(DBG_EXEC, "path=%s\n", path);
338 		printmsg(DBG_EXEC, "cmd=%s\n", cmdline);
339 	}
340 
341 	if (debug_level >= DBG_EXEC_ARGS) {
342 		for (i = 0; i < args->arg_nargs; i++) {
343 			printmsg(DBG_EXEC_ARGS,
344 				"arg[%d]: '%s'\n", i, args->arg_args[i]);
345 		}
346 	}
347 
348 	(void) sigprocmask(SIG_SETMASK, NULL, &set);
349 	(void) sigaddset(&set, SIGCHLD);
350 	(void) sigprocmask(SIG_SETMASK, &set, &prior_set);
351 
352 again:
353 	if ((pid = fork1()) == (pid_t)-1) {
354 		if (errno == EINTR)
355 			goto again;
356 		syslog(LOG_ERR, CANNOT_FORK_ERR, strerror(errno));
357 		free_arglist(args);
358 		return;
359 	}
360 	if (pid != (pid_t)0) {
361 		(void) sigprocmask(SIG_SETMASK, &prior_set, NULL);
362 		free_arglist(args);
363 		return;
364 	}
365 
366 	/*
367 	 * The child
368 	 */
369 	(void) close(0);
370 	(void) close(1);
371 	(void) close(2);
372 	(void) open("/dev/null", O_RDONLY);
373 	(void) dup2(0, 1);
374 	(void) dup2(0, 2);
375 
376 	if (uid != (uid_t)0) {
377 		i = setgid(gid);
378 		if (i == 0)
379 			i = setuid(uid);
380 		if (i != 0) {
381 			syslog(LOG_ERR, SETUID_ERR,
382 				file, line, user, strerror(errno));
383 			_exit(0);
384 		}
385 	}
386 
387 	/*
388 	 * Unblock all signals in the child
389 	 */
390 	(void) sigprocmask(SIG_UNBLOCK, &prior_set, NULL);
391 
392 	if (execv(path, args->arg_args) == -1) {
393 		syslog(LOG_ERR, CANNOT_EXEC_ERR,
394 			path, strerror(errno));
395 		_exit(0);
396 	}
397 }
398 
399 
400 /*
401  * Thread to handle in-coming signals
402  */
403 static void
404 sigwait_thr()
405 {
406 	int	sig;
407 	sigset_t signal_set;
408 
409 	/*
410 	 * SIGCHLD is ignored by default, and we need to handle this
411 	 * signal to reap the status of all children spawned by
412 	 * this daemon.
413 	 */
414 	(void) sigset(SIGCHLD, reapchild);
415 
416 	for (;;) {
417 		(void) sigfillset(&signal_set);
418 		if (sigwait(&signal_set, &sig) == 0) {
419 			/*
420 			 * Block all signals until the signal handler completes
421 			 */
422 			(void) sigfillset(&signal_set);
423 			(void) thr_sigsetmask(SIG_BLOCK, &signal_set, NULL);
424 
425 			if (sig == SIGCHLD) {
426 				reapchild(sig);
427 			} else {
428 				flt_handler(sig);
429 			}
430 		}
431 	}
432 	/* NOTREACHED */
433 }
434 
435 
436 
437 /*
438  * reapchild - reap the status of each child as it exits
439  */
440 /*ARGSUSED*/
441 static void
442 reapchild(int sig)
443 {
444 	siginfo_t info;
445 	char *signam;
446 	int err;
447 
448 	for (;;) {
449 		(void) memset(&info, 0, sizeof (info));
450 		err = waitid(P_ALL, 0, &info, WNOHANG|WEXITED);
451 		if (err == -1) {
452 			if (errno != EINTR && errno != EAGAIN)
453 				return;
454 		} else if (info.si_pid == 0) {
455 			return;
456 		}
457 
458 		if (debug_level >= DBG_CHILD) {
459 			printmsg(DBG_CHILD, CHILD_EXIT_STATUS_ERR,
460 				info.si_pid, info.si_status);
461 		}
462 
463 		if (info.si_status) {
464 			if (info.si_code == CLD_EXITED) {
465 				syserrmsg(CHILD_EXIT_STATUS_ERR,
466 					info.si_pid, info.si_status);
467 			} else {
468 				signam = strsignal(info.si_status);
469 				if (signam == NULL)
470 					signam = "";
471 				if (info.si_code == CLD_DUMPED) {
472 					syserrmsg(
473 					    CHILD_EXIT_CORE_ERR,
474 					    info.si_pid, signam);
475 				} else {
476 					syserrmsg(
477 					    CHILD_EXIT_SIGNAL_ERR,
478 					    info.si_pid, signam);
479 				}
480 			}
481 		}
482 	}
483 }
484 
485 
486 /*
487  * Fault handler for other signals caught
488  */
489 /*ARGSUSED*/
490 static void
491 flt_handler(int sig)
492 {
493 	struct sigaction act;
494 
495 	(void) memset(&act, 0, sizeof (act));
496 	act.sa_handler = SIG_DFL;
497 	act.sa_flags = SA_RESTART;
498 	(void) sigfillset(&act.sa_mask);
499 	(void) sigaction(sig, &act, NULL);
500 
501 	switch (sig) {
502 		case SIGINT:
503 		case SIGSTOP:
504 		case SIGTERM:
505 		case SIGHUP:
506 			exit(1);
507 			/*NOTREACHED*/
508 	}
509 }
510 
511 
512 static arg_t *
513 init_arglist(int hint)
514 {
515 	arg_t	*arglist;
516 
517 	if ((arglist = sc_malloc(sizeof (arg_t))) == NULL)
518 		return (NULL);
519 	arglist->arg_args = NULL;
520 	arglist->arg_nargs = 0;
521 	arglist->arg_alloc = 0;
522 	arglist->arg_hint = hint;
523 	return (arglist);
524 }
525 
526 
527 static void
528 free_arglist(arg_t *arglist)
529 {
530 	if (arglist->arg_args) {
531 		free(arglist->arg_args);
532 	}
533 	free(arglist);
534 }
535 
536 
537 static int
538 add_arg(arg_t *arglist, char *arg)
539 {
540 	char	**new_args;
541 	int	len;
542 
543 	len = arglist->arg_nargs + 2;
544 	if (arglist->arg_alloc < len) {
545 		arglist->arg_alloc = len + arglist->arg_hint;
546 		new_args = (arglist->arg_nargs == 0) ?
547 			sc_malloc(arglist->arg_alloc * sizeof (char **)) :
548 			sc_realloc(arglist->arg_args,
549 				arglist->arg_alloc * sizeof (char **));
550 		if (new_args == NULL)
551 			return (1);
552 		arglist->arg_args = new_args;
553 	}
554 
555 	arglist->arg_args[arglist->arg_nargs++] = arg;
556 	arglist->arg_args[arglist->arg_nargs] = NULL;
557 
558 	return (0);
559 }
560 
561 /*
562  * next_arg() is used to break up a command line
563  * into the arguments for execv(2).  Break up
564  * arguments separated by spaces, but respecting
565  * single/double quotes.
566  */
567 static char *
568 next_arg(char **cpp)
569 {
570 	char	*cp = *cpp;
571 	char	*start;
572 	char	quote;
573 
574 	while (*cp == ' ' || *cp == '\t')
575 		cp++;
576 	if (*cp == 0) {
577 		*cpp = 0;
578 		return (NULL);
579 	}
580 	start = cp;
581 	while (*cp && *cp != ' ' && *cp != '\t') {
582 		if (*cp == '"' || *cp == '\'') {
583 			quote = *cp++;
584 			while (*cp && *cp != quote) {
585 				cp++;
586 			}
587 			if (*cp == 0) {
588 				*cpp = 0;
589 				return (NULL);
590 			} else {
591 				cp++;
592 			}
593 		} else {
594 			cp++;
595 		}
596 	}
597 	if (*cp != 0)
598 		*cp++ = 0;
599 	*cpp = cp;
600 	return (start);
601 }
602 
603 
604 static struct cmd *
605 alloc_cmd(nvlist_t *nvlist)
606 {
607 	struct cmd *cmd;
608 
609 	cmd = sc_malloc(sizeof (struct cmd));
610 	if (cmd) {
611 		if (nvlist_dup(nvlist, &cmd->cmd_nvlist, 0) != 0) {
612 			syslog(LOG_ERR, OUT_OF_MEMORY_ERR);
613 			free(cmd);
614 			return (NULL);
615 		}
616 	}
617 	return (cmd);
618 }
619 
620 static void
621 free_cmd(struct cmd *cmd)
622 {
623 	nvlist_free(cmd->cmd_nvlist);
624 	free(cmd);
625 }
626 
627 
628 static void *
629 sc_malloc(size_t n)
630 {
631 	void *p;
632 
633 	p = malloc(n);
634 	if (p == NULL) {
635 		syslog(LOG_ERR, OUT_OF_MEMORY_ERR);
636 	}
637 	return (p);
638 }
639 
640 static void *
641 sc_realloc(void *p, size_t n)
642 {
643 	p = realloc(p, n);
644 	if (p == NULL) {
645 		syslog(LOG_ERR, OUT_OF_MEMORY_ERR);
646 	}
647 	return (p);
648 }
649 
650 
651 
652 /*
653  * syserrsg - print error messages to the terminal if not
654  *			yet daemonized or to syslog.
655  */
656 /*PRINTFLIKE1*/
657 static void
658 syserrmsg(char *message, ...)
659 {
660 	va_list ap;
661 
662 	va_start(ap, message);
663 	(void) vsyslog(LOG_ERR, message, ap);
664 	va_end(ap);
665 }
666 
667 /*
668  * printmsg -  print messages to the terminal or to syslog
669  *			the following levels are implemented:
670  */
671 /*PRINTFLIKE2*/
672 static void
673 printmsg(int level, char *message, ...)
674 {
675 	va_list ap;
676 
677 	if (level > debug_level) {
678 		return;
679 	}
680 
681 	va_start(ap, message);
682 	(void) syslog(LOG_DEBUG, "%s[%ld]: ", prog, getpid());
683 	(void) vsyslog(LOG_DEBUG, message, ap);
684 	va_end(ap);
685 }
686 
687 /* ARGSUSED */
688 static void *
689 create_door_thr(void *arg)
690 {
691 	(void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
692 	(void) door_return(NULL, 0, NULL, 0);
693 	return (NULL);
694 }
695 
696 /*
697  * Control creation of door server threads
698  *
699  * If first creation of server thread fails there is nothing
700  * we can do about. Doors would not work.
701  */
702 /* ARGSUSED */
703 static void
704 mk_thr_pool(door_info_t *dip)
705 {
706 	(void) mutex_lock(&create_cnt_lock);
707 	if (++cnt_servers > MAX_SERVER_THREADS) {
708 		cnt_servers--;
709 		(void) mutex_unlock(&create_cnt_lock);
710 		return;
711 	}
712 	(void) mutex_unlock(&create_cnt_lock);
713 
714 	(void) thr_create(NULL, 0, create_door_thr, NULL,
715 	    THR_BOUND|THR_DETACHED, NULL);
716 }
717 
718 static sysevent_handle_t *
719 open_channel()
720 {
721 	char	door_path[MAXPATHLEN];
722 	const char *subclass_list;
723 	sysevent_handle_t *handle;
724 
725 	if (snprintf(door_path, sizeof (door_path), "%s/%s",
726 	    root_dir, SYSEVENTCONFD_SERVICE_DOOR) >= sizeof (door_path)) {
727 		syserrmsg(CHANNEL_OPEN_ERR);
728 		return (NULL);
729 	}
730 
731 	/*
732 	 * Setup of door server create function to limit the
733 	 * amount of door servers
734 	 */
735 	(void) door_server_create(mk_thr_pool);
736 
737 	handle = sysevent_open_channel_alt(door_path);
738 	if (handle == NULL) {
739 		syserrmsg(CHANNEL_OPEN_ERR);
740 		return (NULL);
741 	}
742 	if (sysevent_bind_subscriber(handle, event_handler) != 0) {
743 		syserrmsg(CHANNEL_BIND_ERR);
744 		sysevent_close_channel(handle);
745 		return (NULL);
746 	}
747 	subclass_list = EC_SUB_ALL;
748 	if (sysevent_register_event(handle, EC_ALL, &subclass_list, 1)
749 	    != 0) {
750 		syserrmsg(CHANNEL_BIND_ERR);
751 		(void) sysevent_unbind_subscriber(handle);
752 		(void) sysevent_close_channel(handle);
753 		return (NULL);
754 	}
755 	return (handle);
756 }
757