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
76 static int debug_level = 0;
77 static char *root_dir = ""; /* Relative root for lock and door */
78 static char *prog;
79
80 static struct cmd *cmd_list;
81 static struct cmd *cmd_tail;
82
83 static mutex_t cmd_list_lock;
84 static cond_t cmd_list_cv;
85
86 extern char *optarg;
87
88 /*
89 * Support for door server thread handling
90 */
91 #define MAX_SERVER_THREADS 1
92
93 static mutex_t create_cnt_lock;
94 static int cnt_servers = 0;
95
96
97 static void
usage()98 usage() {
99 (void) fprintf(stderr, "usage: syseventconfd [-d <debug_level>]\n");
100 exit(2);
101 }
102
103
104 static void
set_root_dir(char * dir)105 set_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
116 int
main(int argc,char ** argv)117 main(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*/
240 static void
event_handler(sysevent_t * event)241 event_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 */
275 static void
exec_cmd(struct cmd * cmd)276 exec_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
350 again:
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 */
401 static void
sigwait_thr()402 sigwait_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*/
439 static void
reapchild(int sig)440 reapchild(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*/
488 static void
flt_handler(int sig)489 flt_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
510 static arg_t *
init_arglist(int hint)511 init_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
525 static void
free_arglist(arg_t * arglist)526 free_arglist(arg_t *arglist)
527 {
528 if (arglist->arg_args) {
529 free(arglist->arg_args);
530 }
531 free(arglist);
532 }
533
534
535 static int
add_arg(arg_t * arglist,char * arg)536 add_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 */
565 static char *
next_arg(char ** cpp)566 next_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
602 static struct cmd *
alloc_cmd(nvlist_t * nvlist)603 alloc_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
618 static void
free_cmd(struct cmd * cmd)619 free_cmd(struct cmd *cmd)
620 {
621 nvlist_free(cmd->cmd_nvlist);
622 free(cmd);
623 }
624
625
626 static void *
sc_malloc(size_t n)627 sc_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
638 static void *
sc_realloc(void * p,size_t n)639 sc_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*/
655 static void
syserrmsg(char * message,...)656 syserrmsg(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*/
670 static void
printmsg(int level,char * message,...)671 printmsg(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 */
686 static void *
create_door_thr(void * arg)687 create_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 */
701 static void
mk_thr_pool(door_info_t * dip)702 mk_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
716 static sysevent_handle_t *
open_channel()717 open_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