1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2014 The FreeBSD Foundation
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/time.h>
34#include <sys/ioctl.h>
35#include <sys/param.h>
36#include <sys/linker.h>
37#include <sys/mount.h>
38#include <sys/socket.h>
39#include <sys/stat.h>
40#include <sys/wait.h>
41#include <sys/utsname.h>
42#include <assert.h>
43#include <ctype.h>
44#include <errno.h>
45#include <fcntl.h>
46#include <libgen.h>
47#include <libutil.h>
48#include <netdb.h>
49#include <signal.h>
50#include <stdbool.h>
51#include <stdint.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <unistd.h>
56
57#include "autofs_ioctl.h"
58
59#include "common.h"
60
61#define AUTOMOUNTD_PIDFILE	"/var/run/automountd.pid"
62
63static int nchildren = 0;
64static int autofs_fd;
65static int request_id;
66
67static void
68done(int request_error, bool wildcards)
69{
70	struct autofs_daemon_done add;
71	int error;
72
73	memset(&add, 0, sizeof(add));
74	add.add_id = request_id;
75	add.add_wildcards = wildcards;
76	add.add_error = request_error;
77
78	log_debugx("completing request %d with error %d",
79	    request_id, request_error);
80
81	error = ioctl(autofs_fd, AUTOFSDONE, &add);
82	if (error != 0)
83		log_warn("AUTOFSDONE");
84}
85
86/*
87 * Remove "fstype=whatever" from optionsp and return the "whatever" part.
88 */
89static char *
90pick_option(const char *option, char **optionsp)
91{
92	char *tofree, *pair, *newoptions;
93	char *picked = NULL;
94	bool first = true;
95
96	tofree = *optionsp;
97
98	newoptions = calloc(1, strlen(*optionsp) + 1);
99	if (newoptions == NULL)
100		log_err(1, "calloc");
101
102	while ((pair = strsep(optionsp, ",")) != NULL) {
103		/*
104		 * XXX: strncasecmp(3) perhaps?
105		 */
106		if (strncmp(pair, option, strlen(option)) == 0) {
107			picked = checked_strdup(pair + strlen(option));
108		} else {
109			if (first == false)
110				strcat(newoptions, ",");
111			else
112				first = false;
113			strcat(newoptions, pair);
114		}
115	}
116
117	free(tofree);
118	*optionsp = newoptions;
119
120	return (picked);
121}
122
123static void
124create_subtree(const struct node *node, bool incomplete)
125{
126	const struct node *child;
127	char *path;
128	bool wildcard_found = false;
129
130	/*
131	 * Skip wildcard nodes.
132	 */
133	if (strcmp(node->n_key, "*") == 0)
134		return;
135
136	path = node_path(node);
137	log_debugx("creating subtree at %s", path);
138	create_directory(path);
139
140	if (incomplete) {
141		TAILQ_FOREACH(child, &node->n_children, n_next) {
142			if (strcmp(child->n_key, "*") == 0) {
143				wildcard_found = true;
144				break;
145			}
146		}
147
148		if (wildcard_found) {
149			log_debugx("node %s contains wildcard entry; "
150			    "not creating its subdirectories due to -d flag",
151			    path);
152			free(path);
153			return;
154		}
155	}
156
157	free(path);
158
159	TAILQ_FOREACH(child, &node->n_children, n_next)
160		create_subtree(child, incomplete);
161}
162
163static void
164exit_callback(void)
165{
166
167	done(EIO, true);
168}
169
170static void
171handle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
172    bool incomplete_hierarchy)
173{
174	const char *map;
175	struct node *root, *parent, *node;
176	FILE *f;
177	char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp;
178	int error;
179	bool wildcards;
180
181	log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
182	    "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
183	    adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
184
185	/*
186	 * Try to notify the kernel about any problems.
187	 */
188	request_id = adr->adr_id;
189	atexit(exit_callback);
190
191	if (strncmp(adr->adr_from, "map ", 4) != 0) {
192		log_errx(1, "invalid mountfrom \"%s\"; failing request",
193		    adr->adr_from);
194	}
195
196	map = adr->adr_from + 4; /* 4 for strlen("map "); */
197	root = node_new_root();
198	if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
199		/*
200		 * Direct map.  autofs(4) doesn't have a way to determine
201		 * correct map key, but since it's a direct map, we can just
202		 * use adr_path instead.
203		 */
204		parent = root;
205		key = checked_strdup(adr->adr_path);
206	} else {
207		/*
208		 * Indirect map.
209		 */
210		parent = node_new_map(root, checked_strdup(adr->adr_prefix),
211		    NULL,  checked_strdup(map),
212		    checked_strdup("[kernel request]"), lineno);
213
214		if (adr->adr_key[0] == '\0')
215			key = NULL;
216		else
217			key = checked_strdup(adr->adr_key);
218	}
219
220	/*
221	 * "Wildcards" here actually means "make autofs(4) request
222	 * automountd(8) action if the node being looked up does not
223	 * exist, even though the parent is marked as cached".  This
224	 * needs to be done for maps with wildcard entries, but also
225	 * for special and executable maps.
226	 */
227	parse_map(parent, map, key, &wildcards);
228	if (!wildcards)
229		wildcards = node_has_wildcards(parent);
230	if (wildcards)
231		log_debugx("map may contain wildcard entries");
232	else
233		log_debugx("map does not contain wildcard entries");
234
235	if (key != NULL)
236		node_expand_wildcard(root, key);
237
238	node = node_find(root, adr->adr_path);
239	if (node == NULL) {
240		log_errx(1, "map %s does not contain key for \"%s\"; "
241		    "failing mount", map, adr->adr_path);
242	}
243
244	options = node_options(node);
245
246	/*
247	 * Append options from auto_master.
248	 */
249	options = concat(options, ',', adr->adr_options);
250
251	/*
252	 * Prepend options passed via automountd(8) command line.
253	 */
254	options = concat(cmdline_options, ',', options);
255
256	if (node->n_location == NULL) {
257		log_debugx("found node defined at %s:%d; not a mountpoint",
258		    node->n_config_file, node->n_config_line);
259
260		nobrowse = pick_option("nobrowse", &options);
261		if (nobrowse != NULL && key == NULL) {
262			log_debugx("skipping map %s due to \"nobrowse\" "
263			    "option; exiting", map);
264			done(0, true);
265
266			/*
267			 * Exit without calling exit_callback().
268			 */
269			quick_exit(0);
270		}
271
272		/*
273		 * Not a mountpoint; create directories in the autofs mount
274		 * and complete the request.
275		 */
276		create_subtree(node, incomplete_hierarchy);
277
278		if (incomplete_hierarchy && key != NULL) {
279			/*
280			 * We still need to create the single subdirectory
281			 * user is trying to access.
282			 */
283			tmp = concat(adr->adr_path, '/', key);
284			node = node_find(root, tmp);
285			if (node != NULL)
286				create_subtree(node, false);
287		}
288
289		log_debugx("nothing to mount; exiting");
290		done(0, wildcards);
291
292		/*
293		 * Exit without calling exit_callback().
294		 */
295		quick_exit(0);
296	}
297
298	log_debugx("found node defined at %s:%d; it is a mountpoint",
299	    node->n_config_file, node->n_config_line);
300
301	if (key != NULL)
302		node_expand_ampersand(node, key);
303	error = node_expand_defined(node);
304	if (error != 0) {
305		log_errx(1, "variable expansion failed for %s; "
306		    "failing mount", adr->adr_path);
307	}
308
309	/*
310	 * Append "automounted".
311	 */
312	options = concat(options, ',', "automounted");
313
314	/*
315	 * Remove "nobrowse", mount(8) doesn't understand it.
316	 */
317	pick_option("nobrowse", &options);
318
319	/*
320	 * Figure out fstype.
321	 */
322	fstype = pick_option("fstype=", &options);
323	if (fstype == NULL) {
324		log_debugx("fstype not specified in options; "
325		    "defaulting to \"nfs\"");
326		fstype = checked_strdup("nfs");
327	}
328
329	if (strcmp(fstype, "nfs") == 0) {
330		/*
331		 * The mount_nfs(8) command defaults to retry undefinitely.
332		 * We do not want that behaviour, because it leaves mount_nfs(8)
333		 * instances and automountd(8) children hanging forever.
334		 * Disable retries unless the option was passed explicitly.
335		 */
336		retrycnt = pick_option("retrycnt=", &options);
337		if (retrycnt == NULL) {
338			log_debugx("retrycnt not specified in options; "
339			    "defaulting to 1");
340			options = concat(options, ',', "retrycnt=1");
341		} else {
342			options = concat(options, ',',
343			    concat("retrycnt", '=', retrycnt));
344		}
345	}
346
347	f = auto_popen("mount", "-t", fstype, "-o", options,
348	    node->n_location, adr->adr_path, NULL);
349	assert(f != NULL);
350	error = auto_pclose(f);
351	if (error != 0)
352		log_errx(1, "mount failed");
353
354	log_debugx("mount done; exiting");
355	done(0, wildcards);
356
357	/*
358	 * Exit without calling exit_callback().
359	 */
360	quick_exit(0);
361}
362
363static void
364sigchld_handler(int dummy __unused)
365{
366
367	/*
368	 * The only purpose of this handler is to make SIGCHLD
369	 * interrupt the AUTOFSREQUEST ioctl(2), so we can call
370	 * wait_for_children().
371	 */
372}
373
374static void
375register_sigchld(void)
376{
377	struct sigaction sa;
378	int error;
379
380	bzero(&sa, sizeof(sa));
381	sa.sa_handler = sigchld_handler;
382	sigfillset(&sa.sa_mask);
383	error = sigaction(SIGCHLD, &sa, NULL);
384	if (error != 0)
385		log_err(1, "sigaction");
386
387}
388
389
390static int
391wait_for_children(bool block)
392{
393	pid_t pid;
394	int status;
395	int num = 0;
396
397	for (;;) {
398		/*
399		 * If "block" is true, wait for at least one process.
400		 */
401		if (block && num == 0)
402			pid = wait4(-1, &status, 0, NULL);
403		else
404			pid = wait4(-1, &status, WNOHANG, NULL);
405		if (pid <= 0)
406			break;
407		if (WIFSIGNALED(status)) {
408			log_warnx("child process %d terminated with signal %d",
409			    pid, WTERMSIG(status));
410		} else if (WEXITSTATUS(status) != 0) {
411			log_debugx("child process %d terminated with exit status %d",
412			    pid, WEXITSTATUS(status));
413		} else {
414			log_debugx("child process %d terminated gracefully", pid);
415		}
416		num++;
417	}
418
419	return (num);
420}
421
422static void
423usage_automountd(void)
424{
425
426	fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
427	    "[-o opts][-Tidv]\n");
428	exit(1);
429}
430
431int
432main_automountd(int argc, char **argv)
433{
434	struct pidfh *pidfh;
435	pid_t pid, otherpid;
436	const char *pidfile_path = AUTOMOUNTD_PIDFILE;
437	char *options = NULL;
438	struct autofs_daemon_request request;
439	int ch, debug = 0, error, maxproc = 30, retval, saved_errno;
440	bool dont_daemonize = false, incomplete_hierarchy = false;
441
442	defined_init();
443
444	while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
445		switch (ch) {
446		case 'D':
447			defined_parse_and_add(optarg);
448			break;
449		case 'T':
450			/*
451			 * For compatibility with other implementations,
452			 * such as OS X.
453			 */
454			debug++;
455			break;
456		case 'd':
457			dont_daemonize = true;
458			debug++;
459			break;
460		case 'i':
461			incomplete_hierarchy = true;
462			break;
463		case 'm':
464			maxproc = atoi(optarg);
465			break;
466		case 'o':
467			options = concat(options, ',', optarg);
468			break;
469		case 'v':
470			debug++;
471			break;
472		case '?':
473		default:
474			usage_automountd();
475		}
476	}
477	argc -= optind;
478	if (argc != 0)
479		usage_automountd();
480
481	log_init(debug);
482
483	pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
484	if (pidfh == NULL) {
485		if (errno == EEXIST) {
486			log_errx(1, "daemon already running, pid: %jd.",
487			    (intmax_t)otherpid);
488		}
489		log_err(1, "cannot open or create pidfile \"%s\"",
490		    pidfile_path);
491	}
492
493	autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
494	if (autofs_fd < 0 && errno == ENOENT) {
495		saved_errno = errno;
496		retval = kldload("autofs");
497		if (retval != -1)
498			autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
499		else
500			errno = saved_errno;
501	}
502	if (autofs_fd < 0)
503		log_err(1, "failed to open %s", AUTOFS_PATH);
504
505	if (dont_daemonize == false) {
506		if (daemon(0, 0) == -1) {
507			log_warn("cannot daemonize");
508			pidfile_remove(pidfh);
509			exit(1);
510		}
511	} else {
512		lesser_daemon();
513	}
514
515	pidfile_write(pidfh);
516
517	register_sigchld();
518
519	for (;;) {
520		log_debugx("waiting for request from the kernel");
521
522		memset(&request, 0, sizeof(request));
523		error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
524		if (error != 0) {
525			if (errno == EINTR) {
526				nchildren -= wait_for_children(false);
527				assert(nchildren >= 0);
528				continue;
529			}
530
531			log_err(1, "AUTOFSREQUEST");
532		}
533
534		if (dont_daemonize) {
535			log_debugx("not forking due to -d flag; "
536			    "will exit after servicing a single request");
537		} else {
538			nchildren -= wait_for_children(false);
539			assert(nchildren >= 0);
540
541			while (maxproc > 0 && nchildren >= maxproc) {
542				log_debugx("maxproc limit of %d child processes hit; "
543				    "waiting for child process to exit", maxproc);
544				nchildren -= wait_for_children(true);
545				assert(nchildren >= 0);
546			}
547			log_debugx("got request; forking child process #%d",
548			    nchildren);
549			nchildren++;
550
551			pid = fork();
552			if (pid < 0)
553				log_err(1, "fork");
554			if (pid > 0)
555				continue;
556		}
557
558		pidfile_close(pidfh);
559		handle_request(&request, options, incomplete_hierarchy);
560	}
561
562	pidfile_close(pidfh);
563
564	return (0);
565}
566
567