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