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(&copy, "/");
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