]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/autofs/common.c
Update releng/11.3 to RC3 as part of the 11.3-RELEASE cycle.
[FreeBSD/FreeBSD.git] / usr.sbin / autofs / common.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2014 The FreeBSD Foundation
5  * All rights reserved.
6  *
7  * This software was developed by Edward Tomasz Napierala under sponsorship
8  * from the FreeBSD Foundation.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  */
32
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35
36 #include <sys/types.h>
37 #include <sys/time.h>
38 #include <sys/ioctl.h>
39 #include <sys/param.h>
40 #include <sys/linker.h>
41 #include <sys/mount.h>
42 #include <sys/socket.h>
43 #include <sys/stat.h>
44 #include <sys/wait.h>
45 #include <sys/utsname.h>
46 #include <assert.h>
47 #include <ctype.h>
48 #include <err.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <libgen.h>
52 #include <libutil.h>
53 #include <netdb.h>
54 #include <paths.h>
55 #include <signal.h>
56 #include <stdbool.h>
57 #include <stdint.h>
58 #define _WITH_GETLINE
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <unistd.h>
63
64 #include "autofs_ioctl.h"
65
66 #include "common.h"
67
68 extern FILE *yyin;
69 extern char *yytext;
70 extern int yylex(void);
71
72 static void     parse_master_yyin(struct node *root, const char *master);
73 static void     parse_map_yyin(struct node *parent, const char *map,
74                     const char *executable_key);
75
76 char *
77 checked_strdup(const char *s)
78 {
79         char *c;
80
81         assert(s != NULL);
82
83         c = strdup(s);
84         if (c == NULL)
85                 log_err(1, "strdup");
86         return (c);
87 }
88
89 /*
90  * Concatenate two strings, inserting separator between them, unless not needed.
91  */
92 char *
93 concat(const char *s1, char separator, const char *s2)
94 {
95         char *result;
96         char s1last, s2first;
97         int ret;
98
99         if (s1 == NULL)
100                 s1 = "";
101         if (s2 == NULL)
102                 s2 = "";
103
104         if (s1[0] == '\0')
105                 s1last = '\0';
106         else
107                 s1last = s1[strlen(s1) - 1];
108
109         s2first = s2[0];
110
111         if (s1last == separator && s2first == separator) {
112                 /*
113                  * If s1 ends with the separator and s2 begins with
114                  * it - skip the latter; otherwise concatenating "/"
115                  * and "/foo" would end up returning "//foo".
116                  */
117                 ret = asprintf(&result, "%s%s", s1, s2 + 1);
118         } else if (s1last == separator || s2first == separator ||
119             s1[0] == '\0' || s2[0] == '\0') {
120                 ret = asprintf(&result, "%s%s", s1, s2);
121         } else {
122                 ret = asprintf(&result, "%s%c%s", s1, separator, s2);
123         }
124         if (ret < 0)
125                 log_err(1, "asprintf");
126
127         //log_debugx("%s: got %s and %s, returning %s", __func__, s1, s2, result);
128
129         return (result);
130 }
131
132 void
133 create_directory(const char *path)
134 {
135         char *component, *copy, *tofree, *partial, *tmp;
136         int error;
137
138         assert(path[0] == '/');
139
140         /*
141          * +1 to skip the leading slash.
142          */
143         copy = tofree = checked_strdup(path + 1);
144
145         partial = checked_strdup("");
146         for (;;) {
147                 component = strsep(&copy, "/");
148                 if (component == NULL)
149                         break;
150                 tmp = concat(partial, '/', component);
151                 free(partial);
152                 partial = tmp;
153                 //log_debugx("creating \"%s\"", partial);
154                 error = mkdir(partial, 0755);
155                 if (error != 0 && errno != EEXIST) {
156                         log_warn("cannot create %s", partial);
157                         return;
158                 }
159         }
160
161         free(tofree);
162 }
163
164 struct node *
165 node_new_root(void)
166 {
167         struct node *n;
168
169         n = calloc(1, sizeof(*n));
170         if (n == NULL)
171                 log_err(1, "calloc");
172         // XXX
173         n->n_key = checked_strdup("/");
174         n->n_options = checked_strdup("");
175
176         TAILQ_INIT(&n->n_children);
177
178         return (n);
179 }
180
181 struct node *
182 node_new(struct node *parent, char *key, char *options, char *location,
183     const char *config_file, int config_line)
184 {
185         struct node *n;
186
187         n = calloc(1, sizeof(*n));
188         if (n == NULL)
189                 log_err(1, "calloc");
190
191         TAILQ_INIT(&n->n_children);
192         assert(key != NULL);
193         assert(key[0] != '\0');
194         n->n_key = key;
195         if (options != NULL)
196                 n->n_options = options;
197         else
198                 n->n_options = strdup("");
199         n->n_location = location;
200         assert(config_file != NULL);
201         n->n_config_file = config_file;
202         assert(config_line >= 0);
203         n->n_config_line = config_line;
204
205         assert(parent != NULL);
206         n->n_parent = parent;
207         TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);
208
209         return (n);
210 }
211
212 struct node *
213 node_new_map(struct node *parent, char *key, char *options, char *map,
214     const char *config_file, int config_line)
215 {
216         struct node *n;
217
218         n = calloc(1, sizeof(*n));
219         if (n == NULL)
220                 log_err(1, "calloc");
221
222         TAILQ_INIT(&n->n_children);
223         assert(key != NULL);
224         assert(key[0] != '\0');
225         n->n_key = key;
226         if (options != NULL)
227                 n->n_options = options;
228         else
229                 n->n_options = strdup("");
230         n->n_map = map;
231         assert(config_file != NULL);
232         n->n_config_file = config_file;
233         assert(config_line >= 0);
234         n->n_config_line = config_line;
235
236         assert(parent != NULL);
237         n->n_parent = parent;
238         TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);
239
240         return (n);
241 }
242
243 static struct node *
244 node_duplicate(const struct node *o, struct node *parent)
245 {
246         const struct node *child;
247         struct node *n;
248
249         if (parent == NULL)
250                 parent = o->n_parent;
251
252         n = node_new(parent, o->n_key, o->n_options, o->n_location,
253             o->n_config_file, o->n_config_line);
254
255         TAILQ_FOREACH(child, &o->n_children, n_next)
256                 node_duplicate(child, n);
257
258         return (n);
259 }
260
261 static void
262 node_delete(struct node *n)
263 {
264         struct node *child, *tmp;
265
266         assert (n != NULL);
267
268         TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp)
269                 node_delete(child);
270
271         if (n->n_parent != NULL)
272                 TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);
273
274         free(n);
275 }
276
277 /*
278  * Move (reparent) node 'n' to make it sibling of 'previous', placed
279  * just after it.
280  */
281 static void
282 node_move_after(struct node *n, struct node *previous)
283 {
284
285         TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);
286         n->n_parent = previous->n_parent;
287         TAILQ_INSERT_AFTER(&previous->n_parent->n_children, previous, n, n_next);
288 }
289
290 static void
291 node_expand_includes(struct node *root, bool is_master)
292 {
293         struct node *n, *n2, *tmp, *tmp2, *tmproot;
294         int error;
295
296         TAILQ_FOREACH_SAFE(n, &root->n_children, n_next, tmp) {
297                 if (n->n_key[0] != '+')
298                         continue;
299
300                 error = access(AUTO_INCLUDE_PATH, F_OK);
301                 if (error != 0) {
302                         log_errx(1, "directory services not configured; "
303                             "%s does not exist", AUTO_INCLUDE_PATH);
304                 }
305
306                 /*
307                  * "+1" to skip leading "+".
308                  */
309                 yyin = auto_popen(AUTO_INCLUDE_PATH, n->n_key + 1, NULL);
310                 assert(yyin != NULL);
311
312                 tmproot = node_new_root();
313                 if (is_master)
314                         parse_master_yyin(tmproot, n->n_key);
315                 else
316                         parse_map_yyin(tmproot, n->n_key, NULL);
317
318                 error = auto_pclose(yyin);
319                 yyin = NULL;
320                 if (error != 0) {
321                         log_errx(1, "failed to handle include \"%s\"",
322                             n->n_key);
323                 }
324
325                 /*
326                  * Entries to be included are now in tmproot.  We need to merge
327                  * them with the rest, preserving their place and ordering.
328                  */
329                 TAILQ_FOREACH_REVERSE_SAFE(n2,
330                     &tmproot->n_children, nodehead, n_next, tmp2) {
331                         node_move_after(n2, n);
332                 }
333
334                 node_delete(n);
335                 node_delete(tmproot);
336         }
337 }
338
339 static char *
340 expand_ampersand(char *string, const char *key)
341 {
342         char c, *expanded;
343         int i, ret, before_len = 0;
344         bool backslashed = false;
345
346         assert(key[0] != '\0');
347
348         expanded = checked_strdup(string);
349
350         for (i = 0; string[i] != '\0'; i++) {
351                 c = string[i];
352                 if (c == '\\' && backslashed == false) {
353                         backslashed = true;
354                         continue;
355                 }
356                 if (backslashed) {
357                         backslashed = false;
358                         continue;
359                 }
360                 backslashed = false;
361                 if (c != '&')
362                         continue;
363
364                 /*
365                  * The 'before_len' variable contains the number
366                  * of characters before the '&'.
367                  */
368                 before_len = i;
369                 //assert(i + 1 < (int)strlen(string));
370
371                 ret = asprintf(&expanded, "%.*s%s%s",
372                     before_len, string, key, string + before_len + 1);
373                 if (ret < 0)
374                         log_err(1, "asprintf");
375
376                 //log_debugx("\"%s\" expanded with key \"%s\" to \"%s\"",
377                 //    string, key, expanded);
378
379                 /*
380                  * Figure out where to start searching for next variable.
381                  */
382                 string = expanded;
383                 i = before_len + strlen(key);
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  */
399 void
400 node_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  */
423 void
424 node_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
440 int
441 node_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
464 static bool
465 node_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
476 bool
477 node_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
490 bool
491 node_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
503 static void
504 node_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
535 static void
536 node_expand_direct_maps(struct node *n)
537 {
538
539         node_expand_maps(n, false);
540 }
541
542 void
543 node_expand_indirect_maps(struct node *n)
544 {
545
546         node_expand_maps(n, true);
547 }
548
549 static char *
550 node_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  */
575 char *
576 node_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
593 static char *
594 node_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  */
612 char *
613 node_options(const struct node *n)
614 {
615
616         return (node_options_x(n, checked_strdup("")));
617 }
618
619 static void
620 node_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  */
668 void
669 node_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
677 static struct node *
678 node_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
716 struct node *
717 node_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  */
742 static void
743 parse_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;
884 again:
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  */
903 static void
904 parse_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
939 static bool
940 file_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  */
957 static void
958 parse_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  */
1000 static void
1001 parse_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
1028 void
1029 parse_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
1113 static void
1114 parse_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
1155 void
1156 parse_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  */
1181 void
1182 lesser_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 int
1208 main(int argc, char **argv)
1209 {
1210         char *cmdname;
1211
1212         if (argv[0] == NULL)
1213                 log_errx(1, "NULL command name");
1214
1215         cmdname = basename(argv[0]);
1216
1217         if (strcmp(cmdname, "automount") == 0)
1218                 return (main_automount(argc, argv));
1219         else if (strcmp(cmdname, "automountd") == 0)
1220                 return (main_automountd(argc, argv));
1221         else if (strcmp(cmdname, "autounmountd") == 0)
1222                 return (main_autounmountd(argc, argv));
1223         else
1224                 log_errx(1, "binary name should be either \"automount\", "
1225                     "\"automountd\", or \"autounmountd\"");
1226 }