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