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