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: releng/10.3/usr.sbin/autofs/automountd.c 283241 2015-05-21 13:41:08Z trasz $"); 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 71279741Straszdone(int request_error, bool wildcards) 72270096Strasz{ 73270096Strasz struct autofs_daemon_done add; 74270096Strasz int error; 75270096Strasz 76270096Strasz memset(&add, 0, sizeof(add)); 77270096Strasz add.add_id = request_id; 78279741Strasz add.add_wildcards = wildcards; 79270096Strasz add.add_error = request_error; 80270096Strasz 81270096Strasz log_debugx("completing request %d with error %d", 82270096Strasz request_id, request_error); 83270096Strasz 84270096Strasz error = ioctl(autofs_fd, AUTOFSDONE, &add); 85279743Strasz if (error != 0) 86270096Strasz log_warn("AUTOFSDONE"); 87270096Strasz} 88270096Strasz 89270096Strasz/* 90270096Strasz * Remove "fstype=whatever" from optionsp and return the "whatever" part. 91270096Strasz */ 92270096Straszstatic char * 93270096Straszpick_option(const char *option, char **optionsp) 94270096Strasz{ 95270096Strasz char *tofree, *pair, *newoptions; 96270096Strasz char *picked = NULL; 97270096Strasz bool first = true; 98270096Strasz 99270096Strasz tofree = *optionsp; 100270096Strasz 101270096Strasz newoptions = calloc(strlen(*optionsp) + 1, 1); 102270096Strasz if (newoptions == NULL) 103270096Strasz log_err(1, "calloc"); 104270096Strasz 105270096Strasz while ((pair = strsep(optionsp, ",")) != NULL) { 106270096Strasz /* 107270096Strasz * XXX: strncasecmp(3) perhaps? 108270096Strasz */ 109270096Strasz if (strncmp(pair, option, strlen(option)) == 0) { 110270096Strasz picked = checked_strdup(pair + strlen(option)); 111270096Strasz } else { 112270096Strasz if (first == false) 113270096Strasz strcat(newoptions, ","); 114270096Strasz else 115270096Strasz first = false; 116270096Strasz strcat(newoptions, pair); 117270096Strasz } 118270096Strasz } 119270096Strasz 120270096Strasz free(tofree); 121270096Strasz *optionsp = newoptions; 122270096Strasz 123270096Strasz return (picked); 124270096Strasz} 125270096Strasz 126270096Straszstatic void 127270096Straszcreate_subtree(const struct node *node, bool incomplete) 128270096Strasz{ 129270096Strasz const struct node *child; 130270096Strasz char *path; 131270096Strasz bool wildcard_found = false; 132270096Strasz 133270096Strasz /* 134270096Strasz * Skip wildcard nodes. 135270096Strasz */ 136270096Strasz if (strcmp(node->n_key, "*") == 0) 137270096Strasz return; 138270096Strasz 139270096Strasz path = node_path(node); 140270096Strasz log_debugx("creating subtree at %s", path); 141270096Strasz create_directory(path); 142270096Strasz 143270096Strasz if (incomplete) { 144270096Strasz TAILQ_FOREACH(child, &node->n_children, n_next) { 145270096Strasz if (strcmp(child->n_key, "*") == 0) { 146270096Strasz wildcard_found = true; 147270096Strasz break; 148270096Strasz } 149270096Strasz } 150270096Strasz 151270096Strasz if (wildcard_found) { 152270096Strasz log_debugx("node %s contains wildcard entry; " 153270096Strasz "not creating its subdirectories due to -d flag", 154270096Strasz path); 155270096Strasz free(path); 156270096Strasz return; 157270096Strasz } 158270096Strasz } 159270096Strasz 160270096Strasz free(path); 161270096Strasz 162270096Strasz TAILQ_FOREACH(child, &node->n_children, n_next) 163270096Strasz create_subtree(child, incomplete); 164270096Strasz} 165270096Strasz 166270096Straszstatic void 167270096Straszexit_callback(void) 168270096Strasz{ 169270096Strasz 170279741Strasz done(EIO, true); 171270096Strasz} 172270096Strasz 173270096Straszstatic void 174270096Straszhandle_request(const struct autofs_daemon_request *adr, char *cmdline_options, 175270096Strasz bool incomplete_hierarchy) 176270096Strasz{ 177270096Strasz const char *map; 178270096Strasz struct node *root, *parent, *node; 179270096Strasz FILE *f; 180283241Strasz char *key, *options, *fstype, *nobrowse, *retrycnt, *tmp; 181270096Strasz int error; 182279741Strasz bool wildcards; 183270096Strasz 184270096Strasz log_debugx("got request %d: from %s, path %s, prefix \"%s\", " 185270096Strasz "key \"%s\", options \"%s\"", adr->adr_id, adr->adr_from, 186270096Strasz adr->adr_path, adr->adr_prefix, adr->adr_key, adr->adr_options); 187270096Strasz 188270096Strasz /* 189270096Strasz * Try to notify the kernel about any problems. 190270096Strasz */ 191270096Strasz request_id = adr->adr_id; 192270096Strasz atexit(exit_callback); 193270096Strasz 194270096Strasz if (strncmp(adr->adr_from, "map ", 4) != 0) { 195270096Strasz log_errx(1, "invalid mountfrom \"%s\"; failing request", 196270096Strasz adr->adr_from); 197270096Strasz } 198270096Strasz 199270096Strasz map = adr->adr_from + 4; /* 4 for strlen("map "); */ 200270096Strasz root = node_new_root(); 201270096Strasz if (adr->adr_prefix[0] == '\0' || strcmp(adr->adr_prefix, "/") == 0) { 202283241Strasz /* 203283241Strasz * Direct map. autofs(4) doesn't have a way to determine 204283241Strasz * correct map key, but since it's a direct map, we can just 205283241Strasz * use adr_path instead. 206283241Strasz */ 207270096Strasz parent = root; 208283241Strasz key = checked_strdup(adr->adr_path); 209270096Strasz } else { 210283241Strasz /* 211283241Strasz * Indirect map. 212283241Strasz */ 213270096Strasz parent = node_new_map(root, checked_strdup(adr->adr_prefix), 214283235Strasz NULL, checked_strdup(map), 215270096Strasz checked_strdup("[kernel request]"), lineno); 216283241Strasz 217283241Strasz if (adr->adr_key[0] == '\0') 218283241Strasz key = NULL; 219283241Strasz else 220283241Strasz key = checked_strdup(adr->adr_key); 221270096Strasz } 222279741Strasz 223279741Strasz /* 224279741Strasz * "Wildcards" here actually means "make autofs(4) request 225279741Strasz * automountd(8) action if the node being looked up does not 226279741Strasz * exist, even though the parent is marked as cached". This 227279741Strasz * needs to be done for maps with wildcard entries, but also 228279741Strasz * for special and executable maps. 229279741Strasz */ 230283241Strasz parse_map(parent, map, key, &wildcards); 231279741Strasz if (!wildcards) 232279741Strasz wildcards = node_has_wildcards(parent); 233279741Strasz if (wildcards) 234279741Strasz log_debugx("map may contain wildcard entries"); 235279741Strasz else 236279741Strasz log_debugx("map does not contain wildcard entries"); 237279741Strasz 238283241Strasz if (key != NULL) 239283241Strasz node_expand_wildcard(root, key); 240279741Strasz 241270096Strasz node = node_find(root, adr->adr_path); 242270096Strasz if (node == NULL) { 243270096Strasz log_errx(1, "map %s does not contain key for \"%s\"; " 244270096Strasz "failing mount", map, adr->adr_path); 245270096Strasz } 246270096Strasz 247283235Strasz options = node_options(node); 248283235Strasz 249283235Strasz /* 250283237Strasz * Append options from auto_master. 251283237Strasz */ 252283237Strasz options = concat(options, ',', adr->adr_options); 253283237Strasz 254283237Strasz /* 255283235Strasz * Prepend options passed via automountd(8) command line. 256283235Strasz */ 257283238Strasz options = concat(cmdline_options, ',', options); 258283235Strasz 259270096Strasz if (node->n_location == NULL) { 260270096Strasz log_debugx("found node defined at %s:%d; not a mountpoint", 261270096Strasz node->n_config_file, node->n_config_line); 262270096Strasz 263270902Strasz nobrowse = pick_option("nobrowse", &options); 264283241Strasz if (nobrowse != NULL && key == NULL) { 265270902Strasz log_debugx("skipping map %s due to \"nobrowse\" " 266270902Strasz "option; exiting", map); 267279741Strasz done(0, true); 268270902Strasz 269270902Strasz /* 270270902Strasz * Exit without calling exit_callback(). 271270902Strasz */ 272270902Strasz quick_exit(0); 273270902Strasz } 274270902Strasz 275270902Strasz /* 276270096Strasz * Not a mountpoint; create directories in the autofs mount 277270096Strasz * and complete the request. 278270096Strasz */ 279270096Strasz create_subtree(node, incomplete_hierarchy); 280270096Strasz 281283241Strasz if (incomplete_hierarchy && key != NULL) { 282270096Strasz /* 283270096Strasz * We still need to create the single subdirectory 284270096Strasz * user is trying to access. 285270096Strasz */ 286283241Strasz tmp = concat(adr->adr_path, '/', key); 287270096Strasz node = node_find(root, tmp); 288270096Strasz if (node != NULL) 289270096Strasz create_subtree(node, false); 290270096Strasz } 291270096Strasz 292270096Strasz log_debugx("nothing to mount; exiting"); 293279741Strasz done(0, wildcards); 294270096Strasz 295270096Strasz /* 296270096Strasz * Exit without calling exit_callback(). 297270096Strasz */ 298270096Strasz quick_exit(0); 299270096Strasz } 300270096Strasz 301270096Strasz log_debugx("found node defined at %s:%d; it is a mountpoint", 302270096Strasz node->n_config_file, node->n_config_line); 303270096Strasz 304283241Strasz if (key != NULL) 305283241Strasz node_expand_ampersand(node, key); 306270096Strasz error = node_expand_defined(node); 307270096Strasz if (error != 0) { 308270096Strasz log_errx(1, "variable expansion failed for %s; " 309270096Strasz "failing mount", adr->adr_path); 310270096Strasz } 311270096Strasz 312270096Strasz /* 313270096Strasz * Append "automounted". 314270096Strasz */ 315283231Strasz options = concat(options, ',', "automounted"); 316270096Strasz 317270096Strasz /* 318270902Strasz * Remove "nobrowse", mount(8) doesn't understand it. 319270902Strasz */ 320270902Strasz pick_option("nobrowse", &options); 321270902Strasz 322270902Strasz /* 323270096Strasz * Figure out fstype. 324270096Strasz */ 325270096Strasz fstype = pick_option("fstype=", &options); 326270096Strasz if (fstype == NULL) { 327270096Strasz log_debugx("fstype not specified in options; " 328270096Strasz "defaulting to \"nfs\""); 329270096Strasz fstype = checked_strdup("nfs"); 330270096Strasz } 331270096Strasz 332270096Strasz if (strcmp(fstype, "nfs") == 0) { 333270096Strasz /* 334270096Strasz * The mount_nfs(8) command defaults to retry undefinitely. 335270096Strasz * We do not want that behaviour, because it leaves mount_nfs(8) 336270096Strasz * instances and automountd(8) children hanging forever. 337270096Strasz * Disable retries unless the option was passed explicitly. 338270096Strasz */ 339270096Strasz retrycnt = pick_option("retrycnt=", &options); 340270096Strasz if (retrycnt == NULL) { 341270096Strasz log_debugx("retrycnt not specified in options; " 342270096Strasz "defaulting to 1"); 343283231Strasz options = concat(options, ',', "retrycnt=1"); 344270096Strasz } else { 345283231Strasz options = concat(options, ',', 346283231Strasz concat("retrycnt", '=', retrycnt)); 347270096Strasz } 348270096Strasz } 349270096Strasz 350270096Strasz f = auto_popen("mount", "-t", fstype, "-o", options, 351270096Strasz node->n_location, adr->adr_path, NULL); 352270096Strasz assert(f != NULL); 353270096Strasz error = auto_pclose(f); 354270096Strasz if (error != 0) 355270096Strasz log_errx(1, "mount failed"); 356270096Strasz 357270902Strasz log_debugx("mount done; exiting"); 358279741Strasz done(0, wildcards); 359270096Strasz 360270096Strasz /* 361270096Strasz * Exit without calling exit_callback(). 362270096Strasz */ 363270096Strasz quick_exit(0); 364270096Strasz} 365270096Strasz 366274548Straszstatic void 367274548Straszsigchld_handler(int dummy __unused) 368274548Strasz{ 369274548Strasz 370274548Strasz /* 371274548Strasz * The only purpose of this handler is to make SIGCHLD 372274548Strasz * interrupt the AUTOFSREQUEST ioctl(2), so we can call 373274548Strasz * wait_for_children(). 374274548Strasz */ 375274548Strasz} 376274548Strasz 377274548Straszstatic void 378274548Straszregister_sigchld(void) 379274548Strasz{ 380274548Strasz struct sigaction sa; 381274548Strasz int error; 382274548Strasz 383274548Strasz bzero(&sa, sizeof(sa)); 384274548Strasz sa.sa_handler = sigchld_handler; 385274548Strasz sigfillset(&sa.sa_mask); 386274548Strasz error = sigaction(SIGCHLD, &sa, NULL); 387274548Strasz if (error != 0) 388274548Strasz log_err(1, "sigaction"); 389274548Strasz 390274548Strasz} 391274548Strasz 392274548Strasz 393270096Straszstatic int 394270096Straszwait_for_children(bool block) 395270096Strasz{ 396270096Strasz pid_t pid; 397270096Strasz int status; 398270096Strasz int num = 0; 399270096Strasz 400270096Strasz for (;;) { 401270096Strasz /* 402270096Strasz * If "block" is true, wait for at least one process. 403270096Strasz */ 404270096Strasz if (block && num == 0) 405270096Strasz pid = wait4(-1, &status, 0, NULL); 406270096Strasz else 407270096Strasz pid = wait4(-1, &status, WNOHANG, NULL); 408270096Strasz if (pid <= 0) 409270096Strasz break; 410270096Strasz if (WIFSIGNALED(status)) { 411270096Strasz log_warnx("child process %d terminated with signal %d", 412270096Strasz pid, WTERMSIG(status)); 413270096Strasz } else if (WEXITSTATUS(status) != 0) { 414274498Strasz log_debugx("child process %d terminated with exit status %d", 415270096Strasz pid, WEXITSTATUS(status)); 416270096Strasz } else { 417270096Strasz log_debugx("child process %d terminated gracefully", pid); 418270096Strasz } 419270096Strasz num++; 420270096Strasz } 421270096Strasz 422270096Strasz return (num); 423270096Strasz} 424270096Strasz 425270096Straszstatic void 426270096Straszusage_automountd(void) 427270096Strasz{ 428270096Strasz 429270096Strasz fprintf(stderr, "usage: automountd [-D name=value][-m maxproc]" 430270096Strasz "[-o opts][-Tidv]\n"); 431270096Strasz exit(1); 432270096Strasz} 433270096Strasz 434270096Straszint 435270096Straszmain_automountd(int argc, char **argv) 436270096Strasz{ 437270096Strasz struct pidfh *pidfh; 438270096Strasz pid_t pid, otherpid; 439270096Strasz const char *pidfile_path = AUTOMOUNTD_PIDFILE; 440270096Strasz char *options = NULL; 441270096Strasz struct autofs_daemon_request request; 442270096Strasz int ch, debug = 0, error, maxproc = 30, retval, saved_errno; 443270096Strasz bool dont_daemonize = false, incomplete_hierarchy = false; 444270096Strasz 445270096Strasz defined_init(); 446270096Strasz 447270096Strasz while ((ch = getopt(argc, argv, "D:Tdim:o:v")) != -1) { 448270096Strasz switch (ch) { 449270096Strasz case 'D': 450270096Strasz defined_parse_and_add(optarg); 451270096Strasz break; 452270096Strasz case 'T': 453270096Strasz /* 454270096Strasz * For compatibility with other implementations, 455270096Strasz * such as OS X. 456270096Strasz */ 457270096Strasz debug++; 458270096Strasz break; 459270096Strasz case 'd': 460270096Strasz dont_daemonize = true; 461270096Strasz debug++; 462270096Strasz break; 463270096Strasz case 'i': 464270096Strasz incomplete_hierarchy = true; 465270096Strasz break; 466270096Strasz case 'm': 467270096Strasz maxproc = atoi(optarg); 468270096Strasz break; 469270096Strasz case 'o': 470283238Strasz options = concat(options, ',', optarg); 471270096Strasz break; 472270096Strasz case 'v': 473270096Strasz debug++; 474270096Strasz break; 475270096Strasz case '?': 476270096Strasz default: 477270096Strasz usage_automountd(); 478270096Strasz } 479270096Strasz } 480270096Strasz argc -= optind; 481270096Strasz if (argc != 0) 482270096Strasz usage_automountd(); 483270096Strasz 484270096Strasz log_init(debug); 485270096Strasz 486270096Strasz pidfh = pidfile_open(pidfile_path, 0600, &otherpid); 487270096Strasz if (pidfh == NULL) { 488270096Strasz if (errno == EEXIST) { 489270096Strasz log_errx(1, "daemon already running, pid: %jd.", 490270096Strasz (intmax_t)otherpid); 491270096Strasz } 492270096Strasz log_err(1, "cannot open or create pidfile \"%s\"", 493270096Strasz pidfile_path); 494270096Strasz } 495270096Strasz 496270096Strasz autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC); 497270096Strasz if (autofs_fd < 0 && errno == ENOENT) { 498270096Strasz saved_errno = errno; 499270096Strasz retval = kldload("autofs"); 500270096Strasz if (retval != -1) 501270096Strasz autofs_fd = open(AUTOFS_PATH, O_RDWR | O_CLOEXEC); 502270096Strasz else 503270096Strasz errno = saved_errno; 504270096Strasz } 505270096Strasz if (autofs_fd < 0) 506270096Strasz log_err(1, "failed to open %s", AUTOFS_PATH); 507270096Strasz 508270096Strasz if (dont_daemonize == false) { 509270096Strasz if (daemon(0, 0) == -1) { 510270096Strasz log_warn("cannot daemonize"); 511270096Strasz pidfile_remove(pidfh); 512270096Strasz exit(1); 513270096Strasz } 514270096Strasz } else { 515270096Strasz lesser_daemon(); 516270096Strasz } 517270096Strasz 518270096Strasz pidfile_write(pidfh); 519270096Strasz 520274548Strasz register_sigchld(); 521274548Strasz 522270096Strasz for (;;) { 523270096Strasz log_debugx("waiting for request from the kernel"); 524270096Strasz 525270096Strasz memset(&request, 0, sizeof(request)); 526270096Strasz error = ioctl(autofs_fd, AUTOFSREQUEST, &request); 527270096Strasz if (error != 0) { 528270096Strasz if (errno == EINTR) { 529270096Strasz nchildren -= wait_for_children(false); 530270096Strasz assert(nchildren >= 0); 531270096Strasz continue; 532270096Strasz } 533270096Strasz 534270096Strasz log_err(1, "AUTOFSREQUEST"); 535270096Strasz } 536270096Strasz 537270096Strasz if (dont_daemonize) { 538270096Strasz log_debugx("not forking due to -d flag; " 539270096Strasz "will exit after servicing a single request"); 540270096Strasz } else { 541270096Strasz nchildren -= wait_for_children(false); 542270096Strasz assert(nchildren >= 0); 543270096Strasz 544270096Strasz while (maxproc > 0 && nchildren >= maxproc) { 545270096Strasz log_debugx("maxproc limit of %d child processes hit; " 546270096Strasz "waiting for child process to exit", maxproc); 547270096Strasz nchildren -= wait_for_children(true); 548270096Strasz assert(nchildren >= 0); 549270096Strasz } 550270096Strasz log_debugx("got request; forking child process #%d", 551270096Strasz nchildren); 552270096Strasz nchildren++; 553270096Strasz 554270096Strasz pid = fork(); 555270096Strasz if (pid < 0) 556270096Strasz log_err(1, "fork"); 557270096Strasz if (pid > 0) 558270096Strasz continue; 559270096Strasz } 560270096Strasz 561270096Strasz pidfile_close(pidfh); 562270096Strasz handle_request(&request, options, incomplete_hierarchy); 563270096Strasz } 564270096Strasz 565270096Strasz pidfile_close(pidfh); 566270096Strasz 567270096Strasz return (0); 568270096Strasz} 569270096Strasz 570