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