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