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