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
usage()100 usage() {
101 (void) fprintf(stderr, "usage: syseventconfd [-d <debug_level>]\n");
102 exit(2);
103 }
104
105
106 static void
set_root_dir(char * dir)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
main(int argc,char ** argv)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
event_handler(sysevent_t * event)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
exec_cmd(struct cmd * cmd)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
sigwait_thr()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
reapchild(int sig)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
flt_handler(int sig)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 *
init_arglist(int hint)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
free_arglist(arg_t * arglist)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
add_arg(arg_t * arglist,char * arg)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 *
next_arg(char ** cpp)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 *
alloc_cmd(nvlist_t * nvlist)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
free_cmd(struct cmd * cmd)621 free_cmd(struct cmd *cmd)
622 {
623 nvlist_free(cmd->cmd_nvlist);
624 free(cmd);
625 }
626
627
628 static void *
sc_malloc(size_t n)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 *
sc_realloc(void * p,size_t n)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
syserrmsg(char * message,...)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
printmsg(int level,char * message,...)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 *
create_door_thr(void * arg)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
mk_thr_pool(door_info_t * dip)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 *
open_channel()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