watchtab.c (15650B)
1 /* watchtab.c - configuration tables for file watches */ 2 3 /* 4 * Copyright (c) 2013, Natacha Porté 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <errno.h> 20 #include <grp.h> 21 #include <pwd.h> 22 #include <stdlib.h> 23 #include <string.h> 24 25 #include <sys/types.h> 26 #include <sys/event.h> 27 28 #include "log.h" 29 #include "watchtab.h" 30 31 /* number of pointers allocated at once in watch_env */ 32 #define WENV_ALLOC_UNIT 16; 33 34 /********************* 35 * LOCAL SUBPROGRAMS * 36 *********************/ 37 38 /* parse_events - process a configuration string into fflags vnode events */ 39 static u_int 40 parse_events(const char *line, size_t len) { 41 u_int result = 0; 42 size_t i = 0; 43 44 /* Check wildcard */ 45 if (len == 1 && line[0] == '*') 46 return NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB 47 | NOTE_LINK | NOTE_RENAME | NOTE_REVOKE; 48 49 /* 50 * Parse entry as a list of token separated by a single 51 * non-letter byte. 52 */ 53 while (i < len) { 54 if (strncmp(line + i, "delete", 6) == 0 55 || strncmp(line + i, "DELETE", 6) == 0) { 56 result |= NOTE_DELETE; 57 i += 6; 58 } 59 else if (strncmp(line + i, "write", 5) == 0 60 || strncmp(line + i, "WRITE", 5) == 0) { 61 result |= NOTE_WRITE; 62 i += 5; 63 } 64 else if (strncmp(line + i, "extend", 6) == 0 65 || strncmp(line + i, "EXTEND", 6) == 0) { 66 result |= NOTE_EXTEND; 67 i += 6; 68 } 69 else if (strncmp(line + i, "attrib", 6) == 0 70 || strncmp(line + i, "ATTRIB", 6) == 0) { 71 result |= NOTE_ATTRIB; 72 i += 6; 73 } 74 else if (strncmp(line + i, "link", 4) == 0 75 || strncmp(line + i, "LINK", 4) == 0) { 76 result |= NOTE_LINK; 77 i += 4; 78 } 79 else if (strncmp(line + i, "rename", 6) == 0 80 || strncmp(line + i, "RENAME", 6) == 0) { 81 result |= NOTE_RENAME; 82 i += 6; 83 } 84 else if (strncmp(line + i, "revoke", 6) == 0 85 || strncmp(line + i, "REVOKE", 6) == 0) { 86 result |= NOTE_REVOKE; 87 i += 6; 88 } 89 else return 0; 90 91 if (i < len && ((line[i] >= 'a' && line[i] <= 'z') 92 || (line[i] >= 'A' && line[i] <= 'Z'))) 93 return 0; 94 else 95 i++; 96 } 97 98 return result; 99 } 100 101 102 /* strdupesc - duplicate and unescape an input string */ 103 static char * 104 strdupesc(const char *src, size_t len) { 105 size_t s = 0, d = 0; 106 char *dest = malloc(len + 1); 107 108 if (!dest) { 109 log_alloc("watchtab entry internal string"); 110 return 0; 111 } 112 113 while (src[s] && s < len) { 114 if (src[s] != '\\' || (s > 0 && src[s-1] == '\\')) 115 dest[d++] = src[s]; 116 s++; 117 } 118 dest[d] = 0; 119 return dest; 120 } 121 122 /* wenv_resize - preallocate enough storage for new_size pointers */ 123 static int 124 wenv_resize(struct watch_env *wenv, size_t new_size) { 125 size_t new_cap, i; 126 const char **new_env; 127 128 /* Initialize if needed */ 129 if (!wenv->environ && wenv_init(wenv) < 0) 130 return -1; 131 132 /* Don't realloc when enough capacity is available */ 133 if (new_size <= wenv->capacity) return 0; 134 135 /* Actually resize */ 136 new_cap = wenv->capacity; 137 while (new_cap < new_size) new_cap += WENV_ALLOC_UNIT; 138 new_env = realloc(wenv->environ, sizeof *wenv->environ * new_cap); 139 if (!new_env) { 140 log_alloc("environment variables"); 141 return -1; 142 } 143 144 /* Clear new pointers */ 145 for (i = wenv->capacity; i < new_cap; i++) 146 new_env[i] = 0; 147 148 /* Update watch_env */ 149 wenv->capacity = new_cap; 150 wenv->environ = new_env; 151 return 0; 152 } 153 154 155 156 /******************** 157 * PUBLIC INTERFACE * 158 ********************/ 159 160 /* wentry_init - initialize a watch_entry with null values */ 161 void 162 wentry_init(struct watch_entry *wentry) { 163 if (!wentry) return; 164 165 wentry->path = 0; 166 wentry->events = 0; 167 wentry->delay.tv_sec = 0; 168 wentry->delay.tv_nsec = 0; 169 wentry->uid = 0; 170 wentry->gid = 0; 171 wentry->chroot = 0; 172 wentry->command = 0; 173 wentry->envp = 0; 174 wentry->fd = -1; 175 } 176 177 178 /* wentry_release - free internal objects from a watch_entry */ 179 void 180 wentry_release(struct watch_entry *wentry) { 181 if (!wentry) return; 182 183 free((void *)(wentry->path)); 184 free((void *)(wentry->chroot)); 185 free((void *)(wentry->command)); 186 wentry->path = 0; 187 wentry->chroot = 0; 188 wentry->command = 0; 189 190 if (wentry->envp) { 191 size_t i = 0; 192 while (wentry->envp[i]) 193 free(wentry->envp[i++]); 194 free(wentry->envp); 195 } 196 wentry->envp = 0; 197 198 if (wentry->fd != -1) 199 close(wentry->fd); 200 wentry->fd = -1; 201 } 202 203 204 /* wentry_free - free a watch_entry and the string it contains */ 205 void 206 wentry_free(struct watch_entry *wentry) { 207 if (!wentry) return; 208 209 wentry_release(wentry); 210 free(wentry); 211 } 212 213 214 /* wentry_readline - parse a config file line and fill a struct watch_entry */ 215 /* Return 0 on success or -1 on failure. */ 216 int 217 wentry_readline(struct watch_entry *dest, char *line, 218 struct watch_env *base_env, int has_home, 219 const char *filename, unsigned line_no) { 220 size_t path_len = 0; 221 size_t event_first = 0, event_len = 0; 222 size_t delay_first = 0, delay_len = 0; 223 size_t user_first = 0, user_len = 0; 224 size_t chroot_first = 0, chroot_len = 0; 225 size_t cmd_first = 0, cmd_len = 0; 226 struct passwd *pw = 0; 227 struct group *grp = 0; 228 size_t i = 1; 229 230 /* Sanity checks */ 231 if (!line || line[0] == 0 || line[0] == '\t') { 232 LOG_ASSERT(0); 233 return -1; 234 } 235 236 /* Look for fields boundaries */ 237 while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++; 238 path_len = i; 239 while (line[i] == '\t') i++; 240 event_first = i; 241 while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++; 242 event_len = i - event_first; 243 while (line[i] == '\t') i++; 244 delay_first = i; 245 while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++; 246 delay_len = i - delay_first; 247 while (line[i] == '\t') i++; 248 user_first = i; 249 while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++; 250 user_len = i - user_first; 251 while (line[i] == '\t') i++; 252 chroot_first = i; 253 while (line[i] != 0 && (line[i] != '\t' || line[i-1] == '\\')) i++; 254 chroot_len = i - chroot_first; 255 while (line[i] == '\t') i++; 256 cmd_first = i; 257 while (line[i] != 0) i++; 258 cmd_len = i - cmd_first; 259 260 /* Less than 3 fields found is a parse error */ 261 if (line[delay_first] == 0) { 262 log_watchtab_invalid_action(filename, line_no); 263 return -1; 264 } 265 266 /* Adjust offsets depending on which fields are omitted */ 267 if (line[user_first] == 0) { 268 /* 3-field line: path, events, command */ 269 cmd_first = delay_first; 270 cmd_len = delay_len; 271 delay_first = delay_len = 0; 272 user_first = user_len = 0; 273 chroot_first = chroot_len = 0; 274 } 275 else if (line[chroot_first] == 0) { 276 /* 4-field line: path, events, delay, command */ 277 cmd_first = user_first; 278 cmd_len = user_len; 279 user_first = user_len = chroot_first = chroot_len = 0; 280 } 281 else if (line[cmd_first] == 0) { 282 /* 5-field line: path, events, delay, user, command */ 283 cmd_first = chroot_first; 284 cmd_len = chroot_len; 285 chroot_first = chroot_len = 0; 286 } 287 288 /* Parse event set */ 289 dest->events = parse_events(line + event_first, event_len); 290 if (dest->events == 0) { 291 log_watchtab_invalid_events(filename, line_no, 292 line + event_first, event_len); 293 return -1; 294 } 295 296 /* Parse delay */ 297 dest->delay.tv_sec = 0; 298 dest->delay.tv_nsec = 0; 299 if (delay_len > 0 300 && !(delay_len == 1 && line[delay_first] == '*')) { 301 char *s; 302 303 /* Decode integer part */ 304 dest->delay.tv_sec = strtol(line + delay_first, &s, 10); 305 306 /* Decode fractional part if any */ 307 if (*s == '.') { 308 char *ns; 309 dest->delay.tv_nsec = strtol(s + 1, &ns, 10); 310 while (ns - s <= 9) { 311 dest->delay.tv_nsec *= 10; 312 s--; 313 } 314 s = ns; 315 } 316 317 /* Check trailing non-digits */ 318 if (s < line + delay_first + delay_len) { 319 line[delay_first + delay_len] = 0; 320 log_watchtab_invalid_delay(filename, line_no, 321 line + delay_first); 322 return -1; 323 } 324 } 325 326 /* Process user name and optional group name */ 327 if (user_len > 0) { 328 char *login = line + user_first; 329 char *group = 0; 330 331 line[user_first + user_len] = 0; 332 333 /* Process group */ 334 group = strchr(login, ':'); 335 if (group) { 336 *group = 0; 337 group++; 338 for (i = 0; group[i] >= '0' && group[i] <= '9'; i++); 339 errno = 0; 340 grp = group[i] 341 ? getgrnam(group) 342 : getgrgid(strtol(group, 0, 10)); 343 if (!grp) { 344 log_lookup_group(group); 345 return -1; 346 } 347 } 348 349 /* Lookup user name */ 350 for (i = 0; login[i] >= '0' && login[i] <= '9'; i++); 351 errno = 0; 352 pw = login[i] 353 ? getpwnam(login) 354 : getpwuid(strtol(login, 0, 10)); 355 if (!pw) { 356 log_lookup_pw(login); 357 return -1; 358 } 359 } 360 361 /* Store numeric ids */ 362 dest->uid = pw ? pw->pw_uid : 0; 363 dest->gid = grp ? grp->gr_gid : (pw ? pw->pw_gid : 0); 364 365 /* Lookup self name if not overridden */ 366 if (!pw) { 367 char *login; 368 errno = 0; 369 login = getlogin(); 370 pw = login ? getpwnam(login) : 0; 371 if (!pw) { 372 log_lookup_self(); 373 return -1; 374 } 375 } 376 377 /* At this point, no parse error can occur, filling in data */ 378 379 /* Clean up destination */ 380 wentry_release(dest); 381 382 /* Copy string parameters */ 383 dest->path = strdupesc(line, path_len); 384 dest->command = strdupesc(line + cmd_first, cmd_len); 385 386 if (chroot_len > 0) 387 dest->chroot = strdupesc(line + chroot_first, chroot_len); 388 else 389 dest->chroot = 0; 390 391 /* Setup environment */ 392 wenv_set(base_env, "LOGNAME", pw->pw_name, 1); 393 wenv_set(base_env, "USER", pw->pw_name, 1); 394 wenv_set(base_env, "HOME", pw->pw_dir, !has_home); 395 wenv_set(base_env, "TRIGGER", dest->path, 1); 396 dest->envp = wenv_dup(base_env); 397 398 return 0; 399 } 400 401 402 403 /*********************** 404 * WATCH_ENV INTERFACE * 405 ***********************/ 406 407 /* wenv_init - create an empty environment list */ 408 int 409 wenv_init(struct watch_env *wenv) { 410 if (!wenv) { 411 LOG_ASSERT(0); 412 return -1; 413 } 414 415 wenv->capacity = WENV_ALLOC_UNIT; 416 wenv->size = 0; 417 wenv->environ = malloc(sizeof *wenv->environ * wenv->capacity); 418 if (!wenv->environ) { 419 log_alloc("initial environment variables"); 420 return -1; 421 } 422 423 return 0; 424 } 425 426 427 /* wenv_release - free string memory in a struct watch_env but not the struct*/ 428 void 429 wenv_release(struct watch_env *wenv) { 430 free(wenv->environ); 431 wenv->size = 0; 432 wenv->environ = 0; 433 } 434 435 436 /* wenv_add - append a string to an existing struct watch_env */ 437 int 438 wenv_add(struct watch_env *wenv, const char *env_str) { 439 if (!wenv || !env_str) { 440 LOG_ASSERT(0); 441 return -1; 442 } 443 444 /* Increase array size if needed */ 445 if (wenv_resize(wenv, wenv->size + 2) < 0) return -1; 446 447 /* Store a copy of the provided string */ 448 wenv->environ[wenv->size] = strdup(env_str); 449 wenv->size++; 450 return 0; 451 } 452 453 /* wenv_set - insert or reset an environment variable */ 454 int 455 wenv_set(struct watch_env *wenv, const char *name, const char *value, 456 int overwrite) { 457 size_t namelen, linelen, i; 458 char *line; 459 460 if (!wenv || !name || !value) return -1; 461 462 /* Initialize if needed */ 463 if (!wenv->environ && wenv_init(wenv) < 0) 464 return -1; 465 466 /* Build the environment line */ 467 namelen = strlen(name); 468 linelen = namelen + 1 + strlen(value); 469 line = malloc(linelen + 1); 470 if (!line) { 471 log_alloc("environment variable entry"); 472 return -1; 473 } 474 strncpy(line, name, namelen); 475 line[namelen] = '='; 476 strncpy(line + namelen + 1, value, linelen - (namelen + 1)); 477 line[linelen] = 0; 478 479 /* Look for an existing entry for the name */ 480 i = 0; 481 while (wenv->environ[i]) { 482 if (strncmp(wenv->environ[i], line, namelen + 1) == 0) 483 break; 484 i++; 485 } 486 487 /* If not found, insert the crafted line */ 488 if (wenv->environ[i] == 0) { 489 if (wenv_resize(wenv, wenv->size + 2) < 0) { 490 free(line); 491 return -1; 492 } 493 wenv->environ[wenv->size] = line; 494 wenv->size++; 495 return 0; 496 } 497 498 /* Exit when environment variable exist but overwriting is forbidden */ 499 if (!overwrite) return 0; 500 501 /* Replace found variable with new environment line */ 502 free((void *)wenv->environ[i]); 503 wenv->environ[i] = line; 504 return 0; 505 } 506 507 /* wenv_get - lookup environment variable */ 508 const char * 509 wenv_get(struct watch_env *wenv, const char *name) { 510 size_t namelen, i; 511 512 if (!wenv || !wenv->environ || !name) { 513 LOG_ASSERT(0); 514 return 0; 515 } 516 namelen = strlen(name); 517 518 for (i = 0; wenv->environ[i]; i++) { 519 if (strncmp(wenv->environ[i], name, namelen) == 0 520 && wenv->environ[i][namelen] == '=') 521 return wenv->environ[i] + (namelen + 1); 522 } 523 524 return 0; 525 } 526 527 528 /* wenv_dup - deep copy environment strings */ 529 char ** 530 wenv_dup(struct watch_env *wenv) { 531 char **result; 532 size_t len, i; 533 int reported = 0; 534 535 if (!wenv) return 0; 536 len = (wenv->environ ? wenv->size : 0); 537 result = malloc((len + 1) * sizeof *result); 538 if (!result) { 539 log_alloc("environment duplicate"); 540 return 0; 541 } 542 543 for (i = 0; i < len; i++) { 544 result[i] = strdup(wenv->environ[i]); 545 if (!result[i] && !reported) { 546 log_alloc("environment item duplication"); 547 reported = 1; 548 } 549 } 550 result[len] = 0; 551 552 return result; 553 } 554 555 556 557 /********************** 558 * WATCHTAB INTERFACE * 559 **********************/ 560 561 /* wtab_release - release children objects but not the struct watchtab */ 562 void 563 wtab_release(struct watchtab *tab) { 564 struct watch_entry *entry = 0; 565 566 if (!tab) return; 567 568 while ((entry = SLIST_FIRST(tab)) != 0) { 569 SLIST_REMOVE_HEAD(tab, next); 570 wentry_free(entry); 571 } 572 } 573 574 575 /* wtab_readfile - parse the given file to build a new watchtab */ 576 int 577 wtab_readfile(struct watchtab *tab, FILE *input, const char *filename) { 578 char *line = 0; 579 size_t linecap = 0; 580 ssize_t linelen; 581 unsigned line_no = 0; 582 struct watch_entry *entry = 0; 583 int result = 0, has_home = 0; 584 size_t i, skip; 585 struct watch_env env; 586 587 if (!tab) { 588 LOG_ASSERT(0); 589 return -1; 590 } 591 592 /* Setup default environment */ 593 wenv_init(&env); 594 wenv_set(&env, "SHELL", "/bin/sh", 1); 595 wenv_set(&env, "PATH", "/usr/bin:/bin", 1); 596 597 /* Read the input data */ 598 while ((linelen = getdelim(&line, &linecap, '\n', input)) >= 0) { 599 line_no++; 600 601 /* Skip leading blanks */ 602 skip = 0; 603 while (line[skip] == ' ' || line[skip] == '\t') skip++; 604 605 /* Trim trailing blanks */ 606 while ((size_t)linelen > skip && (line[linelen-1] == '\n' 607 || line[linelen-1] == '\r' || line[linelen-1] == ' ' 608 || line[linelen-1] == '\t')) 609 linelen--; 610 line[linelen] = 0; 611 612 /* Ignore empty lines and comments */ 613 if ((size_t)linelen <= skip || line[skip] == '#') 614 continue; 615 616 /* 617 * Define environment lines as lines having an '=' before any 618 * tabulation ('\t') or backslash ('\\'). 619 */ 620 621 i = skip; 622 while (line[i] != 0 && line[i] != '=' 623 && line[i] != '\\' && line[i] != '\t') 624 i++; 625 626 /* Record an environment variable */ 627 if (line[i] == '=') { 628 /* Compute bounds of variable name */ 629 size_t j = i - 1; 630 while (line[j] == ' ' && j > skip) j--; 631 if (j + 1 < i) line[j + 1] = 0; 632 633 /* Check whether this explicitly sets HOME */ 634 if (strcmp(line + skip, "HOME") == 0) 635 has_home = 1; 636 637 /* Compute bounds of variable value */ 638 j = i + 1; 639 while (line[j] == ' ') j++; 640 641 /* Set the variable */ 642 wenv_set(&env, line + skip, line + j, 1); 643 continue; 644 } 645 646 /* Parse an entry line */ 647 entry = malloc(sizeof *entry); 648 if (!entry) { 649 log_alloc("watchtab entry"); 650 return -1; 651 } 652 wentry_init(entry); 653 if (wentry_readline(entry, line + skip, &env, has_home, 654 filename, line_no) < 0) { 655 /* propagate an error but keep parsing */ 656 result = -1; 657 wentry_free(entry); 658 continue; 659 } 660 661 /* Insert the entry in the list */ 662 SLIST_INSERT_HEAD(tab, entry, next); 663 } 664 665 if (ferror(input)) { 666 log_watchtab_read(); 667 return -1; 668 } 669 670 return result; 671 }