00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include <sys/types.h>
00023 #include <sys/stat.h>
00024 #include <sys/wait.h>
00025 #include <fcntl.h>
00026 #include <unistd.h>
00027 #include <stdio.h>
00028 #include <stdlib.h>
00029 #include <string.h>
00030 #include <errno.h>
00031 #include <dirent.h>
00032 #include <ctype.h>
00033 #include <regex.h>
00034 #include <signal.h>
00035
00036 #include "acpid.h"
00037
00038
00039
00040
00041 #define RULE_REGEX_FLAGS (REG_EXTENDED | REG_ICASE | REG_NOSUB | REG_NEWLINE)
00042 struct rule {
00043 enum {
00044 RULE_NONE = 0,
00045 RULE_CMD,
00046 RULE_CLIENT,
00047 } type;
00048 char *origin;
00049 regex_t *event;
00050 union {
00051 char *cmd;
00052 int fd;
00053 } action;
00054 struct rule *next;
00055 struct rule *prev;
00056 };
00057 struct rule_list {
00058 struct rule *head;
00059 struct rule *tail;
00060 };
00061 static struct rule_list cmd_list;
00062 static struct rule_list client_list;
00063
00064
00065 static void enlist_rule(struct rule_list *list, struct rule *r);
00066 static void delist_rule(struct rule_list *list, struct rule *r);
00067 static struct rule *new_rule(void);
00068 static void free_rule(struct rule *r);
00069
00070
00071 static void lock_rules(void);
00072 static void unlock_rules(void);
00073 static sigset_t *signals_handled(void);
00074 static struct rule *parse_file(const char *file);
00075 static struct rule *parse_client(int client);
00076 static int do_cmd_rule(struct rule *r, const char *event);
00077 static int do_client_rule(struct rule *r, const char *event);
00078 static int safe_write(int fd, const char *buf, int len);
00079 static char *parse_cmd(const char *cmd, const char *event);
00080 static int check_escapes(const char *str);
00081
00082
00083 static int
00084 path_is_dir(const char *path)
00085 {
00086 struct stat s;
00087
00088 if (stat(path, &s)) {
00089 acpid_log("ERR: stat(\"%s\"): %s\n", path, strerror(errno));
00090 return 0;
00091 }
00092 return S_ISDIR(s.st_mode);
00093 }
00094
00095
00096
00097
00098 int
00099 acpid_read_conf(const char *confdir)
00100 {
00101 DIR *dir;
00102 struct dirent *dirent;
00103 char *file = NULL;
00104 int nrules = 0;
00105
00106 lock_rules();
00107
00108 dir = opendir(confdir);
00109 if (!dir) {
00110 acpid_log("ERR: opendir(%s): %s\n",
00111 confdir, strerror(errno));
00112 unlock_rules();
00113 return -1;
00114 }
00115
00116
00117 while ((dirent = readdir(dir))) {
00118 int len;
00119 struct rule *r;
00120
00121 if (dirent->d_name[0] == '.')
00122 continue;
00123
00124 len = strlen(dirent->d_name) + strlen(confdir) + 2;
00125 file = (char *)malloc(len);
00126 if (!file) {
00127 acpid_log("ERR: malloc(): %s\n", strerror(errno));
00128 unlock_rules();
00129 return -1;
00130 }
00131 snprintf(file, len, "%s/%s", confdir, dirent->d_name);
00132
00133 if (path_is_dir(file)) {
00134 free(file);
00135 continue;
00136 }
00137
00138 r = parse_file(file);
00139 if (r) {
00140 enlist_rule(&cmd_list, r);
00141 nrules++;
00142 }
00143 free(file);
00144 }
00145 closedir(dir);
00146 unlock_rules();
00147
00148 acpid_log("%d rule%s loaded\n", nrules, (nrules == 1)?"":"s");
00149
00150 return 0;
00151 }
00152
00153
00154
00155
00156 int
00157 acpid_cleanup_rules(void)
00158 {
00159 struct rule *p;
00160 struct rule *next;
00161
00162 lock_rules();
00163
00164 if (acpid_debug >= 3) {
00165 acpid_log("DBG: cleaning up rules\n");
00166 }
00167
00168
00169 p = client_list.head;
00170 while (p) {
00171 next = p->next;
00172 delist_rule(&client_list, p);
00173 close(p->action.fd);
00174 free_rule(p);
00175 p = next;
00176 }
00177
00178
00179 p = cmd_list.head;
00180 while (p) {
00181 next = p->next;
00182 delist_rule(&cmd_list, p);
00183 free_rule(p);
00184 p = next;
00185 }
00186
00187 unlock_rules();
00188
00189 return 0;
00190 }
00191
00192 static struct rule *
00193 parse_file(const char *file)
00194 {
00195 FILE *fp;
00196 char buf[512];
00197 int line = 0;
00198 struct rule *r;
00199
00200 if (acpid_debug) {
00201 acpid_log("DBG: parsing conf file %s\n", file);
00202 }
00203
00204 fp = fopen(file, "r");
00205 if (!fp) {
00206 acpid_log("ERR: fopen(%s): %s\n", file, strerror(errno));
00207 return NULL;
00208 }
00209
00210
00211 r = new_rule();
00212 if (!r) {
00213 fclose(fp);
00214 return NULL;
00215 }
00216 r->type = RULE_CMD;
00217 r->origin = strdup(file);
00218 if (!r->origin) {
00219 acpid_log("ERR: strdup(): %s\n", strerror(errno));
00220 free_rule(r);
00221 fclose(fp);
00222 return NULL;
00223 }
00224
00225
00226 while (!feof(fp)) {
00227 char *p = buf;
00228 char key[64];
00229 char val[512];
00230 int n;
00231
00232 line++;
00233 memset(key, 0, sizeof(key));
00234 memset(val, 0, sizeof(val));
00235
00236 if (fgets(buf, sizeof(buf)-1, fp) == NULL) {
00237 continue;
00238 }
00239
00240
00241 while (*p && isspace((int)*p)) {
00242 p++;
00243 }
00244
00245 if (!*p || *p == '#') {
00246 continue;
00247 }
00248
00249
00250 n = sscanf(p, "%63[^=\n]=%255[^\n]", key, val);
00251 if (n != 2) {
00252 acpid_log("can't parse %s at line %d\n",
00253 file, line);
00254 continue;
00255 }
00256 if (acpid_debug >= 3) {
00257 acpid_log("DBG: key=\"%s\" val=\"%s\"\n", key, val);
00258 }
00259
00260 if (!strcasecmp(key, "event")) {
00261 int rv;
00262 r->event = malloc(sizeof(regex_t));
00263 if (!r) {
00264 acpid_log("ERR: malloc(): %s\n",
00265 strerror(errno));
00266 free_rule(r);
00267 fclose(fp);
00268 return NULL;
00269 }
00270 rv = regcomp(r->event, val, RULE_REGEX_FLAGS);
00271 if (rv) {
00272 char buf[128];
00273 regerror(rv, r->event, buf, sizeof(buf));
00274 acpid_log("ERR: regcomp(): %s\n", buf);
00275 free_rule(r);
00276 fclose(fp);
00277 return NULL;
00278 }
00279 } else if (!strcasecmp(key, "action")) {
00280 if (check_escapes(val) < 0) {
00281 acpid_log("ERR: can't load file %s\n", file);
00282 free_rule(r);
00283 fclose(fp);
00284 return NULL;
00285 }
00286 r->action.cmd = strdup(val);
00287 if (!r->action.cmd) {
00288 acpid_log("ERR: strdup(): %s\n",
00289 strerror(errno));
00290 free_rule(r);
00291 fclose(fp);
00292 return NULL;
00293 }
00294 } else {
00295 acpid_log("unknown option '%s' in %s at line %d\n",
00296 key, file, line);
00297 continue;
00298 }
00299 }
00300 if (!r->event || !r->action.cmd) {
00301 if (acpid_debug) {
00302 acpid_log("DBG: skipping incomplete file %s\n", file);
00303 }
00304 free_rule(r);
00305 return NULL;
00306 }
00307 fclose(fp);
00308
00309 return r;
00310 }
00311
00312 int
00313 acpid_add_client(int clifd, const char *origin)
00314 {
00315 struct rule *r;
00316 int nrules = 0;
00317
00318 acpid_log("client connected from %s\n", origin);
00319
00320 r = parse_client(clifd);
00321 if (r) {
00322 r->origin = strdup(origin);
00323 enlist_rule(&client_list, r);
00324 nrules++;
00325 }
00326
00327 acpid_log("%d client rule%s loaded\n", nrules, (nrules == 1)?"":"s");
00328
00329 return 0;
00330 }
00331
00332 static struct rule *
00333 parse_client(int client)
00334 {
00335 struct rule *r;
00336 int rv;
00337
00338
00339 r = new_rule();
00340 if (!r) {
00341 return NULL;
00342 }
00343 r->type = RULE_CLIENT;
00344 r->action.fd = client;
00345 r->event = malloc(sizeof(regex_t));
00346 if (!r) {
00347 acpid_log("ERR: malloc(): %s\n", strerror(errno));
00348 free_rule(r);
00349 return NULL;
00350 }
00351 rv = regcomp(r->event, ".*", RULE_REGEX_FLAGS);
00352 if (rv) {
00353 char buf[128];
00354 regerror(rv, r->event, buf, sizeof(buf));
00355 acpid_log("ERR: regcomp(): %s\n", buf);
00356 free_rule(r);
00357 return NULL;
00358 }
00359
00360 return r;
00361 }
00362
00363
00364
00365
00366
00367 static void
00368 enlist_rule(struct rule_list *list, struct rule *r)
00369 {
00370 r->next = r->prev = NULL;
00371 if (!list->head) {
00372 list->head = list->tail = r;
00373 } else {
00374 list->tail->next = r;
00375 r->prev = list->tail;
00376 list->tail = r;
00377 }
00378 }
00379
00380 static void
00381 delist_rule(struct rule_list *list, struct rule *r)
00382 {
00383 if (r->next) {
00384 r->next->prev = r->prev;
00385 } else {
00386 list->tail = r->prev;
00387 }
00388
00389 if (r->prev) {
00390 r->prev->next = r->next;
00391 } else {
00392 list->head = r->next;;
00393 }
00394
00395 r->next = r->prev = NULL;
00396 }
00397
00398 static struct rule *
00399 new_rule(void)
00400 {
00401 struct rule *r;
00402
00403 r = malloc(sizeof(*r));
00404 if (!r) {
00405 acpid_log("ERR: malloc(): %s\n", strerror(errno));
00406 return NULL;
00407 }
00408
00409 r->type = RULE_NONE;
00410 r->origin = NULL;
00411 r->event = NULL;
00412 r->action.cmd = NULL;
00413 r->prev = r->next = NULL;
00414
00415 return r;
00416 }
00417
00418
00419 static void
00420 free_rule(struct rule *r)
00421 {
00422 if (r->type == RULE_CMD) {
00423 if (r->action.cmd) {
00424 free(r->action.cmd);
00425 }
00426 }
00427
00428 if (r->origin) {
00429 free(r->origin);
00430 }
00431 if (r->event) {
00432 regfree(r->event);
00433 free(r->event);
00434 }
00435
00436 free(r);
00437 }
00438
00439
00440
00441
00442 int
00443 acpid_handle_event(const char *event)
00444 {
00445 struct rule *p;
00446 int nrules = 0;
00447 struct rule_list *ar[] = { &client_list, &cmd_list, NULL };
00448 struct rule_list **lp;
00449
00450
00451 lock_rules();
00452
00453
00454 for (lp = ar; *lp; lp++) {
00455 struct rule_list *l = *lp;
00456 p = l->head;
00457 while (p) {
00458
00459 struct rule *pnext = p->next;
00460 if (!regexec(p->event, event, 0, NULL, 0)) {
00461
00462 if (acpid_debug) {
00463 acpid_log("DBG: rule from %s matched\n",
00464 p->origin);
00465 }
00466 nrules++;
00467 if (p->type == RULE_CMD) {
00468 do_cmd_rule(p, event);
00469 } else if (p->type == RULE_CLIENT) {
00470 do_client_rule(p, event);
00471 } else {
00472 acpid_log("unknown rule type: %d\n",
00473 p->type);
00474 }
00475 } else {
00476 if (acpid_debug >= 3) {
00477 acpid_log("DBG: rule from %s did not "
00478 "match\n", p->origin);
00479 }
00480 }
00481 p = pnext;
00482 }
00483 }
00484
00485 unlock_rules();
00486
00487 if (acpid_debug) {
00488 acpid_log("DBG: %d total rule%s matched\n",
00489 nrules, (nrules == 1)?"":"s");
00490 }
00491
00492 return 0;
00493 }
00494
00495
00496 static sigset_t *
00497 signals_handled(void)
00498 {
00499 static sigset_t sigset;
00500
00501 sigemptyset(&sigset);
00502 sigaddset(&sigset, SIGHUP);
00503 sigaddset(&sigset, SIGTERM);
00504 sigaddset(&sigset, SIGQUIT);
00505 sigaddset(&sigset, SIGINT);
00506
00507 return &sigset;
00508 }
00509
00510 static void
00511 lock_rules(void)
00512 {
00513 if (acpid_debug >= 4) {
00514 acpid_log("DBG: blocking signals for rule lock\n");
00515 }
00516 sigprocmask(SIG_BLOCK, signals_handled(), NULL);
00517 }
00518
00519 static void
00520 unlock_rules(void)
00521 {
00522 if (acpid_debug >= 4) {
00523 acpid_log("DBG: unblocking signals for rule lock\n");
00524 }
00525 sigprocmask(SIG_UNBLOCK, signals_handled(), NULL);
00526 }
00527
00528
00529
00530
00531
00532 static int
00533 do_cmd_rule(struct rule *rule, const char *event)
00534 {
00535 pid_t pid;
00536 int status;
00537 const char *action;
00538
00539 pid = fork();
00540 switch (pid) {
00541 case -1:
00542 acpid_log("ERR: fork(): %s\n", strerror(errno));
00543 return -1;
00544 case 0:
00545
00546 action = parse_cmd(rule->action.cmd, event);
00547 acpid_log("executing action \"%s\"\n", action);
00548
00549
00550 signal(SIGHUP, SIG_DFL);
00551 signal(SIGTERM, SIG_DFL);
00552 signal(SIGINT, SIG_DFL);
00553 signal(SIGQUIT, SIG_DFL);
00554 signal(SIGPIPE, SIG_DFL);
00555 sigprocmask(SIG_UNBLOCK, signals_handled(), NULL);
00556
00557 acpid_log("BEGIN HANDLER MESSAGES\n");
00558 execl("/bin/sh", "/bin/sh", "-c", action, NULL);
00559
00560 acpid_log("ERR: execl(): %s\n", strerror(errno));
00561 exit(EXIT_FAILURE);
00562 }
00563
00564
00565 waitpid(pid, &status, 0);
00566 acpid_log("END HANDLER MESSAGES\n");
00567 if (WIFEXITED(status)) {
00568 acpid_log("action exited with status %d\n",
00569 WEXITSTATUS(status));
00570 } else if (WIFSIGNALED(status)) {
00571 acpid_log("action exited on signal %d\n", WTERMSIG(status));
00572 } else {
00573 acpid_log("action exited with status %d\n", status);
00574 }
00575
00576 return 0;
00577 }
00578
00579 static int
00580 do_client_rule(struct rule *rule, const char *event)
00581 {
00582 int r;
00583 int client = rule->action.fd;
00584
00585 acpid_log("notifying client %s\n", rule->origin);
00586
00587 r = safe_write(client, event, strlen(event));
00588 if (r < 0 && errno == EPIPE) {
00589
00590 acpid_log("client has disconnected\n");
00591 delist_rule(&client_list, rule);
00592 return -1;
00593 }
00594 safe_write(client, "\n", 1);
00595
00596 return 0;
00597 }
00598
00599 #define NTRIES 100
00600 static int
00601 safe_write(int fd, const char *buf, int len)
00602 {
00603 int r;
00604 int ttl = 0;
00605 int ntries = NTRIES;
00606
00607 do {
00608 r = write(fd, buf+ttl, len-ttl);
00609 if (r < 0) {
00610 if (errno != EAGAIN && errno != EINTR) {
00611
00612 return r;
00613 }
00614 ntries--;
00615 } else if (r > 0) {
00616
00617 ntries = NTRIES;
00618 ttl += r;
00619 }
00620 } while (ttl < len && ntries);
00621
00622 if (!ntries) {
00623
00624 if (acpid_debug >= 2) {
00625 acpid_log("uh-oh! safe_write() timed out\n");
00626 }
00627 return r;
00628 }
00629
00630 return ttl;
00631 }
00632
00633 static char *
00634 parse_cmd(const char *cmd, const char *event)
00635 {
00636 static char buf[4096];
00637 int i;
00638 const char *p;
00639
00640 p = cmd;
00641 i = 0;
00642
00643 memset(buf, 0, sizeof(buf));
00644 while (i < (sizeof(buf)-1)) {
00645 if (*p == '%') {
00646 p++;
00647 if (*p == 'e') {
00648
00649 int size = sizeof(buf) - i;
00650 size = snprintf(buf+i, size, "%s", event);
00651 i += size;
00652 p++;
00653 continue;
00654 }
00655 }
00656 if (!*p) {
00657 break;
00658 }
00659 buf[i++] = *p++;
00660 }
00661 if (acpid_debug >= 2) {
00662 acpid_log("DBG: expanded \"%s\" -> \"%s\"\n", cmd, buf);
00663 }
00664
00665 return buf;
00666 }
00667
00668 static int
00669 check_escapes(const char *str)
00670 {
00671 const char *p;
00672 int r = 0;
00673
00674 p = str;
00675 while (*p) {
00676
00677 if (*p == '%') {
00678 p++;
00679 if (!*p) {
00680 acpid_log("invalid escape at EOL\n");
00681 return -1;
00682 } else if (*p != '%' && *p != 'e') {
00683 acpid_log("invalid escape \"%%%c\"\n", *p);
00684 r = -1;
00685 }
00686 }
00687 p++;
00688 }
00689 return r;
00690 }