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