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