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