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