fix systemd unit install path
[external/acpid.git] / event.c
1 /*
2  *  event.c - ACPI daemon event handler
3  *
4  *  Copyright (C) 2000 Andrew Henroid
5  *  Copyright (C) 2001 Sun Microsystems (thockin@sun.com)
6  *  Copyright (C) 2004 Tim Hockin (thockin@hockin.org)
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #include <sys/poll.h>
27 #include <fcntl.h>
28 #include <unistd.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33 #include <dirent.h>
34 #include <ctype.h>
35 #include <regex.h>
36 #include <signal.h>
37
38 #include "acpid.h"
39 #include "log.h"
40 #include "sock.h"
41 #include "ud_socket.h"
42
43 /*
44  * What is a rule?  It's polymorphic, pretty much.
45  */
46 #define RULE_REGEX_FLAGS (REG_EXTENDED | REG_ICASE | REG_NOSUB | REG_NEWLINE)
47 struct rule {
48         enum {
49                 RULE_NONE = 0,
50                 RULE_CMD,
51                 RULE_CLIENT,
52         } type;
53         char *origin;
54         regex_t *event;
55         union {
56                 char *cmd;
57                 int fd;
58         } action;
59         struct rule *next;
60         struct rule *prev;
61 };
62 struct rule_list {
63         struct rule *head;
64         struct rule *tail;
65 };
66 static struct rule_list cmd_list;
67 static struct rule_list client_list;
68
69 /* rule routines */
70 static void enlist_rule(struct rule_list *list, struct rule *r);
71 static void delist_rule(struct rule_list *list, struct rule *r);
72 static struct rule *new_rule(void);
73 static void free_rule(struct rule *r);
74
75 /* other helper routines */
76 static void lock_rules(void);
77 static void unlock_rules(void);
78 static sigset_t *signals_handled(void);
79 static struct rule *parse_file(const char *file);
80 static struct rule *parse_client(int client);
81 static int do_cmd_rule(struct rule *r, const char *event);
82 static int do_client_rule(struct rule *r, const char *event);
83 static int safe_write(int fd, const char *buf, int len);
84 static char *parse_cmd(const char *cmd, const char *event);
85 static int check_escapes(const char *str);
86
87 /*
88  * read in all the configuration files
89  */
90 int
91 acpid_read_conf(const char *confdir)
92 {
93         DIR *dir;
94         struct dirent *dirent;
95         char *file = NULL;
96         int nrules = 0;
97         regex_t preg;
98         int rc = 0;
99
100         lock_rules();
101
102         dir = opendir(confdir);
103         if (!dir) {
104                 acpid_log(LOG_ERR, "opendir(%s): %s",
105                         confdir, strerror(errno));
106                 unlock_rules();
107                 return -1;
108         }
109
110         /* Compile the regular expression.  This is based on run-parts(8). */
111         rc = regcomp(&preg, "^[a-zA-Z0-9_-]+$", RULE_REGEX_FLAGS);
112         if (rc) {
113                 acpid_log(LOG_ERR, "regcomp(): %d", rc);
114                 unlock_rules();
115                 return -1;
116         }
117
118         /* scan all the files */
119         while ((dirent = readdir(dir))) {
120                 int len;
121                 struct rule *r;
122                 struct stat stat_buf;
123
124                 len = strlen(dirent->d_name);
125
126                 /* skip "." and ".." */
127                 if (strncmp(dirent->d_name, ".", sizeof(dirent->d_name)) == 0)
128                         continue;
129                 if (strncmp(dirent->d_name, "..", sizeof(dirent->d_name)) == 0)
130                         continue;
131
132                 /* skip any files that don't match the run-parts convention */
133                 if (regexec(&preg, dirent->d_name, 0, NULL, 0) != 0) {
134                         acpid_log(LOG_INFO, "skipping conf file %s/%s", 
135                                 confdir, dirent->d_name);
136                         continue;
137                 }
138
139                 /* Compute the length of the full path name adding one for */
140                 /* the slash and one more for the NULL. */
141                 len += strlen(confdir) + 2;
142
143                 file = malloc(len);
144                 if (!file) {
145                         acpid_log(LOG_ERR, "malloc(): %s", strerror(errno));
146                         unlock_rules();
147                         return -1;
148                 }
149                 snprintf(file, len, "%s/%s", confdir, dirent->d_name);
150
151                 /* allow only regular files and symlinks to files */
152                 if (stat(file, &stat_buf) != 0) {
153                         acpid_log(LOG_ERR, "stat(%s): %s", file,
154                                 strerror(errno));
155                         free(file);
156                         continue; /* keep trying the rest of the files */
157                 }
158                 if (!S_ISREG(stat_buf.st_mode)) {
159                         acpid_log(LOG_INFO, "skipping non-file %s", file);
160                         free(file);
161                         continue; /* skip non-regular files */
162                 }
163
164                 r = parse_file(file);
165                 if (r) {
166                         enlist_rule(&cmd_list, r);
167                         nrules++;
168                 }
169                 free(file);
170         }
171         closedir(dir);
172         unlock_rules();
173
174         acpid_log(LOG_INFO, "%d rule%s loaded",
175             nrules, (nrules == 1)?"":"s");
176
177         return 0;
178 }
179
180 /*
181  * cleanup all rules
182  */
183 int
184 acpid_cleanup_rules(int do_detach)
185 {
186         struct rule *p;
187         struct rule *next;
188
189         lock_rules();
190
191         if (acpid_debug >= 3) {
192                 acpid_log(LOG_DEBUG, "cleaning up rules");
193         }
194
195         if (do_detach) {
196                 /* tell our clients to buzz off */
197                 p = client_list.head;
198                 while (p) {
199                         next = p->next;
200                         delist_rule(&client_list, p);
201                         close(p->action.fd);
202                         free_rule(p);
203                         p = next;
204                 }
205         }
206
207         /* clear out our conf rules */
208         p = cmd_list.head;
209         while (p) {
210                 next = p->next;
211                 delist_rule(&cmd_list, p);
212                 free_rule(p);
213                 p = next;
214         }
215
216         unlock_rules();
217
218         return 0;
219 }
220
221 static struct rule *
222 parse_file(const char *file)
223 {
224         FILE *fp;
225         char buf[512];
226         int line = 0;
227         struct rule *r;
228
229         acpid_log(LOG_DEBUG, "parsing conf file %s", file);
230
231         fp = fopen(file, "r");
232         if (!fp) {
233                 acpid_log(LOG_ERR, "fopen(%s): %s", file, strerror(errno));
234                 return NULL;
235         }
236
237         /* make a new rule */
238         r = new_rule();
239         if (!r) {
240                 fclose(fp);
241                 return NULL;
242         }
243         r->type = RULE_CMD;
244         r->origin = strdup(file);
245         if (!r->origin) {
246                 acpid_log(LOG_ERR, "strdup(): %s", strerror(errno));
247                 free_rule(r);
248                 fclose(fp);
249                 return NULL;
250         }
251
252         /* read each line */
253         while (!feof(fp) && !ferror(fp)) {
254                 char *p = buf;
255                 char key[64];
256                 char val[512];
257                 int n;
258
259                 line++;
260                 memset(key, 0, sizeof(key));
261                 memset(val, 0, sizeof(val));
262
263                 if (fgets(buf, sizeof(buf)-1, fp) == NULL) {
264                         continue;
265                 }
266
267                 /* skip leading whitespace */
268                 while (*p && isspace((int)*p)) {
269                         p++;
270                 }
271                 /* blank lines and comments get ignored */
272                 if (!*p || *p == '#') {
273                         continue;
274                 }
275
276                 /* quick parse */
277                 n = sscanf(p, "%63[^=\n]=%255[^\n]", key, val);
278                 if (n != 2) {
279                         acpid_log(LOG_WARNING, "can't parse %s at line %d",
280                             file, line);
281                         continue;
282                 }
283                 if (acpid_debug >= 3) {
284                         acpid_log(LOG_DEBUG, "    key=\"%s\" val=\"%s\"",
285                             key, val);
286                 }
287                 /* handle the parsed line */
288                 if (!strcasecmp(key, "event")) {
289                         int rv;
290                         r->event = malloc(sizeof(regex_t));
291                         if (!r->event) {
292                                 acpid_log(LOG_ERR, "malloc(): %s",
293                                         strerror(errno));
294                                 free_rule(r);
295                                 fclose(fp);
296                                 return NULL;
297                         }
298                         rv = regcomp(r->event, val, RULE_REGEX_FLAGS);
299                         if (rv) {
300                                 char rbuf[128];
301                                 regerror(rv, r->event, rbuf, sizeof(rbuf));
302                                 acpid_log(LOG_ERR, "regcomp(): %s", rbuf);
303                                 free_rule(r);
304                                 fclose(fp);
305                                 return NULL;
306                         }
307                 } else if (!strcasecmp(key, "action")) {
308                         if (check_escapes(val) < 0) {
309                                 acpid_log(LOG_ERR, "can't load file %s",
310                                     file);
311                                 free_rule(r);
312                                 fclose(fp);
313                                 return NULL;
314                         }
315                         r->action.cmd = strdup(val);
316                         if (!r->action.cmd) {
317                                 acpid_log(LOG_ERR, "strdup(): %s",
318                                         strerror(errno));
319                                 free_rule(r);
320                                 fclose(fp);
321                                 return NULL;
322                         }
323                 } else {
324                         acpid_log(LOG_WARNING,
325                             "unknown option '%s' in %s at line %d",
326                             key, file, line);
327                         continue;
328                 }
329         }
330         if (!r->event || !r->action.cmd) {
331                 acpid_log(LOG_INFO, "skipping incomplete file %s", file);
332                 free_rule(r);
333                 fclose(fp);
334                 return NULL;
335         }
336         fclose(fp);
337
338         return r;
339 }
340
341 int
342 acpid_add_client(int clifd, const char *origin)
343 {
344         struct rule *r;
345         int nrules = 0;
346
347         acpid_log(LOG_NOTICE, "client connected from %s", origin);
348
349         r = parse_client(clifd);
350         if (r) {
351                 r->origin = strdup(origin);
352                 enlist_rule(&client_list, r);
353                 nrules++;
354         }
355
356         acpid_log(LOG_INFO, "%d client rule%s loaded",
357             nrules, (nrules == 1)?"":"s");
358
359         return 0;
360 }
361
362 static struct rule *
363 parse_client(int client)
364 {
365         struct rule *r;
366         int rv;
367
368         /* make a new rule */
369         r = new_rule();
370         if (!r) {
371                 return NULL;
372         }
373         r->type = RULE_CLIENT;
374         r->action.fd = client;
375         r->event = malloc(sizeof(regex_t));
376         if (!r->event) {
377                 acpid_log(LOG_ERR, "malloc(): %s", strerror(errno));
378                 free_rule(r);
379                 return NULL;
380         }
381         rv = regcomp(r->event, ".*", RULE_REGEX_FLAGS);
382         if (rv) {
383                 char buf[128];
384                 regerror(rv, r->event, buf, sizeof(buf));
385                 acpid_log(LOG_ERR, "regcomp(): %s", buf);
386                 free_rule(r);
387                 return NULL;
388         }
389
390         return r;
391 }
392
393 /*
394  * a few rule methods
395  */
396
397 static void
398 enlist_rule(struct rule_list *list, struct rule *r)
399 {
400         r->next = r->prev = NULL;
401         if (!list->head) {
402                 list->head = list->tail = r;
403         } else {
404                 list->tail->next = r;
405                 r->prev = list->tail;
406                 list->tail = r;
407         }
408 }
409
410 static void
411 delist_rule(struct rule_list *list, struct rule *r)
412 {
413         if (r->next) {
414                 r->next->prev = r->prev;
415         } else {
416                 list->tail = r->prev;
417         }
418
419         if (r->prev) {
420                 r->prev->next = r->next;
421         } else {
422                 list->head = r->next;;
423         }
424
425         r->next = r->prev = NULL;
426 }
427
428 static struct rule *
429 new_rule(void)
430 {
431         struct rule *r;
432
433         r = malloc(sizeof(*r));
434         if (!r) {
435                 acpid_log(LOG_ERR, "malloc(): %s", strerror(errno));
436                 return NULL;
437         }
438
439         r->type = RULE_NONE;
440         r->origin = NULL;
441         r->event = NULL;
442         r->action.cmd = NULL;
443         r->prev = r->next = NULL;
444
445         return r;
446 }
447
448 /* I hope you delisted the rule before you free() it */
449 static void
450 free_rule(struct rule *r)
451 {
452         if (r->type == RULE_CMD) {
453                 if (r->action.cmd) {
454                         free(r->action.cmd);
455                 }
456         }
457
458         if (r->origin) {
459                 free(r->origin);
460         }
461         if (r->event) {
462                 regfree(r->event);
463                 free(r->event);
464         }
465
466         free(r);
467 }
468
469 static int
470 client_is_dead(int fd)
471 {
472         struct pollfd pfd;
473         int r;
474
475         /* check the fd to see if it is dead */
476         pfd.fd = fd;
477         pfd.events = POLLERR | POLLHUP;
478         r = poll(&pfd, 1, 0);
479
480         if (r < 0) {
481                 acpid_log(LOG_ERR, "poll(): %s", strerror(errno));
482                 return 0;
483         }
484
485         return pfd.revents;
486 }
487
488 void
489 acpid_close_dead_clients(void)
490 {
491         struct rule *p;
492
493         lock_rules();
494
495         /* scan our client list */
496         p = client_list.head;
497         while (p) {
498                 struct rule *next = p->next;
499                 if (client_is_dead(p->action.fd)) {
500                         struct ucred cred;
501                         /* closed */
502                         acpid_log(LOG_NOTICE,
503                             "client %s has disconnected", p->origin);
504                         delist_rule(&client_list, p);
505                         ud_get_peercred(p->action.fd, &cred);
506                         if (cred.uid != 0) {
507                                 non_root_clients--;
508                         }
509                         close(p->action.fd);
510                         free_rule(p);
511                 }
512                 p = next;
513         }
514
515         unlock_rules();
516 }
517
518 /*
519  * the main hook for propogating events
520  */
521 int
522 acpid_handle_event(const char *event)
523 {
524         struct rule *p;
525         int nrules = 0;
526         struct rule_list *ar[] = { &client_list, &cmd_list, NULL };
527         struct rule_list **lp;
528
529         /* make an event be atomic wrt known signals */
530         lock_rules();
531
532         /* scan each rule list for any rules that care about this event */
533         for (lp = ar; *lp; lp++) {
534                 struct rule_list *l = *lp;
535                 p = l->head;
536                 while (p) {
537                         /* the list can change underneath us */
538                         struct rule *pnext = p->next;
539                         if (!regexec(p->event, event, 0, NULL, 0)) {
540                                 /* a match! */
541                                 if (logevents) {
542                                         acpid_log(LOG_INFO,
543                                             "rule from %s matched",
544                                             p->origin);
545                                 }
546                                 nrules++;
547                                 if (p->type == RULE_CMD) {
548                                         do_cmd_rule(p, event);
549                                 } else if (p->type == RULE_CLIENT) {
550                                         do_client_rule(p, event);
551                                 } else {
552                                         acpid_log(LOG_WARNING,
553                                             "unknown rule type: %d",
554                                             p->type);
555                                 }
556                         } else {
557                                 if (acpid_debug >= 3 && logevents) {
558                                         acpid_log(LOG_INFO,
559                                             "rule from %s did not match",
560                                             p->origin);
561                                 }
562                         }
563                         p = pnext;
564                 }
565         }
566
567         unlock_rules();
568
569         if (logevents) {
570                 acpid_log(LOG_INFO, "%d total rule%s matched",
571                         nrules, (nrules == 1)?"":"s");
572         }
573
574         return 0;
575 }
576
577 /* helper functions to block signals while iterating */
578 static sigset_t *
579 signals_handled(void)
580 {
581         static sigset_t set;
582
583         sigemptyset(&set);
584         sigaddset(&set, SIGHUP);
585         sigaddset(&set, SIGTERM);
586         sigaddset(&set, SIGQUIT);
587         sigaddset(&set, SIGINT);
588
589         return &set;
590 }
591
592 static void
593 lock_rules(void)
594 {
595         if (acpid_debug >= 4) {
596                 acpid_log(LOG_DEBUG, "blocking signals for rule lock");
597         }
598         sigprocmask(SIG_BLOCK, signals_handled(), NULL);
599 }
600
601 static void
602 unlock_rules(void)
603 {
604         if (acpid_debug >= 4) {
605                 acpid_log(LOG_DEBUG, "unblocking signals for rule lock");
606         }
607         sigprocmask(SIG_UNBLOCK, signals_handled(), NULL);
608 }
609
610 /*
611  * the meat of the rules
612  */
613
614 static int
615 do_cmd_rule(struct rule *rule, const char *event)
616 {
617         pid_t pid;
618         int status;
619         const char *action;
620
621         pid = fork();
622         switch (pid) {
623         case -1:
624                 acpid_log(LOG_ERR, "fork(): %s", strerror(errno));
625                 return -1;
626         case 0: /* child */
627                 /* parse the commandline, doing any substitutions needed */
628                 action = parse_cmd(rule->action.cmd, event);
629                 if (logevents) {
630                         acpid_log(LOG_INFO,
631                             "executing action \"%s\"", action);
632                 }
633
634                 /* reset signals */
635                 signal(SIGHUP, SIG_DFL);
636                 signal(SIGTERM, SIG_DFL);
637                 signal(SIGINT, SIG_DFL);
638                 signal(SIGQUIT, SIG_DFL);
639                 signal(SIGPIPE, SIG_DFL);
640                 sigprocmask(SIG_UNBLOCK, signals_handled(), NULL);
641
642                 if (acpid_debug && logevents) {
643                         fprintf(stdout, "BEGIN HANDLER MESSAGES\n");
644                 }
645                 umask(0077);
646                 execl("/bin/sh", "/bin/sh", "-c", action, NULL);
647                 /* should not get here */
648                 acpid_log(LOG_ERR, "execl(): %s", strerror(errno));
649                 _exit(EXIT_FAILURE);
650         }
651
652         /* parent */
653         waitpid(pid, &status, 0);
654         if (acpid_debug && logevents) {
655                 fprintf(stdout, "END HANDLER MESSAGES\n");
656         }
657
658         if (logevents) {
659                 if (WIFEXITED(status)) {
660                         acpid_log(LOG_INFO, "action exited with status %d",
661                             WEXITSTATUS(status));
662                 } else if (WIFSIGNALED(status)) {
663                         acpid_log(LOG_INFO, "action exited on signal %d",
664                             WTERMSIG(status));
665                 } else {
666                         acpid_log(LOG_INFO, "action exited with status %d",
667                             status);
668                 }
669         }
670
671         return 0;
672 }
673
674 static int
675 do_client_rule(struct rule *rule, const char *event)
676 {
677         int r;
678         int client = rule->action.fd;
679
680         if (logevents) {
681                 acpid_log(LOG_INFO, "notifying client %s", rule->origin);
682         }
683
684         r = safe_write(client, event, strlen(event));
685         if (r < 0 && errno == EPIPE) {
686                 struct ucred cred;
687                 /* closed */
688                 acpid_log(LOG_NOTICE,
689                     "client %s has disconnected", rule->origin);
690                 delist_rule(&client_list, rule);
691                 ud_get_peercred(rule->action.fd, &cred);
692                 if (cred.uid != 0) {
693                         non_root_clients--;
694                 }
695                 close(rule->action.fd);
696                 free_rule(rule);
697                 return -1;
698         }
699         safe_write(client, "\n", 1);
700
701         return 0;
702 }
703
704 #define NTRIES 100
705 static int
706 safe_write(int fd, const char *buf, int len)
707 {
708         int r;
709         int ttl = 0;
710         int ntries = NTRIES;
711
712         do {
713                 r = write(fd, buf+ttl, len-ttl);
714                 if (r < 0) {
715                         if (errno != EAGAIN && errno != EINTR) {
716                                 /* a legit error */
717                                 return r;
718                         }
719                         ntries--;
720                 } else if (r > 0) {
721                         /* as long as we make forward progress, reset ntries */
722                         ntries = NTRIES;
723                         ttl += r;
724                 }
725         } while (ttl < len && ntries);
726
727         if (!ntries) {
728                 if (acpid_debug >= 2) {
729                         acpid_log(LOG_ERR, "safe_write() timed out");
730                 }
731                 return r;
732         }
733
734         return ttl;
735 }
736
737 static char *
738 parse_cmd(const char *cmd, const char *event)
739 {
740         static char buf[4096];
741         size_t i;
742         const char *p;
743
744         p = cmd;
745         i = 0;
746
747         memset(buf, 0, sizeof(buf));
748         while (i < (sizeof(buf)-1)) {
749                 if (*p == '%') {
750                         p++;
751                         if (*p == 'e') {
752                                 /* handle an event expansion */
753                                 size_t size = sizeof(buf) - i;
754                                 size = snprintf(buf+i, size, "%s", event);
755                                 i += size;
756                                 p++;
757                                 continue;
758                         }
759                 }
760                 if (!*p) {
761                         break;
762                 }
763                 buf[i++] = *p++;
764         }
765         if (acpid_debug >= 2) {
766                 acpid_log(LOG_DEBUG, "expanded \"%s\" -> \"%s\"", cmd, buf);
767         }
768
769         return buf;
770 }
771
772 static int
773 check_escapes(const char *str)
774 {
775         const char *p;
776         int r = 0;
777
778         p = str;
779         while (*p) {
780                 /* found an escape */
781                 if (*p == '%') {
782                         p++;
783                         if (!*p) {
784                                 acpid_log(LOG_WARNING,
785                                     "invalid escape at EOL");
786                                 return -1;
787                         } else if (*p != '%' && *p != 'e') {
788                                 acpid_log(LOG_WARNING,
789                                     "invalid escape \"%%%c\"", *p);
790                                 r = -1;
791                         }
792                 }
793                 p++;
794         }
795         return r;
796 }