1 /*- 2 * Copyright (c) 2014 The FreeBSD Foundation 3 * All rights reserved. 4 * 5 * This software was developed by Edward Tomasz Napierala under sponsorship 6 * from the FreeBSD Foundation. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include <sys/types.h> 35 #include <sys/time.h> 36 #include <sys/ioctl.h> 37 #include <sys/param.h> 38 #include <sys/linker.h> 39 #include <sys/mount.h> 40 #include <sys/socket.h> 41 #include <sys/stat.h> 42 #include <sys/wait.h> 43 #include <sys/utsname.h> 44 #include <assert.h> 45 #include <ctype.h> 46 #include <err.h> 47 #include <errno.h> 48 #include <fcntl.h> 49 #include <libgen.h> 50 #include <netdb.h> 51 #include <paths.h> 52 #include <signal.h> 53 #include <stdbool.h> 54 #include <stdint.h> 55 #define _WITH_GETLINE 56 #include <stdio.h> 57 #include <stdlib.h> 58 #include <string.h> 59 #include <unistd.h> 60 61 #include <libutil.h> 62 63 #include "autofs_ioctl.h" 64 65 #include "common.h" 66 67 extern FILE *yyin; 68 extern char *yytext; 69 extern int yylex(void); 70 71 static void parse_master_yyin(struct node *root, const char *master); 72 static void parse_map_yyin(struct node *parent, const char *map, 73 const char *executable_key); 74 75 char * 76 checked_strdup(const char *s) 77 { 78 char *c; 79 80 assert(s != NULL); 81 82 c = strdup(s); 83 if (c == NULL) 84 log_err(1, "strdup"); 85 return (c); 86 } 87 88 /* 89 * Take two pointers to strings, concatenate the contents with "/" in the 90 * middle, make the first pointer point to the result, the second pointer 91 * to NULL, and free the old strings. 92 * 93 * Concatenate pathnames, basically. 94 */ 95 static void 96 concat(char **p1, char **p2) 97 { 98 int ret; 99 char *path; 100 101 assert(p1 != NULL); 102 assert(p2 != NULL); 103 104 if (*p1 == NULL) 105 *p1 = checked_strdup(""); 106 107 if (*p2 == NULL) 108 *p2 = checked_strdup(""); 109 110 ret = asprintf(&path, "%s/%s", *p1, *p2); 111 if (ret < 0) 112 log_err(1, "asprintf"); 113 114 /* 115 * XXX 116 */ 117 //free(*p1); 118 //free(*p2); 119 120 *p1 = path; 121 *p2 = NULL; 122 } 123 124 /* 125 * Concatenate two strings, inserting separator between them, unless not needed. 126 * 127 * This function is very convenient to use when you do not care about freeing 128 * memory - which is okay here, because we are a short running process. 129 */ 130 char * 131 separated_concat(const char *s1, const char *s2, char separator) 132 { 133 char *result; 134 int ret; 135 136 assert(s1 != NULL); 137 assert(s2 != NULL); 138 139 if (s1[0] == '\0' || s2[0] == '\0' || 140 s1[strlen(s1) - 1] == separator || s2[0] == separator) { 141 ret = asprintf(&result, "%s%s", s1, s2); 142 } else { 143 ret = asprintf(&result, "%s%c%s", s1, separator, s2); 144 } 145 if (ret < 0) 146 log_err(1, "asprintf"); 147 148 //log_debugx("separated_concat: got %s and %s, returning %s", s1, s2, result); 149 150 return (result); 151 } 152 153 void 154 create_directory(const char *path) 155 { 156 char *component, *copy, *tofree, *partial; 157 int error; 158 159 assert(path[0] == '/'); 160 161 /* 162 * +1 to skip the leading slash. 163 */ 164 copy = tofree = checked_strdup(path + 1); 165 166 partial = NULL; 167 for (;;) { 168 component = strsep(©, "/"); 169 if (component == NULL) 170 break; 171 concat(&partial, &component); 172 //log_debugx("checking \"%s\" for existence", partial); 173 error = access(partial, F_OK); 174 if (error == 0) 175 continue; 176 if (errno != ENOENT) 177 log_err(1, "cannot access %s", partial); 178 log_debugx("directory %s does not exist, creating", 179 partial); 180 error = mkdir(partial, 0755); 181 if (error != 0) 182 log_err(1, "cannot create %s", partial); 183 } 184 185 free(tofree); 186 } 187 188 struct node * 189 node_new_root(void) 190 { 191 struct node *n; 192 193 n = calloc(1, sizeof(*n)); 194 if (n == NULL) 195 log_err(1, "calloc"); 196 // XXX 197 n->n_key = checked_strdup("/"); 198 n->n_options = checked_strdup(""); 199 200 TAILQ_INIT(&n->n_children); 201 202 return (n); 203 } 204 205 struct node * 206 node_new(struct node *parent, char *key, char *options, char *location, 207 const char *config_file, int config_line) 208 { 209 struct node *n; 210 211 n = calloc(1, sizeof(*n)); 212 if (n == NULL) 213 log_err(1, "calloc"); 214 215 TAILQ_INIT(&n->n_children); 216 assert(key != NULL); 217 assert(key[0] != '\0'); 218 n->n_key = key; 219 if (options != NULL) 220 n->n_options = options; 221 else 222 n->n_options = strdup(""); 223 n->n_location = location; 224 assert(config_file != NULL); 225 n->n_config_file = config_file; 226 assert(config_line >= 0); 227 n->n_config_line = config_line; 228 229 assert(parent != NULL); 230 n->n_parent = parent; 231 TAILQ_INSERT_TAIL(&parent->n_children, n, n_next); 232 233 return (n); 234 } 235 236 struct node * 237 node_new_map(struct node *parent, char *key, char *options, char *map, 238 const char *config_file, int config_line) 239 { 240 struct node *n; 241 242 n = calloc(1, sizeof(*n)); 243 if (n == NULL) 244 log_err(1, "calloc"); 245 246 TAILQ_INIT(&n->n_children); 247 assert(key != NULL); 248 assert(key[0] != '\0'); 249 n->n_key = key; 250 if (options != NULL) 251 n->n_options = options; 252 else 253 n->n_options = strdup(""); 254 n->n_map = map; 255 assert(config_file != NULL); 256 n->n_config_file = config_file; 257 assert(config_line >= 0); 258 n->n_config_line = config_line; 259 260 assert(parent != NULL); 261 n->n_parent = parent; 262 TAILQ_INSERT_TAIL(&parent->n_children, n, n_next); 263 264 return (n); 265 } 266 267 static struct node * 268 node_duplicate(const struct node *o, struct node *parent) 269 { 270 const struct node *child; 271 struct node *n; 272 273 if (parent == NULL) 274 parent = o->n_parent; 275 276 n = node_new(parent, o->n_key, o->n_options, o->n_location, 277 o->n_config_file, o->n_config_line); 278 279 TAILQ_FOREACH(child, &o->n_children, n_next) 280 node_duplicate(child, n); 281 282 return (n); 283 } 284 285 static void 286 node_delete(struct node *n) 287 { 288 struct node *child, *tmp; 289 290 assert (n != NULL); 291 292 TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) 293 node_delete(child); 294 295 if (n->n_parent != NULL) 296 TAILQ_REMOVE(&n->n_parent->n_children, n, n_next); 297 298 free(n); 299 } 300 301 /* 302 * Move (reparent) node 'n' to make it sibling of 'previous', placed 303 * just after it. 304 */ 305 static void 306 node_move_after(struct node *n, struct node *previous) 307 { 308 309 TAILQ_REMOVE(&n->n_parent->n_children, n, n_next); 310 n->n_parent = previous->n_parent; 311 TAILQ_INSERT_AFTER(&previous->n_parent->n_children, previous, n, n_next); 312 } 313 314 static void 315 node_expand_includes(struct node *root, bool is_master) 316 { 317 struct node *n, *n2, *tmp, *tmp2, *tmproot; 318 int error; 319 320 TAILQ_FOREACH_SAFE(n, &root->n_children, n_next, tmp) { 321 if (n->n_key[0] != '+') 322 continue; 323 324 error = access(AUTO_INCLUDE_PATH, F_OK); 325 if (error != 0) { 326 log_errx(1, "directory services not configured; " 327 "%s does not exist", AUTO_INCLUDE_PATH); 328 } 329 330 /* 331 * "+1" to skip leading "+". 332 */ 333 yyin = auto_popen(AUTO_INCLUDE_PATH, n->n_key + 1, NULL); 334 assert(yyin != NULL); 335 336 tmproot = node_new_root(); 337 if (is_master) 338 parse_master_yyin(tmproot, n->n_key); 339 else 340 parse_map_yyin(tmproot, n->n_key, NULL); 341 342 error = auto_pclose(yyin); 343 yyin = NULL; 344 if (error != 0) { 345 log_errx(1, "failed to handle include \"%s\"", 346 n->n_key); 347 } 348 349 /* 350 * Entries to be included are now in tmproot. We need to merge 351 * them with the rest, preserving their place and ordering. 352 */ 353 TAILQ_FOREACH_REVERSE_SAFE(n2, 354 &tmproot->n_children, nodehead, n_next, tmp2) { 355 node_move_after(n2, n); 356 } 357 358 node_delete(n); 359 node_delete(tmproot); 360 } 361 } 362 363 static char * 364 expand_ampersand(char *string, const char *key) 365 { 366 char c, *expanded; 367 int i, ret, before_len = 0; 368 bool backslashed = false; 369 370 assert(key[0] != '\0'); 371 372 expanded = checked_strdup(string); 373 374 for (i = 0; string[i] != '\0'; i++) { 375 c = string[i]; 376 if (c == '\\' && backslashed == false) { 377 backslashed = true; 378 continue; 379 } 380 if (backslashed) { 381 backslashed = false; 382 continue; 383 } 384 backslashed = false; 385 if (c != '&') 386 continue; 387 388 /* 389 * The 'before_len' variable contains the number 390 * of characters before the '&'. 391 */ 392 before_len = i; 393 //assert(i + 1 < (int)strlen(string)); 394 395 ret = asprintf(&expanded, "%.*s%s%s", 396 before_len, string, key, string + before_len + 1); 397 if (ret < 0) 398 log_err(1, "asprintf"); 399 400 //log_debugx("\"%s\" expanded with key \"%s\" to \"%s\"", 401 // string, key, expanded); 402 403 /* 404 * Figure out where to start searching for next variable. 405 */ 406 string = expanded; 407 i = before_len + strlen(key); 408 backslashed = false; 409 //assert(i < (int)strlen(string)); 410 } 411 412 return (expanded); 413 } 414 415 /* 416 * Expand "&" in n_location. If the key is NULL, try to use 417 * key from map entries themselves. Keep in mind that maps 418 * consist of tho levels of node structures, the key is one 419 * level up. 420 * 421 * Variant with NULL key is for "automount -LL". 422 */ 423 void 424 node_expand_ampersand(struct node *n, const char *key) 425 { 426 struct node *child; 427 428 if (n->n_location != NULL) { 429 if (key == NULL) { 430 if (n->n_parent != NULL && 431 strcmp(n->n_parent->n_key, "*") != 0) { 432 n->n_location = expand_ampersand(n->n_location, 433 n->n_parent->n_key); 434 } 435 } else { 436 n->n_location = expand_ampersand(n->n_location, key); 437 } 438 } 439 440 TAILQ_FOREACH(child, &n->n_children, n_next) 441 node_expand_ampersand(child, key); 442 } 443 444 /* 445 * Expand "*" in n_key. 446 */ 447 void 448 node_expand_wildcard(struct node *n, const char *key) 449 { 450 struct node *child, *expanded; 451 452 assert(key != NULL); 453 454 if (strcmp(n->n_key, "*") == 0) { 455 expanded = node_duplicate(n, NULL); 456 expanded->n_key = checked_strdup(key); 457 node_move_after(expanded, n); 458 } 459 460 TAILQ_FOREACH(child, &n->n_children, n_next) 461 node_expand_wildcard(child, key); 462 } 463 464 int 465 node_expand_defined(struct node *n) 466 { 467 struct node *child; 468 int error, cumulated_error = 0; 469 470 if (n->n_location != NULL) { 471 n->n_location = defined_expand(n->n_location); 472 if (n->n_location == NULL) { 473 log_warnx("failed to expand location for %s", 474 node_path(n)); 475 return (EINVAL); 476 } 477 } 478 479 TAILQ_FOREACH(child, &n->n_children, n_next) { 480 error = node_expand_defined(child); 481 if (error != 0 && cumulated_error == 0) 482 cumulated_error = error; 483 } 484 485 return (cumulated_error); 486 } 487 488 bool 489 node_is_direct_map(const struct node *n) 490 { 491 492 for (;;) { 493 assert(n->n_parent != NULL); 494 if (n->n_parent->n_parent == NULL) 495 break; 496 n = n->n_parent; 497 } 498 499 assert(n->n_key != NULL); 500 if (strcmp(n->n_key, "/-") != 0) 501 return (false); 502 503 return (true); 504 } 505 506 static void 507 node_expand_maps(struct node *n, bool indirect) 508 { 509 struct node *child, *tmp; 510 511 TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) { 512 if (node_is_direct_map(child)) { 513 if (indirect) 514 continue; 515 } else { 516 if (indirect == false) 517 continue; 518 } 519 520 /* 521 * This is the first-level map node; the one that contains 522 * the key and subnodes with mountpoints and actual map names. 523 */ 524 if (child->n_map == NULL) 525 continue; 526 527 if (indirect) { 528 log_debugx("map \"%s\" is an indirect map, parsing", 529 child->n_map); 530 } else { 531 log_debugx("map \"%s\" is a direct map, parsing", 532 child->n_map); 533 } 534 parse_map(child, child->n_map, NULL); 535 } 536 } 537 538 static void 539 node_expand_direct_maps(struct node *n) 540 { 541 542 node_expand_maps(n, false); 543 } 544 545 void 546 node_expand_indirect_maps(struct node *n) 547 { 548 549 node_expand_maps(n, true); 550 } 551 552 static char * 553 node_path_x(const struct node *n, char *x) 554 { 555 char *path; 556 size_t len; 557 558 if (n->n_parent == NULL) 559 return (x); 560 561 /* 562 * Return "/-" for direct maps only if we were asked for path 563 * to the "/-" node itself, not to any of its subnodes. 564 */ 565 if (n->n_parent->n_parent == NULL && 566 strcmp(n->n_key, "/-") == 0 && 567 x[0] != '\0') { 568 return (x); 569 } 570 571 assert(n->n_key[0] != '\0'); 572 path = separated_concat(n->n_key, x, '/'); 573 free(x); 574 575 /* 576 * Strip trailing slash. 577 */ 578 len = strlen(path); 579 assert(len > 0); 580 if (path[len - 1] == '/') 581 path[len - 1] = '\0'; 582 583 return (node_path_x(n->n_parent, path)); 584 } 585 586 /* 587 * Return full path for node, consisting of concatenated 588 * paths of node itself and all its parents, up to the root. 589 */ 590 char * 591 node_path(const struct node *n) 592 { 593 594 return (node_path_x(n, checked_strdup(""))); 595 } 596 597 static char * 598 node_options_x(const struct node *n, char *x) 599 { 600 char *options; 601 602 options = separated_concat(x, n->n_options, ','); 603 if (n->n_parent == NULL) 604 return (options); 605 606 return (node_options_x(n->n_parent, options)); 607 } 608 609 /* 610 * Return options for node, consisting of concatenated 611 * options from the node itself and all its parents, 612 * up to the root. 613 */ 614 char * 615 node_options(const struct node *n) 616 { 617 618 return (node_options_x(n, checked_strdup(""))); 619 } 620 621 static void 622 node_print_indent(const struct node *n, int indent) 623 { 624 const struct node *child, *first_child; 625 char *path, *options; 626 627 path = node_path(n); 628 options = node_options(n); 629 630 /* 631 * Do not show both parent and child node if they have the same 632 * mountpoint; only show the child node. This means the typical, 633 * "key location", map entries are shown in a single line; 634 * the "key mountpoint1 location2 mountpoint2 location2" entries 635 * take multiple lines. 636 */ 637 first_child = TAILQ_FIRST(&n->n_children); 638 if (first_child == NULL || TAILQ_NEXT(first_child, n_next) != NULL || 639 strcmp(path, node_path(first_child)) != 0) { 640 assert(n->n_location == NULL || n->n_map == NULL); 641 printf("%*.s%-*s %s%-*s %-*s # %s map %s at %s:%d\n", 642 indent, "", 643 25 - indent, 644 path, 645 options[0] != '\0' ? "-" : " ", 646 20, 647 options[0] != '\0' ? options : "", 648 20, 649 n->n_location != NULL ? n->n_location : n->n_map != NULL ? n->n_map : "", 650 node_is_direct_map(n) ? "direct" : "indirect", 651 indent == 0 ? "referenced" : "defined", 652 n->n_config_file, n->n_config_line); 653 } 654 655 free(path); 656 free(options); 657 658 TAILQ_FOREACH(child, &n->n_children, n_next) 659 node_print_indent(child, indent + 2); 660 } 661 662 void 663 node_print(const struct node *n) 664 { 665 const struct node *child; 666 667 TAILQ_FOREACH(child, &n->n_children, n_next) 668 node_print_indent(child, 0); 669 } 670 671 struct node * 672 node_find(struct node *node, const char *path) 673 { 674 struct node *child, *found; 675 char *tmp; 676 size_t tmplen; 677 678 //log_debugx("looking up %s in %s", path, node->n_key); 679 680 tmp = node_path(node); 681 tmplen = strlen(tmp); 682 if (strncmp(tmp, path, tmplen) != 0) { 683 free(tmp); 684 return (NULL); 685 } 686 if (path[tmplen] != '/' && path[tmplen] != '\0') { 687 /* 688 * If we have two map entries like 'foo' and 'foobar', make 689 * sure the search for 'foobar' won't match 'foo' instead. 690 */ 691 free(tmp); 692 return (NULL); 693 } 694 free(tmp); 695 696 TAILQ_FOREACH(child, &node->n_children, n_next) { 697 found = node_find(child, path); 698 if (found != NULL) 699 return (found); 700 } 701 702 return (node); 703 } 704 705 /* 706 * Canonical form of a map entry looks like this: 707 * 708 * key [-options] [ [/mountpoint] [-options2] location ... ] 709 * 710 * Entries for executable maps are slightly different, as they 711 * lack the 'key' field and are always single-line; the key field 712 * for those maps is taken from 'executable_key' argument. 713 * 714 * We parse it in such a way that a map always has two levels - first 715 * for key, and the second, for the mountpoint. 716 */ 717 static void 718 parse_map_yyin(struct node *parent, const char *map, const char *executable_key) 719 { 720 char *key = NULL, *options = NULL, *mountpoint = NULL, 721 *options2 = NULL, *location = NULL; 722 int ret; 723 struct node *node; 724 725 lineno = 1; 726 727 if (executable_key != NULL) 728 key = checked_strdup(executable_key); 729 730 for (;;) { 731 ret = yylex(); 732 if (ret == 0 || ret == NEWLINE) { 733 /* 734 * In case of executable map, the key is always 735 * non-NULL, even if the map is empty. So, make sure 736 * we don't fail empty maps here. 737 */ 738 if ((key != NULL && executable_key == NULL) || 739 options != NULL) { 740 log_errx(1, "truncated entry at %s, line %d", 741 map, lineno); 742 } 743 if (ret == 0 || executable_key != NULL) { 744 /* 745 * End of file. 746 */ 747 break; 748 } else { 749 key = options = NULL; 750 continue; 751 } 752 } 753 if (key == NULL) { 754 key = checked_strdup(yytext); 755 if (key[0] == '+') { 756 node_new(parent, key, NULL, NULL, map, lineno); 757 key = options = NULL; 758 continue; 759 } 760 continue; 761 } else if (yytext[0] == '-') { 762 if (options != NULL) { 763 log_errx(1, "duplicated options at %s, line %d", 764 map, lineno); 765 } 766 /* 767 * +1 to skip leading "-". 768 */ 769 options = checked_strdup(yytext + 1); 770 continue; 771 } 772 773 /* 774 * We cannot properly handle a situation where the map key 775 * is "/". Ignore such entries. 776 * 777 * XXX: According to Piete Brooks, Linux automounter uses 778 * "/" as a wildcard character in LDAP maps. Perhaps 779 * we should work around this braindamage by substituting 780 * "*" for "/"? 781 */ 782 if (strcmp(key, "/") == 0) { 783 log_warnx("nonsensical map key \"/\" at %s, line %d; " 784 "ignoring map entry ", map, lineno); 785 786 /* 787 * Skip the rest of the entry. 788 */ 789 do { 790 ret = yylex(); 791 } while (ret != 0 && ret != NEWLINE); 792 793 key = options = NULL; 794 continue; 795 } 796 797 //log_debugx("adding map node, %s", key); 798 node = node_new(parent, key, options, NULL, map, lineno); 799 key = options = NULL; 800 801 for (;;) { 802 if (yytext[0] == '/') { 803 if (mountpoint != NULL) { 804 log_errx(1, "duplicated mountpoint " 805 "in %s, line %d", map, lineno); 806 } 807 if (options2 != NULL || location != NULL) { 808 log_errx(1, "mountpoint out of order " 809 "in %s, line %d", map, lineno); 810 } 811 mountpoint = checked_strdup(yytext); 812 goto again; 813 } 814 815 if (yytext[0] == '-') { 816 if (options2 != NULL) { 817 log_errx(1, "duplicated options " 818 "in %s, line %d", map, lineno); 819 } 820 if (location != NULL) { 821 log_errx(1, "options out of order " 822 "in %s, line %d", map, lineno); 823 } 824 options2 = checked_strdup(yytext + 1); 825 goto again; 826 } 827 828 if (location != NULL) { 829 log_errx(1, "too many arguments " 830 "in %s, line %d", map, lineno); 831 } 832 833 /* 834 * If location field starts with colon, e.g. ":/dev/cd0", 835 * then strip it. 836 */ 837 if (yytext[0] == ':') { 838 location = checked_strdup(yytext + 1); 839 if (location[0] == '\0') { 840 log_errx(1, "empty location in %s, " 841 "line %d", map, lineno); 842 } 843 } else { 844 location = checked_strdup(yytext); 845 } 846 847 if (mountpoint == NULL) 848 mountpoint = checked_strdup("/"); 849 if (options2 == NULL) 850 options2 = checked_strdup(""); 851 852 #if 0 853 log_debugx("adding map node, %s %s %s", 854 mountpoint, options2, location); 855 #endif 856 node_new(node, mountpoint, options2, location, 857 map, lineno); 858 mountpoint = options2 = location = NULL; 859 again: 860 ret = yylex(); 861 if (ret == 0 || ret == NEWLINE) { 862 if (mountpoint != NULL || options2 != NULL || 863 location != NULL) { 864 log_errx(1, "truncated entry " 865 "in %s, line %d", map, lineno); 866 } 867 break; 868 } 869 } 870 } 871 } 872 873 /* 874 * Parse output of a special map called without argument. It is a list 875 * of keys, separated by newlines. They can contain whitespace, so use 876 * getline(3) instead of lexer used for maps. 877 */ 878 static void 879 parse_map_keys_yyin(struct node *parent, const char *map) 880 { 881 char *line = NULL, *key; 882 size_t linecap = 0; 883 ssize_t linelen; 884 885 lineno = 1; 886 887 for (;;) { 888 linelen = getline(&line, &linecap, yyin); 889 if (linelen < 0) { 890 /* 891 * End of file. 892 */ 893 break; 894 } 895 if (linelen <= 1) { 896 /* 897 * Empty line, consisting of just the newline. 898 */ 899 continue; 900 } 901 902 /* 903 * "-1" to strip the trailing newline. 904 */ 905 key = strndup(line, linelen - 1); 906 907 log_debugx("adding key \"%s\"", key); 908 node_new(parent, key, NULL, NULL, map, lineno); 909 lineno++; 910 } 911 free(line); 912 } 913 914 static bool 915 file_is_executable(const char *path) 916 { 917 struct stat sb; 918 int error; 919 920 error = stat(path, &sb); 921 if (error != 0) 922 log_err(1, "cannot stat %s", path); 923 if ((sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) || 924 (sb.st_mode & S_IXOTH)) 925 return (true); 926 return (false); 927 } 928 929 /* 930 * Parse a special map, e.g. "-hosts". 931 */ 932 static void 933 parse_special_map(struct node *parent, const char *map, const char *key) 934 { 935 char *path; 936 int error, ret; 937 938 assert(map[0] == '-'); 939 940 /* 941 * +1 to skip leading "-" in map name. 942 */ 943 ret = asprintf(&path, "%s/special_%s", AUTO_SPECIAL_PREFIX, map + 1); 944 if (ret < 0) 945 log_err(1, "asprintf"); 946 947 yyin = auto_popen(path, key, NULL); 948 assert(yyin != NULL); 949 950 if (key == NULL) { 951 parse_map_keys_yyin(parent, map); 952 } else { 953 parse_map_yyin(parent, map, key); 954 } 955 956 error = auto_pclose(yyin); 957 yyin = NULL; 958 if (error != 0) 959 log_errx(1, "failed to handle special map \"%s\"", map); 960 961 node_expand_includes(parent, false); 962 node_expand_direct_maps(parent); 963 964 free(path); 965 } 966 967 /* 968 * Retrieve and parse map from directory services, e.g. LDAP. 969 * Note that it is different from executable maps, in that 970 * the include script outputs the whole map to standard output 971 * (as opposed to executable maps that only output a single 972 * entry, without the key), and it takes the map name as an 973 * argument, instead of key. 974 */ 975 static void 976 parse_included_map(struct node *parent, const char *map) 977 { 978 int error; 979 980 assert(map[0] != '-'); 981 assert(map[0] != '/'); 982 983 error = access(AUTO_INCLUDE_PATH, F_OK); 984 if (error != 0) { 985 log_errx(1, "directory services not configured;" 986 " %s does not exist", AUTO_INCLUDE_PATH); 987 } 988 989 yyin = auto_popen(AUTO_INCLUDE_PATH, map, NULL); 990 assert(yyin != NULL); 991 992 parse_map_yyin(parent, map, NULL); 993 994 error = auto_pclose(yyin); 995 yyin = NULL; 996 if (error != 0) 997 log_errx(1, "failed to handle remote map \"%s\"", map); 998 999 node_expand_includes(parent, false); 1000 node_expand_direct_maps(parent); 1001 } 1002 1003 void 1004 parse_map(struct node *parent, const char *map, const char *key) 1005 { 1006 char *path = NULL; 1007 int error, ret; 1008 bool executable; 1009 1010 assert(map != NULL); 1011 assert(map[0] != '\0'); 1012 1013 log_debugx("parsing map \"%s\"", map); 1014 1015 if (map[0] == '-') 1016 return (parse_special_map(parent, map, key)); 1017 1018 if (map[0] == '/') { 1019 path = checked_strdup(map); 1020 } else { 1021 ret = asprintf(&path, "%s/%s", AUTO_MAP_PREFIX, map); 1022 if (ret < 0) 1023 log_err(1, "asprintf"); 1024 log_debugx("map \"%s\" maps to \"%s\"", map, path); 1025 1026 /* 1027 * See if the file exists. If not, try to obtain the map 1028 * from directory services. 1029 */ 1030 error = access(path, F_OK); 1031 if (error != 0) { 1032 log_debugx("map file \"%s\" does not exist; falling " 1033 "back to directory services", path); 1034 return (parse_included_map(parent, map)); 1035 } 1036 } 1037 1038 executable = file_is_executable(path); 1039 1040 if (executable) { 1041 log_debugx("map \"%s\" is executable", map); 1042 1043 if (key != NULL) { 1044 yyin = auto_popen(path, key, NULL); 1045 } else { 1046 yyin = auto_popen(path, NULL); 1047 } 1048 assert(yyin != NULL); 1049 } else { 1050 yyin = fopen(path, "r"); 1051 if (yyin == NULL) 1052 log_err(1, "unable to open \"%s\"", path); 1053 } 1054 1055 free(path); 1056 path = NULL; 1057 1058 parse_map_yyin(parent, map, executable ? key : NULL); 1059 1060 if (executable) { 1061 error = auto_pclose(yyin); 1062 yyin = NULL; 1063 if (error != 0) { 1064 log_errx(1, "failed to handle executable map \"%s\"", 1065 map); 1066 } 1067 } else { 1068 fclose(yyin); 1069 } 1070 yyin = NULL; 1071 1072 log_debugx("done parsing map \"%s\"", map); 1073 1074 node_expand_includes(parent, false); 1075 node_expand_direct_maps(parent); 1076 } 1077 1078 static void 1079 parse_master_yyin(struct node *root, const char *master) 1080 { 1081 char *mountpoint = NULL, *map = NULL, *options = NULL; 1082 int ret; 1083 1084 /* 1085 * XXX: 1 gives incorrect values; wtf? 1086 */ 1087 lineno = 0; 1088 1089 for (;;) { 1090 ret = yylex(); 1091 if (ret == 0 || ret == NEWLINE) { 1092 if (mountpoint != NULL) { 1093 //log_debugx("adding map for %s", mountpoint); 1094 node_new_map(root, mountpoint, options, map, 1095 master, lineno); 1096 } 1097 if (ret == 0) { 1098 break; 1099 } else { 1100 mountpoint = map = options = NULL; 1101 continue; 1102 } 1103 } 1104 if (mountpoint == NULL) { 1105 mountpoint = checked_strdup(yytext); 1106 } else if (map == NULL) { 1107 map = checked_strdup(yytext); 1108 } else if (options == NULL) { 1109 /* 1110 * +1 to skip leading "-". 1111 */ 1112 options = checked_strdup(yytext + 1); 1113 } else { 1114 log_errx(1, "too many arguments at %s, line %d", 1115 master, lineno); 1116 } 1117 } 1118 } 1119 1120 void 1121 parse_master(struct node *root, const char *master) 1122 { 1123 1124 log_debugx("parsing auto_master file at \"%s\"", master); 1125 1126 yyin = fopen(master, "r"); 1127 if (yyin == NULL) 1128 err(1, "unable to open %s", master); 1129 1130 parse_master_yyin(root, master); 1131 1132 fclose(yyin); 1133 yyin = NULL; 1134 1135 log_debugx("done parsing \"%s\"", master); 1136 1137 node_expand_includes(root, true); 1138 node_expand_direct_maps(root); 1139 } 1140 1141 /* 1142 * Two things daemon(3) does, that we actually also want to do 1143 * when running in foreground, is closing the stdin and chdiring 1144 * to "/". This is what we do here. 1145 */ 1146 void 1147 lesser_daemon(void) 1148 { 1149 int error, fd; 1150 1151 error = chdir("/"); 1152 if (error != 0) 1153 log_warn("chdir"); 1154 1155 fd = open(_PATH_DEVNULL, O_RDWR, 0); 1156 if (fd < 0) { 1157 log_warn("cannot open %s", _PATH_DEVNULL); 1158 return; 1159 } 1160 1161 error = dup2(fd, STDIN_FILENO); 1162 if (error != 0) 1163 log_warn("dup2"); 1164 1165 error = close(fd); 1166 if (error != 0) { 1167 /* Bloody hell. */ 1168 log_warn("close"); 1169 } 1170 } 1171 1172 int 1173 main(int argc, char **argv) 1174 { 1175 char *cmdname; 1176 1177 if (argv[0] == NULL) 1178 log_errx(1, "NULL command name"); 1179 1180 cmdname = basename(argv[0]); 1181 1182 if (strcmp(cmdname, "automount") == 0) 1183 return (main_automount(argc, argv)); 1184 else if (strcmp(cmdname, "automountd") == 0) 1185 return (main_automountd(argc, argv)); 1186 else if (strcmp(cmdname, "autounmountd") == 0) 1187 return (main_autounmountd(argc, argv)); 1188 else 1189 log_errx(1, "binary name should be either \"automount\", " 1190 "\"automountd\", or \"autounmountd\""); 1191 } 1192