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