/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "automount.h" #include #include #include #include #include #include #include #include #include #include #include #include static void autofs_doorfunc(void *, char *, size_t, door_desc_t *, uint_t); static void autofs_setdoor(int); static void autofs_mntinfo_1_r(autofs_lookupargs *, autofs_mountres *, ucred_t *); static void autofs_mount_1_free_r(struct autofs_mountres *); static void autofs_lookup_1_r(autofs_lookupargs *, autofs_lookupres *, ucred_t *); static void autofs_lookup_1_free_args(autofs_lookupargs *); static void autofs_unmount_1_r(umntrequest *, umntres *, ucred_t *); static void autofs_unmount_1_free_args(umntrequest *); static void autofs_readdir_1_r(autofs_rddirargs *, autofs_rddirres *, ucred_t *); static void autofs_readdir_1_free_r(struct autofs_rddirres *); static int decode_args(xdrproc_t, autofs_door_args_t *, caddr_t *, int); static bool_t encode_res(xdrproc_t, autofs_door_res_t **, caddr_t, int *); static void usage(); static void warn_hup(int); static void free_action_list(); static int start_autofs_svcs(); /* * Private autofs system call */ extern int _autofssys(int, void *); #define CTIME_BUF_LEN 26 /* * XXX - this limit was imposed due to resource problems - even though * we can and do try and set the rlimit to be able to handle more threads, * fopen() doesn't allow more than 256 fp's. */ #define MAXTHREADS 64 #define RESOURCE_FACTOR 8 #ifdef DEBUG #define AUTOFS_DOOR "/var/run/autofs_door" #endif /* DEBUG */ static thread_key_t s_thr_key; struct autodir *dir_head; struct autodir *dir_tail; char self[64]; time_t timenow; int verbose = 0; int trace = 0; int automountd_nobrowse = 0; int main(argc, argv) int argc; char *argv[]; { pid_t pid; int c, error; struct rlimit rlset; char *defval; if (geteuid() != 0) { (void) fprintf(stderr, "%s must be run as root\n", argv[0]); exit(1); } /* * Read in the values from config file first before we check * commandline options so the options override the file. */ if ((defopen(AUTOFSADMIN)) == 0) { if ((defval = defread("AUTOMOUNTD_VERBOSE=")) != NULL) { if (strncasecmp("true", defval, 4) == 0) verbose = TRUE; else verbose = FALSE; } if ((defval = defread("AUTOMOUNTD_NOBROWSE=")) != NULL) { if (strncasecmp("true", defval, 4) == 0) automountd_nobrowse = TRUE; else automountd_nobrowse = FALSE; } if ((defval = defread("AUTOMOUNTD_TRACE=")) != NULL) { errno = 0; trace = strtol(defval, (char **)NULL, 10); if (errno != 0) trace = 0; } put_automountd_env(); /* close defaults file */ defopen(NULL); } while ((c = getopt(argc, argv, "vnTD:")) != EOF) { switch (c) { case 'v': verbose++; break; case 'n': automountd_nobrowse++; break; case 'T': trace++; break; case 'D': (void) putenv(optarg); break; default: usage(); } } if (sysinfo(SI_HOSTNAME, self, sizeof (self)) == -1) { error = errno; (void) fprintf(stderr, "automountd: can't determine hostname, error: %d\n", error); exit(1); } #ifndef DEBUG pid = fork(); if (pid < 0) { perror("cannot fork"); exit(1); } if (pid) exit(0); #endif (void) setsid(); openlog("automountd", LOG_PID, LOG_DAEMON); (void) setlocale(LC_ALL, ""); (void) rwlock_init(&cache_lock, USYNC_THREAD, NULL); (void) rwlock_init(&autofs_rddir_cache_lock, USYNC_THREAD, NULL); /* * initialize the name services, use NULL arguments to ensure * we don't initialize the stack of files used in file service */ (void) ns_setup(NULL, NULL); /* * establish our lock on the lock file and write our pid to it. * exit if some other process holds the lock, or if there's any * error in writing/locking the file. */ pid = _enter_daemon_lock(AUTOMOUNTD); switch (pid) { case 0: break; case -1: syslog(LOG_ERR, "error locking for %s: %s", AUTOMOUNTD, strerror(errno)); exit(2); default: /* daemon was already running */ exit(0); } /* * If we coredump it'll be /core. */ if (chdir("/") < 0) syslog(LOG_ERR, "chdir /: %m"); /* * Create cache_cleanup thread */ if (thr_create(NULL, 0, (void *(*)(void *))cache_cleanup, NULL, THR_DETACHED | THR_DAEMON | THR_NEW_LWP, NULL)) { syslog(LOG_ERR, "unable to create cache_cleanup thread"); exit(1); } /* other initializations */ (void) rwlock_init(&portmap_cache_lock, USYNC_THREAD, NULL); /* on a labeled system, the automounter implements read-down policy */ if (is_system_labeled()) { if ((setpflags(NET_MAC_AWARE, 1) == -1) || (setpflags(NET_MAC_AWARE_INHERIT, 1) == -1)) syslog(LOG_ERR, "ignored failure to set MAC-aware " "mode: %m"); } (void) signal(SIGHUP, warn_hup); /* start services */ return (start_autofs_svcs()); } /* * The old automounter supported a SIGHUP * to allow it to resynchronize internal * state with the /etc/mnttab. * This is no longer relevant, but we * need to catch the signal and warn * the user. */ /* ARGSUSED */ static void warn_hup(i) int i; { syslog(LOG_ERR, "SIGHUP received: ignored"); (void) signal(SIGHUP, warn_hup); } static void usage() { (void) fprintf(stderr, "Usage: automountd\n" "\t[-T]\t\t(trace requests)\n" "\t[-v]\t\t(verbose error msgs)\n" "\t[-D n=s]\t(define env variable)\n"); exit(1); /* NOTREACHED */ } static void autofs_readdir_1_r( autofs_rddirargs *req, autofs_rddirres *res, ucred_t *autofs_cred) { if (trace > 0) trace_prt(1, "READDIR REQUEST : %s @ %ld\n", req->rda_map, req->rda_offset); do_readdir(req, res, autofs_cred); if (trace > 0) trace_prt(1, "READDIR REPLY : status=%d\n", res->rd_status); } static void autofs_readdir_1_free_r(struct autofs_rddirres *res) { if (res->rd_status == AUTOFS_OK) { if (res->rd_rddir.rddir_entries) free(res->rd_rddir.rddir_entries); } } /* ARGSUSED */ static void autofs_unmount_1_r( umntrequest *m, umntres *res, ucred_t *autofs_cred) { struct umntrequest *ul; if (trace > 0) { char ctime_buf[CTIME_BUF_LEN]; if (ctime_r(&timenow, ctime_buf, CTIME_BUF_LEN) == NULL) ctime_buf[0] = '\0'; trace_prt(1, "UNMOUNT REQUEST: %s", ctime_buf); for (ul = m; ul; ul = ul->next) trace_prt(1, " resource=%s fstype=%s mntpnt=%s" " mntopts=%s %s\n", ul->mntresource, ul->fstype, ul->mntpnt, ul->mntopts, ul->isdirect ? "direct" : "indirect"); } res->status = do_unmount1(m); if (trace > 0) trace_prt(1, "UNMOUNT REPLY: status=%d\n", res->status); } static void autofs_lookup_1_r( autofs_lookupargs *m, autofs_lookupres *res, ucred_t *autofs_cred) { autofs_action_t action; struct linka link; int status; if (trace > 0) { char ctime_buf[CTIME_BUF_LEN]; if (ctime_r(&timenow, ctime_buf, CTIME_BUF_LEN) == NULL) ctime_buf[0] = '\0'; trace_prt(1, "LOOKUP REQUEST: %s", ctime_buf); trace_prt(1, " name=%s[%s] map=%s opts=%s path=%s direct=%d\n", m->name, m->subdir, m->map, m->opts, m->path, m->isdirect); } bzero(&link, sizeof (struct linka)); status = do_lookup1(m->map, m->name, m->subdir, m->opts, m->path, (uint_t)m->isdirect, &action, &link, autofs_cred); if (status == 0) { /* * Return action list to kernel. */ res->lu_res = AUTOFS_OK; if ((res->lu_type.action = action) == AUTOFS_LINK_RQ) { res->lu_type.lookup_result_type_u.lt_linka = link; } } else { /* * Entry not found */ res->lu_res = AUTOFS_NOENT; } res->lu_verbose = verbose; if (trace > 0) trace_prt(1, "LOOKUP REPLY : status=%d\n", res->lu_res); } static void autofs_mntinfo_1_r( autofs_lookupargs *m, autofs_mountres *res, ucred_t *autofs_cred) { int status; action_list *alp = NULL; if (trace > 0) { char ctime_buf[CTIME_BUF_LEN]; if (ctime_r(&timenow, ctime_buf, CTIME_BUF_LEN) == NULL) ctime_buf[0] = '\0'; trace_prt(1, "MOUNT REQUEST: %s", ctime_buf); trace_prt(1, " name=%s[%s] map=%s opts=%s path=%s direct=%d\n", m->name, m->subdir, m->map, m->opts, m->path, m->isdirect); } status = do_mount1(m->map, m->name, m->subdir, m->opts, m->path, (uint_t)m->isdirect, &alp, autofs_cred, DOMOUNT_USER); if (status != 0) { /* * An error occurred, free action list if allocated. */ if (alp != NULL) { free_action_list(alp); alp = NULL; } } if (alp != NULL) { /* * Return action list to kernel. */ res->mr_type.status = AUTOFS_ACTION; res->mr_type.mount_result_type_u.list = alp; } else { /* * No work to do left for the kernel */ res->mr_type.status = AUTOFS_DONE; res->mr_type.mount_result_type_u.error = status; } if (trace > 0) { switch (res->mr_type.status) { case AUTOFS_ACTION: trace_prt(1, "MOUNT REPLY : status=%d, AUTOFS_ACTION\n", status); break; case AUTOFS_DONE: trace_prt(1, "MOUNT REPLY : status=%d, AUTOFS_DONE\n", status); break; default: trace_prt(1, "MOUNT REPLY : status=%d, UNKNOWN\n", status); } } if (status && verbose) { if (m->isdirect) { /* direct mount */ syslog(LOG_ERR, "mount of %s failed", m->path); } else { /* indirect mount */ syslog(LOG_ERR, "mount of %s/%s failed", m->path, m->name); } } } static void autofs_mount_1_free_r(struct autofs_mountres *res) { if (res->mr_type.status == AUTOFS_ACTION) { if (trace > 2) trace_prt(1, "freeing action list\n"); free_action_list(res->mr_type.mount_result_type_u.list); } } /* * Used for reporting messages from code shared with automount command. * Formats message into a buffer and calls syslog. * * Print an error. Works like printf (fmt string and variable args) * except that it will subsititute an error message for a "%m" string * (like syslog). */ void pr_msg(const char *fmt, ...) { va_list ap; char fmtbuff[BUFSIZ], buff[BUFSIZ]; const char *p1; char *p2; p2 = fmtbuff; fmt = gettext(fmt); for (p1 = fmt; *p1; p1++) { if (*p1 == '%' && *(p1 + 1) == 'm') { (void) strcpy(p2, strerror(errno)); p2 += strlen(p2); p1++; } else { *p2++ = *p1; } } if (p2 > fmtbuff && *(p2-1) != '\n') *p2++ = '\n'; *p2 = '\0'; va_start(ap, fmt); (void) vsprintf(buff, fmtbuff, ap); va_end(ap); syslog(LOG_ERR, buff); } static void free_action_list(action_list *alp) { action_list *p, *next = NULL; struct mounta *mp; for (p = alp; p != NULL; p = next) { switch (p->action.action) { case AUTOFS_MOUNT_RQ: mp = &(p->action.action_list_entry_u.mounta); /* LINTED pointer alignment */ if (mp->fstype) { if (strcmp(mp->fstype, "autofs") == 0) { free_autofs_args((autofs_args *) mp->dataptr); } else if (strncmp(mp->fstype, "nfs", 3) == 0) { free_nfs_args((struct nfs_args *) mp->dataptr); } } mp->dataptr = NULL; mp->datalen = 0; free_mounta(mp); break; case AUTOFS_LINK_RQ: syslog(LOG_ERR, "non AUTOFS_MOUNT_RQ requests not implemented\n"); break; default: syslog(LOG_ERR, "non AUTOFS_MOUNT_RQ requests not implemented\n"); break; } next = p->next; free(p); } } static void autofs_lookup_1_free_args(autofs_lookupargs *args) { if (args->map) free(args->map); if (args->path) free(args->path); if (args->name) free(args->name); if (args->subdir) free(args->subdir); if (args->opts) free(args->opts); } static void autofs_unmount_1_free_args(umntrequest *args) { if (args->mntresource) free(args->mntresource); if (args->mntpnt) free(args->mntpnt); if (args->fstype) free(args->fstype); if (args->mntopts) free(args->mntopts); if (args->next) autofs_unmount_1_free_args(args->next); } static void autofs_setdoor(int did) { if (did < 0) { did = 0; } (void) _autofssys(AUTOFS_SETDOOR, &did); } void * autofs_get_buffer(size_t size) { autofs_tsd_t *tsd = NULL; /* * Make sure the buffer size is aligned */ (void) thr_getspecific(s_thr_key, (void **)&tsd); if (tsd == NULL) { tsd = (autofs_tsd_t *)malloc(sizeof (autofs_tsd_t)); if (tsd == NULL) { return (NULL); } tsd->atsd_buf = malloc(size); if (tsd->atsd_buf != NULL) tsd->atsd_len = size; else tsd->atsd_len = 0; (void) thr_setspecific(s_thr_key, tsd); } else { if (tsd->atsd_buf && (tsd->atsd_len < size)) { free(tsd->atsd_buf); tsd->atsd_buf = malloc(size); if (tsd->atsd_buf != NULL) tsd->atsd_len = size; else { tsd->atsd_len = 0; } } } if (tsd->atsd_buf) { bzero(tsd->atsd_buf, size); return (tsd->atsd_buf); } else { syslog(LOG_ERR, gettext("Can't Allocate tsd buffer, size %d"), size); return (NULL); } } /* * Each request will automatically spawn a new thread with this * as its entry point. */ /* ARGUSED */ static void autofs_doorfunc( void *cookie, char *argp, size_t arg_size, door_desc_t *dp, uint_t n_desc) { char *res; int res_size; int which, error = 0; autofs_lookupargs *xdrargs; autofs_lookupres lookup_res; autofs_rddirargs *rddir_args; autofs_rddirres rddir_res; autofs_mountres mount_res; umntrequest *umnt_args; umntres umount_res; autofs_door_res_t *door_res; autofs_door_res_t failed_res; /* * autofs_cred is nulled because door_cred assumes non-null * to have been previously allocated. */ ucred_t *autofs_cred = NULL; if (arg_size < sizeof (autofs_door_args_t)) { failed_res.res_status = EINVAL; error = door_return((char *)&failed_res, sizeof (autofs_door_res_t), NULL, 0); /* * If we got here the door_return() failed. */ syslog(LOG_ERR, "Bad argument, door_return failure %d", error); return; } error = door_ucred(&autofs_cred); if (error) { failed_res.res_status = error; error = door_return((char *)&failed_res, sizeof (autofs_door_res_t), NULL, 0); /* * If we got here, door_return() failed */ syslog(LOG_ERR, "Bad cred, door_return() failed, %d", error); return; } timenow = time((time_t *)NULL); which = ((autofs_door_args_t *)argp)->cmd; switch (which) { case AUTOFS_LOOKUP: if (error = decode_args(xdr_autofs_lookupargs, (autofs_door_args_t *)argp, (caddr_t *)&xdrargs, sizeof (autofs_lookupargs))) { syslog(LOG_ERR, "error allocating lookup arguments" " buffer"); failed_res.res_status = error; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; res_size = 0; break; } bzero(&lookup_res, sizeof (autofs_lookupres)); autofs_lookup_1_r(xdrargs, &lookup_res, autofs_cred); autofs_lookup_1_free_args(xdrargs); free(xdrargs); if (!encode_res(xdr_autofs_lookupres, &door_res, (caddr_t)&lookup_res, &res_size)) { syslog(LOG_ERR, "error allocating lookup" "results buffer"); failed_res.res_status = EINVAL; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; } else { door_res->res_status = 0; res = (caddr_t)door_res; } break; case AUTOFS_MNTINFO: if (error = decode_args(xdr_autofs_lookupargs, (autofs_door_args_t *)argp, (caddr_t *)&xdrargs, sizeof (autofs_lookupargs))) { syslog(LOG_ERR, "error allocating lookup arguments" " buffer"); failed_res.res_status = error; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; res_size = 0; break; } autofs_mntinfo_1_r((autofs_lookupargs *)xdrargs, &mount_res, autofs_cred); autofs_lookup_1_free_args(xdrargs); free(xdrargs); /* * Only reason we would get a NULL res is because * we could not allocate a results buffer. Use * a local one to return the error EAGAIN as has * always been done when memory allocations fail. */ if (!encode_res(xdr_autofs_mountres, &door_res, (caddr_t)&mount_res, &res_size)) { syslog(LOG_ERR, "error allocating mount" "results buffer"); failed_res.res_status = EAGAIN; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; } else { door_res->res_status = 0; res = (caddr_t)door_res; } autofs_mount_1_free_r(&mount_res); break; case AUTOFS_UNMOUNT: if (error = decode_args(xdr_umntrequest, (autofs_door_args_t *)argp, (caddr_t *)&umnt_args, sizeof (umntrequest))) { syslog(LOG_ERR, "error allocating unmount " "argument buffer"); failed_res.res_status = error; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; res_size = sizeof (autofs_door_res_t); break; } autofs_unmount_1_r(umnt_args, &umount_res, autofs_cred); error = umount_res.status; autofs_unmount_1_free_args(umnt_args); free(umnt_args); if (!encode_res(xdr_umntres, &door_res, (caddr_t)&umount_res, &res_size)) { syslog(LOG_ERR, "error allocating unmount" "results buffer"); failed_res.res_status = EINVAL; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; res_size = sizeof (autofs_door_res_t); } else { door_res->res_status = 0; res = (caddr_t)door_res; } break; case AUTOFS_READDIR: if (error = decode_args(xdr_autofs_rddirargs, (autofs_door_args_t *)argp, (caddr_t *)&rddir_args, sizeof (autofs_rddirargs))) { syslog(LOG_ERR, "error allocating readdir argument buffer"); failed_res.res_status = error; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; res_size = sizeof (autofs_door_res_t); break; } autofs_readdir_1_r(rddir_args, &rddir_res, autofs_cred); free(rddir_args->rda_map); free(rddir_args); if (!encode_res(xdr_autofs_rddirres, &door_res, (caddr_t)&rddir_res, &res_size)) { syslog(LOG_ERR, "error allocating readdir" "results buffer"); failed_res.res_status = ENOMEM; failed_res.xdr_len = 0; res = (caddr_t)&failed_res; res_size = sizeof (autofs_door_res_t); } else { door_res->res_status = 0; res = (caddr_t)door_res; } autofs_readdir_1_free_r(&rddir_res); break; #ifdef MALLOC_DEBUG case AUTOFS_DUMP_DEBUG: ucred_free(autofs_cred); check_leaks("/var/tmp/automountd.leak"); error = door_return(NULL, 0, NULL, 0); /* * If we got here, door_return() failed */ syslog(LOG_ERR, "dump debug door_return failure %d", error); return; #endif case NULLPROC: res = NULL; res_size = 0; break; default: failed_res.res_status = EINVAL; res = (char *)&failed_res; res_size = sizeof (autofs_door_res_t); break; } ucred_free(autofs_cred); error = door_return(res, res_size, NULL, 0); /* * If we got here, door_return failed. */ syslog(LOG_ERR, "door_return failed %d, buffer %p, buffer size %d", error, (void *)res, res_size); } static int start_autofs_svcs(void) { int doorfd; #ifdef DEBUG int dfd; #endif if ((doorfd = door_create(autofs_doorfunc, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) { syslog(LOG_ERR, gettext("Unable to create door\n")); return (1); } #ifdef DEBUG /* * Create a file system path for the door */ if ((dfd = open(AUTOFS_DOOR, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) == -1) { syslog(LOG_ERR, "Unable to open %s: %m\n", AUTOFS_DOOR); (void) close(doorfd); return (1); } /* * stale associations clean up */ (void) fdetach(AUTOFS_DOOR); /* * Register in the namespace to the kernel to door_ki_open. */ if (fattach(doorfd, AUTOFS_DOOR) == -1) { syslog(LOG_ERR, "Unable to fattach door %m\n", AUTOFS_DOOR); (void) close(dfd); (void) close(doorfd); return (1); } #endif /* DEBUG */ /* * Pass door name to kernel for door_ki_open */ autofs_setdoor(doorfd); (void) thr_keycreate(&s_thr_key, NULL); /* * Wait for incoming calls */ /*CONSTCOND*/ while (1) (void) pause(); /* NOTREACHED */ syslog(LOG_ERR, gettext("Door server exited")); return (10); } static int decode_args( xdrproc_t xdrfunc, autofs_door_args_t *argp, caddr_t *xdrargs, int size) { XDR xdrs; caddr_t tmpargs = (caddr_t)&((autofs_door_args_t *)argp)->xdr_arg; size_t arg_size = ((autofs_door_args_t *)argp)->xdr_len; xdrmem_create(&xdrs, tmpargs, arg_size, XDR_DECODE); *xdrargs = malloc(size); if (*xdrargs == NULL) { syslog(LOG_ERR, "error allocating arguments" " buffer"); return (ENOMEM); } bzero(*xdrargs, size); if (!(*xdrfunc)(&xdrs, *xdrargs)) { free(*xdrargs); *xdrargs = NULL; syslog(LOG_ERR, "error decoding arguments"); return (EINVAL); } return (0); } static bool_t encode_res( xdrproc_t xdrfunc, autofs_door_res_t **results, caddr_t resp, int *size) { XDR xdrs; *size = xdr_sizeof((*xdrfunc), resp); *results = autofs_get_buffer( sizeof (autofs_door_res_t) + *size); if (*results == NULL) { (*results)->res_status = ENOMEM; return (FALSE); } (*results)->xdr_len = *size; *size = sizeof (autofs_door_res_t) + (*results)->xdr_len; xdrmem_create(&xdrs, (caddr_t)((*results)->xdr_res), (*results)->xdr_len, XDR_ENCODE); if (!(*xdrfunc)(&xdrs, resp)) { (*results)->res_status = EINVAL; syslog(LOG_ERR, "error encoding results"); return (FALSE); } (*results)->res_status = 0; return (TRUE); }