1270096Strasz/*-
2270096Strasz * Copyright (c) 2014 The FreeBSD Foundation
3270096Strasz * All rights reserved.
4270096Strasz *
5270096Strasz * This software was developed by Edward Tomasz Napierala under sponsorship
6270096Strasz * from the FreeBSD Foundation.
7270096Strasz *
8270096Strasz * Redistribution and use in source and binary forms, with or without
9270096Strasz * modification, are permitted provided that the following conditions
10270096Strasz * are met:
11270096Strasz * 1. Redistributions of source code must retain the above copyright
12270096Strasz *    notice, this list of conditions and the following disclaimer.
13270096Strasz * 2. Redistributions in binary form must reproduce the above copyright
14270096Strasz *    notice, this list of conditions and the following disclaimer in the
15270096Strasz *    documentation and/or other materials provided with the distribution.
16270096Strasz *
17270096Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18270096Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19270096Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20270096Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21270096Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22270096Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23270096Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24270096Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25270096Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26270096Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27270096Strasz * SUCH DAMAGE.
28270096Strasz *
29270096Strasz */
30270096Strasz
31270897Strasz#include <sys/cdefs.h>
32270897Strasz__FBSDID("$FreeBSD$");
33270897Strasz
34270096Strasz#include <sys/types.h>
35270096Strasz#include <sys/time.h>
36270096Strasz#include <sys/ioctl.h>
37270096Strasz#include <sys/param.h>
38270096Strasz#include <sys/linker.h>
39270096Strasz#include <sys/mount.h>
40270096Strasz#include <sys/socket.h>
41270096Strasz#include <sys/stat.h>
42270096Strasz#include <sys/wait.h>
43270096Strasz#include <sys/utsname.h>
44270096Strasz#include <assert.h>
45270096Strasz#include <ctype.h>
46270096Strasz#include <errno.h>
47270096Strasz#include <fcntl.h>
48270096Strasz#include <libgen.h>
49270096Strasz#include <netdb.h>
50270096Strasz#include <signal.h>
51270096Strasz#include <stdbool.h>
52270096Strasz#include <stdint.h>
53270096Strasz#include <stdio.h>
54270096Strasz#include <stdlib.h>
55270096Strasz#include <string.h>
56270096Strasz#include <unistd.h>
57270096Strasz
58270096Strasz#include <libutil.h>
59270096Strasz
60270096Strasz#include "autofs_ioctl.h"
61270096Strasz
62270096Strasz#include "common.h"
63270096Strasz
64270096Strasz#define AUTOMOUNTD_PIDFILE	"/var/run/automountd.pid"
65270096Strasz
66270096Straszstatic int nchildren = 0;
67270096Straszstatic int autofs_fd;
68270096Straszstatic int request_id;
69270096Strasz
70270096Straszstatic void
71270096Straszdone(int request_error)
72270096Strasz{
73270096Strasz	struct autofs_daemon_done add;
74270096Strasz	int error;
75270096Strasz
76270096Strasz	memset(&add, 0, sizeof(add));
77270096Strasz	add.add_id = request_id;
78270096Strasz	add.add_error = request_error;
79270096Strasz
80270096Strasz	log_debugx("completing request %d with error %d",
81270096Strasz	    request_id, request_error);
82270096Strasz
83270096Strasz	error = ioctl(autofs_fd, AUTOFSDONE, &add);
84270096Strasz	if (error != 0) {
85270096Strasz		/*
86270096Strasz		 * Do this instead of log_err() to avoid calling
87270096Strasz		 * done() again with error, from atexit handler.
88270096Strasz		 */
89270096Strasz		log_warn("AUTOFSDONE");
90270096Strasz	}
91270096Strasz	quick_exit(1);
92270096Strasz}
93270096Strasz
94270096Strasz/*
95270096Strasz * Remove "fstype=whatever" from optionsp and return the "whatever" part.
96270096Strasz */
97270096Straszstatic char *
98270096Straszpick_option(const char *option, char **optionsp)
99270096Strasz{
100270096Strasz	char *tofree, *pair, *newoptions;
101270096Strasz	char *picked = NULL;
102270096Strasz	bool first = true;
103270096Strasz
104270096Strasz	tofree = *optionsp;
105270096Strasz
106270096Strasz	newoptions = calloc(strlen(*optionsp) + 1, 1);
107270096Strasz	if (newoptions == NULL)
108270096Strasz		log_err(1, "calloc");
109270096Strasz
110270096Strasz	while ((pair = strsep(optionsp, ",")) != NULL) {
111270096Strasz		/*
112270096Strasz		 * XXX: strncasecmp(3) perhaps?
113270096Strasz		 */
114270096Strasz		if (strncmp(pair, option, strlen(option)) == 0) {
115270096Strasz			picked = checked_strdup(pair + strlen(option));
116270096Strasz		} else {
117270096Strasz			if (first == false)
118270096Strasz				strcat(newoptions, ",");
119270096Strasz			else
120270096Strasz				first = false;
121270096Strasz			strcat(newoptions, pair);
122270096Strasz		}
123270096Strasz	}
124270096Strasz
125270096Strasz	free(tofree);
126270096Strasz	*optionsp = newoptions;
127270096Strasz
128270096Strasz	return (picked);
129270096Strasz}
130270096Strasz
131270096Straszstatic void
132270096Straszcreate_subtree(const struct node *node, bool incomplete)
133270096Strasz{
134270096Strasz	const struct node *child;
135270096Strasz	char *path;
136270096Strasz	bool wildcard_found = false;
137270096Strasz
138270096Strasz	/*
139270096Strasz	 * Skip wildcard nodes.
140270096Strasz	 */
141270096Strasz	if (strcmp(node->n_key, "*") == 0)
142270096Strasz		return;
143270096Strasz
144270096Strasz	path = node_path(node);
145270096Strasz	log_debugx("creating subtree at %s", path);
146270096Strasz	create_directory(path);
147270096Strasz
148270096Strasz	if (incomplete) {
149270096Strasz		TAILQ_FOREACH(child, &node->n_children, n_next) {
150270096Strasz			if (strcmp(child->n_key, "*") == 0) {
151270096Strasz				wildcard_found = true;
152270096Strasz				break;
153270096Strasz			}
154270096Strasz		}
155270096Strasz
156270096Strasz		if (wildcard_found) {
157270096Strasz			log_debugx("node %s contains wildcard entry; "
158270096Strasz			    "not creating its subdirectories due to -d flag",
159270096Strasz			    path);
160270096Strasz			free(path);
161270096Strasz			return;
162270096Strasz		}
163270096Strasz	}
164270096Strasz
165270096Strasz	free(path);
166270096Strasz
167270096Strasz	TAILQ_FOREACH(child, &node->n_children, n_next)
168270096Strasz		create_subtree(child, incomplete);
169270096Strasz}
170270096Strasz
171270096Straszstatic void
172270096Straszexit_callback(void)
173270096Strasz{
174270096Strasz
175270096Strasz	done(EIO);
176270096Strasz}
177270096Strasz
178270096Straszstatic void
179270096Straszhandle_request(const struct autofs_daemon_request *adr, char *cmdline_options,
180270096Strasz    bool incomplete_hierarchy)
181270096Strasz{
182270096Strasz	const char *map;
183270096Strasz	struct node *root, *parent, *node;
184270096Strasz	FILE *f;
185270902Strasz	char *options, *fstype, *nobrowse, *retrycnt, *tmp;
186270096Strasz	int error;
187270096Strasz
188270096Strasz	log_debugx("got request %d: from %s, path %s, prefix \"%s\", "
189270096Strasz	    "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from,
190270096Strasz	    adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options);
191270096Strasz
192270096Strasz	/*
193270096Strasz	 * Try to notify the kernel about any problems.
194270096Strasz	 */
195270096Strasz	request_id = adr->adr_id;
196270096Strasz	atexit(exit_callback);
197270096Strasz
198270096Strasz	if (strncmp(adr->adr_from, "map ", 4) != 0) {
199270096Strasz		log_errx(1, "invalid mountfrom \"%s\"; failing request",
200270096Strasz		    adr->adr_from);
201270096Strasz	}
202270096Strasz
203270096Strasz	map = adr->adr_from + 4; /* 4 for strlen("map "); */
204270096Strasz	root = node_new_root();
205270096Strasz	if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) {
206270096Strasz		parent = root;
207270096Strasz	} else {
208270096Strasz		parent = node_new_map(root, checked_strdup(adr->adr_prefix),
209270096Strasz		    checked_strdup(adr->adr_options), checked_strdup(map),
210270096Strasz		    checked_strdup("[kernel request]"), lineno);
211270096Strasz	}
212270096Strasz	parse_map(parent, map, adr->adr_key[0] != '\0' ? adr->adr_key : NULL);
213270096Strasz	if (adr->adr_key[0] != '\0')
214270096Strasz		node_expand_wildcard(root, adr->adr_key);
215270096Strasz	node = node_find(root, adr->adr_path);
216270096Strasz	if (node == NULL) {
217270096Strasz		log_errx(1, "map %s does not contain key for \"%s\"; "
218270096Strasz		    "failing mount", map, adr->adr_path);
219270096Strasz	}
220270096Strasz
221270096Strasz	if (node->n_location == NULL) {
222270096Strasz		log_debugx("found node defined at %s:%d; not a mountpoint",
223270096Strasz		    node->n_config_file, node->n_config_line);
224270096Strasz
225270902Strasz		options = node_options(node);
226270902Strasz
227270096Strasz		/*
228270902Strasz		 * Prepend options passed via automountd(8) command line.
229270902Strasz		 */
230270902Strasz		if (cmdline_options != NULL) {
231270902Strasz			options =
232270902Strasz			    separated_concat(cmdline_options, options, ',');
233270902Strasz		}
234270902Strasz
235270902Strasz		nobrowse = pick_option("nobrowse", &options);
236270902Strasz		if (nobrowse != NULL && adr->adr_key[0] == '\0') {
237270902Strasz			log_debugx("skipping map %s due to \"nobrowse\" "
238270902Strasz			    "option; exiting", map);
239270902Strasz			done(0);
240270902Strasz
241270902Strasz			/*
242270902Strasz			 * Exit without calling exit_callback().
243270902Strasz			 */
244270902Strasz			quick_exit(0);
245270902Strasz		}
246270902Strasz
247270902Strasz		/*
248270096Strasz		 * Not a mountpoint; create directories in the autofs mount
249270096Strasz		 * and complete the request.
250270096Strasz		 */
251270096Strasz		create_subtree(node, incomplete_hierarchy);
252270096Strasz
253270096Strasz		if (incomplete_hierarchy && adr->adr_key[0] != '\0') {
254270096Strasz			/*
255270096Strasz			 * We still need to create the single subdirectory
256270096Strasz			 * user is trying to access.
257270096Strasz			 */
258270096Strasz			tmp = separated_concat(adr->adr_path,
259270096Strasz			    adr->adr_key, '/');
260270096Strasz			node = node_find(root, tmp);
261270096Strasz			if (node != NULL)
262270096Strasz				create_subtree(node, false);
263270096Strasz		}
264270096Strasz
265270096Strasz		log_debugx("nothing to mount; exiting");
266270902Strasz		done(0);
267270096Strasz
268270096Strasz		/*
269270096Strasz		 * Exit without calling exit_callback().
270270096Strasz		 */
271270096Strasz		quick_exit(0);
272270096Strasz	}
273270096Strasz
274270096Strasz	log_debugx("found node defined at %s:%d; it is a mountpoint",
275270096Strasz	    node->n_config_file, node->n_config_line);
276270096Strasz
277270096Strasz	node_expand_ampersand(node,
278270096Strasz	    adr->adr_key[0] != '\0' ? adr->adr_key : NULL);
279270096Strasz	error = node_expand_defined(node);
280270096Strasz	if (error != 0) {
281270096Strasz		log_errx(1, "variable expansion failed for %s; "
282270096Strasz		    "failing mount", adr->adr_path);
283270096Strasz	}
284270096Strasz
285270096Strasz	options = node_options(node);
286270096Strasz
287270096Strasz	/*
288270096Strasz	 * Prepend options passed via automountd(8) command line.
289270096Strasz	 */
290270096Strasz	if (cmdline_options != NULL)
291270096Strasz		options = separated_concat(cmdline_options, options, ',');
292270096Strasz
293270096Strasz	/*
294270096Strasz	 * Append "automounted".
295270096Strasz	 */
296270096Strasz	options = separated_concat(options, "automounted", ',');
297270096Strasz
298270096Strasz	/*
299270902Strasz	 * Remove "nobrowse", mount(8) doesn't understand it.
300270902Strasz	 */
301270902Strasz	pick_option("nobrowse", &options);
302270902Strasz
303270902Strasz	/*
304270096Strasz	 * Figure out fstype.
305270096Strasz	 */
306270096Strasz	fstype = pick_option("fstype=", &options);
307270096Strasz	if (fstype == NULL) {
308270096Strasz		log_debugx("fstype not specified in options; "
309270096Strasz		    "defaulting to \"nfs\"");
310270096Strasz		fstype = checked_strdup("nfs");
311270096Strasz	}
312270096Strasz
313270096Strasz	if (strcmp(fstype, "nfs") == 0) {
314270096Strasz		/*
315270096Strasz		 * The mount_nfs(8) command defaults to retry undefinitely.
316270096Strasz		 * We do not want that behaviour, because it leaves mount_nfs(8)
317270096Strasz		 * instances and automountd(8) children hanging forever.
318270096Strasz		 * Disable retries unless the option was passed explicitly.
319270096Strasz		 */
320270096Strasz		retrycnt = pick_option("retrycnt=", &options);
321270096Strasz		if (retrycnt == NULL) {
322270096Strasz			log_debugx("retrycnt not specified in options; "
323270096Strasz			    "defaulting to 1");
324270096Strasz			options = separated_concat(options,
325270096Strasz			    separated_concat("retrycnt", "1", '='), ',');
326270096Strasz		} else {
327270096Strasz			options = separated_concat(options,
328270096Strasz			    separated_concat("retrycnt", retrycnt, '='), ',');
329270096Strasz		}
330270096Strasz	}
331270096Strasz
332270096Strasz	f = auto_popen("mount", "-t", fstype, "-o", options,
333270096Strasz	    node->n_location, adr->adr_path, NULL);
334270096Strasz	assert(f != NULL);
335270096Strasz	error = auto_pclose(f);
336270096Strasz	if (error != 0)
337270096Strasz		log_errx(1, "mount failed");
338270096Strasz
339270902Strasz	log_debugx("mount done; exiting");
340270096Strasz	done(0);
341270096Strasz
342270096Strasz	/*
343270096Strasz	 * Exit without calling exit_callback().
344270096Strasz	 */
345270096Strasz	quick_exit(0);
346270096Strasz}
347270096Strasz
348270096Straszstatic int
349270096Straszwait_for_children(bool block)
350270096Strasz{
351270096Strasz	pid_t pid;
352270096Strasz	int status;
353270096Strasz	int num = 0;
354270096Strasz
355270096Strasz	for (;;) {
356270096Strasz		/*
357270096Strasz		 * If "block" is true, wait for at least one process.
358270096Strasz		 */
359270096Strasz		if (block && num == 0)
360270096Strasz			pid = wait4(-1, &status, 0, NULL);
361270096Strasz		else
362270096Strasz			pid = wait4(-1, &status, WNOHANG, NULL);
363270096Strasz		if (pid <= 0)
364270096Strasz			break;
365270096Strasz		if (WIFSIGNALED(status)) {
366270096Strasz			log_warnx("child process %d terminated with signal %d",
367270096Strasz			    pid, WTERMSIG(status));
368270096Strasz		} else if (WEXITSTATUS(status) != 0) {
369270096Strasz			log_warnx("child process %d terminated with exit status %d",
370270096Strasz			    pid, WEXITSTATUS(status));
371270096Strasz		} else {
372270096Strasz			log_debugx("child process %d terminated gracefully", pid);
373270096Strasz		}
374270096Strasz		num++;
375270096Strasz	}
376270096Strasz
377270096Strasz	return (num);
378270096Strasz}
379270096Strasz
380270096Straszstatic void
381270096Straszusage_automountd(void)
382270096Strasz{
383270096Strasz
384270096Strasz	fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]"
385270096Strasz	    "[-o opts][-Tidv]\n");
386270096Strasz	exit(1);
387270096Strasz}
388270096Strasz
389270096Straszint
390270096Straszmain_automountd(int argc, char **argv)
391270096Strasz{
392270096Strasz	struct pidfh *pidfh;
393270096Strasz	pid_t pid, otherpid;
394270096Strasz	const char *pidfile_path = AUTOMOUNTD_PIDFILE;
395270096Strasz	char *options = NULL;
396270096Strasz	struct autofs_daemon_request request;
397270096Strasz	int ch, debug = 0, error, maxproc = 30, retval, saved_errno;
398270096Strasz	bool dont_daemonize = false, incomplete_hierarchy = false;
399270096Strasz
400270096Strasz	defined_init();
401270096Strasz
402270096Strasz	while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) {
403270096Strasz		switch (ch) {
404270096Strasz		case 'D':
405270096Strasz			defined_parse_and_add(optarg);
406270096Strasz			break;
407270096Strasz		case 'T':
408270096Strasz			/*
409270096Strasz			 * For compatibility with other implementations,
410270096Strasz			 * such as OS X.
411270096Strasz			 */
412270096Strasz			debug++;
413270096Strasz			break;
414270096Strasz		case 'd':
415270096Strasz			dont_daemonize = true;
416270096Strasz			debug++;
417270096Strasz			break;
418270096Strasz		case 'i':
419270096Strasz			incomplete_hierarchy = true;
420270096Strasz			break;
421270096Strasz		case 'm':
422270096Strasz			maxproc = atoi(optarg);
423270096Strasz			break;
424270096Strasz		case 'o':
425270096Strasz			if (options == NULL) {
426270096Strasz				options = checked_strdup(optarg);
427270096Strasz			} else {
428270096Strasz				options =
429270096Strasz				    separated_concat(options, optarg, ',');
430270096Strasz			}
431270096Strasz			break;
432270096Strasz		case 'v':
433270096Strasz			debug++;
434270096Strasz			break;
435270096Strasz		case '?':
436270096Strasz		default:
437270096Strasz			usage_automountd();
438270096Strasz		}
439270096Strasz	}
440270096Strasz	argc -= optind;
441270096Strasz	if (argc != 0)
442270096Strasz		usage_automountd();
443270096Strasz
444270096Strasz	log_init(debug);
445270096Strasz
446270096Strasz	pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
447270096Strasz	if (pidfh == NULL) {
448270096Strasz		if (errno == EEXIST) {
449270096Strasz			log_errx(1, "daemon already running, pid: %jd.",
450270096Strasz			    (intmax_t)otherpid);
451270096Strasz		}
452270096Strasz		log_err(1, "cannot open or create pidfile \"%s\"",
453270096Strasz		    pidfile_path);
454270096Strasz	}
455270096Strasz
456270096Strasz	autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
457270096Strasz	if (autofs_fd < 0 && errno == ENOENT) {
458270096Strasz		saved_errno = errno;
459270096Strasz		retval = kldload("autofs");
460270096Strasz		if (retval != -1)
461270096Strasz			autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC);
462270096Strasz		else
463270096Strasz			errno = saved_errno;
464270096Strasz	}
465270096Strasz	if (autofs_fd < 0)
466270096Strasz		log_err(1, "failed to open %s", AUTOFS_PATH);
467270096Strasz
468270096Strasz	if (dont_daemonize == false) {
469270096Strasz		if (daemon(0, 0) == -1) {
470270096Strasz			log_warn("cannot daemonize");
471270096Strasz			pidfile_remove(pidfh);
472270096Strasz			exit(1);
473270096Strasz		}
474270096Strasz	} else {
475270096Strasz		lesser_daemon();
476270096Strasz	}
477270096Strasz
478270096Strasz	pidfile_write(pidfh);
479270096Strasz
480270096Strasz	for (;;) {
481270096Strasz		log_debugx("waiting for request from the kernel");
482270096Strasz
483270096Strasz		memset(&request, 0, sizeof(request));
484270096Strasz		error = ioctl(autofs_fd, AUTOFSREQUEST, &request);
485270096Strasz		if (error != 0) {
486270096Strasz			if (errno == EINTR) {
487270096Strasz				nchildren -= wait_for_children(false);
488270096Strasz				assert(nchildren >= 0);
489270096Strasz				continue;
490270096Strasz			}
491270096Strasz
492270096Strasz			log_err(1, "AUTOFSREQUEST");
493270096Strasz		}
494270096Strasz
495270096Strasz		if (dont_daemonize) {
496270096Strasz			log_debugx("not forking due to -d flag; "
497270096Strasz			    "will exit after servicing a single request");
498270096Strasz		} else {
499270096Strasz			nchildren -= wait_for_children(false);
500270096Strasz			assert(nchildren >= 0);
501270096Strasz
502270096Strasz			while (maxproc > 0 && nchildren >= maxproc) {
503270096Strasz				log_debugx("maxproc limit of %d child processes hit; "
504270096Strasz				    "waiting for child process to exit", maxproc);
505270096Strasz				nchildren -= wait_for_children(true);
506270096Strasz				assert(nchildren >= 0);
507270096Strasz			}
508270096Strasz			log_debugx("got request; forking child process #%d",
509270096Strasz			    nchildren);
510270096Strasz			nchildren++;
511270096Strasz
512270096Strasz			pid = fork();
513270096Strasz			if (pid < 0)
514270096Strasz				log_err(1, "fork");
515270096Strasz			if (pid > 0)
516270096Strasz				continue;
517270096Strasz		}
518270096Strasz
519270096Strasz		pidfile_close(pidfh);
520270096Strasz		handle_request(&request, options, incomplete_hierarchy);
521270096Strasz	}
522270096Strasz
523270096Strasz	pidfile_close(pidfh);
524270096Strasz
525270096Strasz	return (0);
526270096Strasz}
527270096Strasz
528