#pragma ident "@(#)autod_mount.c 1.71 05/06/08 SMI"
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <errno.h>
#include <mntopts.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <assert.h>
#include <fcntl.h>
#include "automount.h"
#include "automountd.h"
#include "auto_mntopts.h"
#include "replica.h"
#include "sysctl_fsid.h"
#include "umount_by_fsid.h"
static void free_action_list(action_list *);
static int unmount_mntpnt(int32_t, int32_t, struct mnttab *);
static int fork_exec(char *, char **, uid_t, mach_port_t);
static void remove_browse_options(char *, bool_t);
static int inherit_options(char *, char **);
#define ROUND_UP(a, b) ((((a) + (b) - 1)/(b)) * (b))
pthread_mutex_t gssd_port_lock;
static uint32_t
countstring(char *string)
{
uint32_t stringlen;
if (string != NULL)
stringlen = (uint32_t)strlen(string);
else
stringlen = 0;
return ((uint32_t)sizeof (uint32_t) + stringlen);
}
static uint8_t *
putstring(uint8_t *outbuf, char *string)
{
uint32_t stringlen;
if (string != NULL) {
stringlen = (uint32_t)strlen(string);
memcpy(outbuf, &stringlen, sizeof (uint32_t));
outbuf += sizeof (uint32_t);
memcpy(outbuf, string, stringlen);
outbuf += stringlen;
} else {
stringlen = 0xFFFFFFFF;
memcpy(outbuf, &stringlen, sizeof (uint32_t));
outbuf += sizeof (uint32_t);
}
return (outbuf);
}
int
do_mount1(autofs_pathname mapname, char *key, autofs_pathname subdir,
autofs_opts mapopts, autofs_pathname path, boolean_t isdirect,
uid_t sendereuid, mach_port_t gssd_port,
byte_buffer *actions, mach_msg_type_number_t *actionsCnt)
{
bool_t from_fstab = FALSE;
struct mapent *me, *mapents;
char mntpnt[MAXPATHLEN];
char spec_mntpnt[MAXPATHLEN];
int err;
char *private;
ssize_t len;
action_list *alp, *alphead, *prev, *tmp;
char root[MAXPATHLEN];
char next_subdir[MAXPATHLEN];
bool_t mount_access = TRUE;
bool_t iswildcard;
bool_t isrestricted = hasrestrictopt(mapopts);
kern_return_t ret;
size_t bufsize;
vm_address_t buffer_vm_address;
uint8_t *outbuf;
if (strcmp(mapname, "-fstab") == 0 || strcmp(mapname, "-static") == 0)
from_fstab = TRUE;
retry:
iswildcard = FALSE;
mapents = parse_entry(key, mapname, mapopts, subdir, isdirect,
&iswildcard, isrestricted, mount_access, &err);
if (mapents == NULL) {
return (err);
}
if (trace > 1) {
struct mapfs *mfs;
trace_prt(1, " do_mount1:\n");
for (me = mapents; me; me = me->map_next) {
trace_prt(1, " (%s,%s)\t%s%s%s\n",
me->map_fstype ? me->map_fstype : "",
me->map_mounter ? me->map_mounter : "",
path ? path : "",
me->map_root ? me->map_root : "",
me->map_mntpnt ? me->map_mntpnt : "");
trace_prt(0, "\t\t-%s\n",
me->map_mntopts ? me->map_mntopts : "");
for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next)
trace_prt(0, "\t\t%s:%s\tpenalty=%d\n",
mfs->mfs_host ? mfs->mfs_host: "",
mfs->mfs_dir ? mfs->mfs_dir : "",
mfs->mfs_penalty);
}
}
alphead = NULL;
private = "";
for (me = mapents; me && !err; me = me->map_next) {
len = snprintf(mntpnt, sizeof (mntpnt), "%s%s%s", path,
mapents->map_root, me->map_mntpnt);
if (len < 0) {
free_mapent(mapents);
return (EINVAL);
}
if ((size_t)len >= sizeof (mntpnt)) {
free_mapent(mapents);
return (ENAMETOOLONG);
}
len -= 1;
while (mntpnt[len] == '/')
mntpnt[len--] = '\0';
(void) strcpy(spec_mntpnt, mntpnt);
if (isrestricted &&
inherit_options(mapopts, &me->map_mntopts) != 0) {
syslog(LOG_ERR, "malloc of options failed");
free_mapent(mapents);
return (EAGAIN);
}
if (strcmp(me->map_fstype, MNTTYPE_NFS) == 0) {
remove_browse_options(me->map_mntopts, from_fstab);
err =
mount_nfs(me, spec_mntpnt, private, isdirect,
gssd_port);
if (err == EACCES && me->map_next != NULL) {
if (mount_access == TRUE) {
mount_access = FALSE;
free_mapent(mapents);
goto retry;
}
}
} else if (strcmp(me->map_fstype, MNTTYPE_AUTOFS) == 0) {
if (isdirect) {
len = strlcpy(root, path, sizeof (root));
} else {
len = snprintf(root, sizeof (root), "%s/%s",
path, key);
}
if (len < 0) {
free_mapent(mapents);
return (EINVAL);
}
if ((size_t)len >= sizeof (root)) {
free_mapent(mapents);
return (ENAMETOOLONG);
}
alp = (action_list *)malloc(sizeof (action_list));
if (alp == NULL) {
syslog(LOG_ERR, "malloc of alp failed");
continue;
}
memset(alp, 0, sizeof (action_list));
if (me->map_modified || me->map_faked) {
len = snprintf(next_subdir,
sizeof (next_subdir), "%s%s", subdir,
me->map_mntpnt);
} else {
next_subdir[0] = '\0';
len = 0;
}
if (trace > 2)
trace_prt(1, " root=%s\t next_subdir=%s\n",
root, next_subdir);
if (len < 0) {
err = EINVAL;
} else if ((size_t)len < sizeof (next_subdir)) {
err = mount_autofs(me, spec_mntpnt, alp,
root, next_subdir, key);
} else {
err = ENAMETOOLONG;
}
if (err == 0) {
if (alphead == NULL)
alphead = alp;
else {
for (tmp = alphead; tmp != NULL;
tmp = tmp->next)
prev = tmp;
prev->next = alp;
}
} else
free(alp);
#ifdef HAVE_LOFS
} else if (strcmp(me->map_fstype, MNTTYPE_LOFS) == 0) {
remove_browse_options(me->map_mntopts, from_fstab);
err = loopbackmount(me->map_fs->mfs_dir, spec_mntpnt,
me->map_mntopts);
#endif
} else {
remove_browse_options(me->map_mntopts, from_fstab);
err = mount_generic(me->map_fs->mfs_dir,
me->map_fstype, me->map_mntopts,
spec_mntpnt, isdirect, sendereuid,
gssd_port);
}
}
if (mapents)
free_mapent(mapents);
if (!err) {
bufsize = 0;
for (tmp = alphead; tmp != NULL; tmp = tmp->next) {
bufsize += countstring(tmp->mounta.dir);
bufsize += countstring(tmp->mounta.opts);
bufsize += countstring(tmp->mounta.path);
bufsize += countstring(tmp->mounta.map);
bufsize += countstring(tmp->mounta.subdir);
bufsize += sizeof (uint32_t);
bufsize += countstring(tmp->mounta.key);
}
if (bufsize != 0) {
ret = vm_allocate(current_task(), &buffer_vm_address,
bufsize, VM_FLAGS_ANYWHERE);
if (ret != KERN_SUCCESS) {
syslog(LOG_ERR, "memory allocation error: %s",
mach_error_string(ret));
free_action_list(alphead);
return (ENOMEM);
}
outbuf = (uint8_t *)buffer_vm_address;
*actions = outbuf;
for (tmp = alphead; tmp != NULL; tmp = tmp->next) {
outbuf = putstring(outbuf, tmp->mounta.dir);
outbuf = putstring(outbuf, tmp->mounta.opts);
outbuf = putstring(outbuf, tmp->mounta.path);
outbuf = putstring(outbuf, tmp->mounta.map);
outbuf = putstring(outbuf, tmp->mounta.subdir);
memcpy(outbuf, &tmp->mounta.isdirect,
sizeof (uint32_t));
outbuf += sizeof (uint32_t);
outbuf = putstring(outbuf, tmp->mounta.key);
}
free_action_list(alphead);
}
*actionsCnt = (mach_msg_type_number_t)bufsize;
}
return (err);
}
static void
free_action_list(action_list *alp)
{
action_list *p, *next = NULL;
for (p = alp; p != NULL; p = next) {
free_action_list_fields(p);
next = p->next;
free(p);
}
}
int
do_check_trigger(autofs_pathname mapname, char *key, autofs_pathname subdir,
autofs_opts mapopts, autofs_pathname path, boolean_t isdirect,
boolean_t *istrigger)
{
struct mapent *me, *mapents = NULL;
int err = 0;
bool_t iswildcard;
bool_t isrestricted = hasrestrictopt(mapopts);
iswildcard = FALSE;
*istrigger = FALSE;
mapents = parse_entry(key, mapname, mapopts, subdir, isdirect,
&iswildcard, isrestricted, TRUE, &err);
if (mapents == NULL)
return (err);
if (trace > 1) {
struct mapfs *mfs;
trace_prt(1, " do_check_trigger:\n");
for (me = mapents; me; me = me->map_next) {
trace_prt(1, " (%s,%s)\t%s%s%s\n",
me->map_fstype ? me->map_fstype : "",
me->map_mounter ? me->map_mounter : "",
path ? path : "",
me->map_root ? me->map_root : "",
me->map_mntpnt ? me->map_mntpnt : "");
trace_prt(0, "\t\t-%s\n",
me->map_mntopts ? me->map_mntopts : "");
for (mfs = me->map_fs; mfs; mfs = mfs->mfs_next)
trace_prt(0, "\t\t%s:%s\tpenalty=%d\n",
mfs->mfs_host ? mfs->mfs_host: "",
mfs->mfs_dir ? mfs->mfs_dir : "",
mfs->mfs_penalty);
}
}
for (me = mapents; me; me = me->map_next) {
if (strcmp(me->map_fstype, MNTTYPE_AUTOFS) != 0)
*istrigger = TRUE;
}
free_mapent(mapents);
return (0);
}
#define ARGV_MAX 21
#define VFS_PATH "/sbin"
#define MOUNT_PATH "/sbin/mount"
#define MOUNT_URL_PATH "/usr/libexec/mount_url"
int
mount_generic(char *special, char *fstype, char *opts, char *mntpnt,
boolean_t isdirect, uid_t sendereuid, mach_port_t gssd_port)
{
struct stat stbuf;
int i, res;
char *newargv[ARGV_MAX];
if (trace > 1) {
trace_prt(1, " mount: %s %s %s %s\n",
special, mntpnt, fstype, opts);
}
if (!isdirect && stat(mntpnt, &stbuf) < 0) {
syslog(LOG_ERR, "Couldn't stat %s: %m", mntpnt);
return (ENOENT);
}
i = 1;
#if 0
newargv[i++] = "-q";
#endif
newargv[i++] = "-o";
newargv[i++] = "nobrowse";
if (strcmp(fstype, "nfs") == 0) {
newargv[i++] = "-t";
newargv[i++] = "nfs";
newargv[i++] = "-o";
newargv[i++] = "retrycnt=0";
}
if (automountd_defopts != NULL) {
newargv[i++] = "-o";
newargv[i++] = automountd_defopts;
}
if (opts && *opts) {
newargv[i++] = "-o";
newargv[i++] = opts;
}
newargv[i++] = "-o";
newargv[i++] = "automounted";
if (special[0] == '-') {
syslog(LOG_ERR,
"Can't mount \"%s\", as its name begins with \"-\"\n",
special);
return (ENOENT);
}
newargv[i++] = special;
newargv[i++] = mntpnt;
newargv[i] = NULL;
res = fork_exec(fstype, newargv, sendereuid, gssd_port);
if (res == 0 && trace > 1) {
if (stat(mntpnt, &stbuf) == 0) {
trace_prt(1, " mount of %s dev=%x rdev=%x OK\n",
mntpnt, stbuf.st_dev, stbuf.st_rdev);
} else {
trace_prt(1, " failed to stat %s\n", mntpnt);
}
}
return (res);
}
static void
put_back_gssd_port(mach_port_t saved_gssd_port)
{
kern_return_t ret;
ret = task_set_gssd_port(current_task(), saved_gssd_port);
pthread_mutex_unlock(&gssd_port_lock);
if (ret != KERN_SUCCESS) {
syslog(LOG_ERR, "Cannot restore gssd port: %s",
mach_error_string(ret));
} else {
mach_port_deallocate(current_task(), saved_gssd_port);
}
}
static int
fork_exec(char *fstype, char **newargv, uid_t sendereuid, mach_port_t gssd_port)
{
char *path;
volatile int path_is_allocated;
struct stat stbuf;
int i;
kern_return_t ret;
mach_port_t saved_gssd_port;
int child_pid;
int stat_loc;
int fd = 0;
int res;
if (strcmp(fstype, "url") == 0) {
path = MOUNT_URL_PATH;
path_is_allocated = 0;
} else if (strcmp(fstype, "nfs") == 0) {
path = MOUNT_PATH;
path_is_allocated = 0;
} else {
if (strcmp(fstype, "smb") == 0)
fstype = "smbfs";
if (asprintf(&path, "%s/mount_%s", VFS_PATH, fstype) == -1) {
res = errno;
syslog(LOG_ERR, "Can't construct pathname of mount program: %m");
return (res);
}
path_is_allocated = 1;
}
if (stat(path, &stbuf) != 0) {
res = errno;
syslog(LOG_ERR, "Can't stat mount program %s: %m", path);
goto done;
}
if (trace > 1) {
trace_prt(1, " fork_exec: %s ", path);
for (i = 1; newargv[i]; i++)
trace_prt(0, "%s ", newargv[i]);
trace_prt(0, "\n");
}
newargv[0] = path;
pthread_mutex_lock(&gssd_port_lock);
ret = task_get_gssd_port(current_task(), &saved_gssd_port);
if (ret != KERN_SUCCESS) {
pthread_mutex_unlock(&gssd_port_lock);
syslog(LOG_ERR, "Cannot get gssd port: %s",
mach_error_string(ret));
res = EIO;
goto done;
}
ret = task_set_gssd_port(current_task(), gssd_port);
if (ret != KERN_SUCCESS) {
mach_port_deallocate(current_task(), saved_gssd_port);
pthread_mutex_unlock(&gssd_port_lock);
syslog(LOG_ERR, "Cannot set gssd port: %s",
mach_error_string(ret));
res = EIO;
goto done;
}
switch ((child_pid = fork1())) {
case -1:
put_back_gssd_port(saved_gssd_port);
res = errno;
syslog(LOG_ERR, "Cannot fork: %m");
goto done;
case 0:
if (!verbose) {
fd = open("/dev/null", O_WRONLY);
if (fd != -1) {
(void) dup2(fd, 1);
(void) dup2(fd, 2);
(void) close(fd);
}
}
if (setuid(sendereuid) == -1) {
res = errno;
syslog(LOG_ERR, "Can't set mount subprocess UID: %s",
strerror(res));
_exit(res);
}
(void) execv(path, newargv);
res = errno;
syslog(LOG_ERR, "exec %s: %m", path);
_exit(res);
default:
put_back_gssd_port(saved_gssd_port);
(void) waitpid(child_pid, &stat_loc, WUNTRACED);
if (WIFEXITED(stat_loc)) {
if (trace > 1) {
trace_prt(1,
" fork_exec: returns exit status %d\n",
WEXITSTATUS(stat_loc));
}
res = WEXITSTATUS(stat_loc);
} else if (WIFSIGNALED(stat_loc)) {
syslog(LOG_ERR, "Mount subprocess terminated with %s",
strsignal(WTERMSIG(stat_loc)));
if (trace > 1) {
trace_prt(1,
" fork_exec: returns signal status %d\n",
WTERMSIG(stat_loc));
}
res = EIO;
} else if (WIFSTOPPED(stat_loc)) {
syslog(LOG_ERR, "Mount subprocess stopped with %s",
strsignal(WSTOPSIG(stat_loc)));
res = EIO;
} else {
syslog(LOG_ERR, "Mount subprocess got unknown status 0x%08x",
stat_loc);
if (trace > 1)
trace_prt(1,
" fork_exec: returns unknown status\n");
res = EIO;
}
}
done:
if (path_is_allocated)
free(path);
return (res);
}
static const struct mntopt mopts_nfs[] = {
MOPT_NFS
};
int
do_unmount1(int32_t fsid_val0, int32_t fsid_val1,
autofs_pathname mntresource, autofs_pathname mntpnt,
autofs_component fstype, autofs_opts mntopts)
{
struct mnttab m;
int res = 0;
mntoptparse_t mp;
int flags;
int altflags;
m.mnt_special = mntresource;
m.mnt_mountp = mntpnt;
m.mnt_fstype = fstype;
m.mnt_mntopts = mntopts;
if (strcmp(fstype, MNTTYPE_NFS) == 0) {
struct replica *list;
int i, n;
long nfs_port;
flags = altflags = 0;
getmnt_silent = 1;
mp = getmntopts(mntopts, mopts_nfs, &flags, &altflags);
if (mp != NULL) {
if (altflags & NFS_MNT_PORT) {
nfs_port = getmntoptnum(mp, "port");
if (nfs_port != -1)
nfs_port &= USHRT_MAX;
else {
syslog(LOG_ERR, "Couldn't parse port= option in \"%s\": %m",
mntopts);
nfs_port = 0;
}
} else
nfs_port = 0;
freemntopts(mp);
} else {
syslog(LOG_ERR, "Couldn't parse mount options \"%s\": %m",
mntopts);
nfs_port = 0;
}
list = parse_replica(mntresource, &n);
if (list == NULL) {
if (n >= 0)
syslog(LOG_ERR, "Memory allocation failed: %m");
res = 1;
goto done;
}
for (i = 0; i < n; i++) {
if (pingnfs(list[i].host, NULL, 0, nfs_port,
list[i].path, NULL) != RPC_SUCCESS) {
res = 1;
free_replica(list, n);
goto done;
}
}
free_replica(list, n);
}
res = unmount_mntpnt(fsid_val0, fsid_val1, &m);
done: return (res);
}
static int
unmount_mntpnt(int32_t fsid_val0, int32_t fsid_val1, struct mnttab *mnt)
{
fsid_t fsid;
int res = 0;
fsid.val[0] = fsid_val0;
fsid.val[1] = fsid_val1;
if (umount_by_fsid(&fsid, 0) < 0)
res = errno;
if (trace > 1)
trace_prt(1, " unmount %s %s\n",
mnt->mnt_mountp, res ? "failed" : "OK");
return (res);
}
static void
remove_browse_options(char *opts, bool_t from_fstab)
{
char *p, *pb;
char buf[MAXOPTSLEN], new[MAXOPTSLEN];
char *placeholder;
new[0] = '\0';
CHECK_STRCPY(buf, opts, sizeof buf);
pb = buf;
while ((p = (char *)strtok_r(pb, ",", &placeholder)) != NULL) {
pb = NULL;
if (strcmp(p, MNTOPT_RESTRICT) == 0)
continue;
if (!from_fstab) {
if (strcmp(p, "nobrowse") == 0 ||
strcmp(p, "browse") == 0)
continue;
}
if (new[0] != '\0')
(void) strcat(new, ",");
(void) strcat(new, p);
}
(void) strcpy(opts, new);
}
static const struct mntopt mopts_restrict[] = {
RESTRICTED_MNTOPTS
};
#define NROPTS ((sizeof (mopts_restrict)/sizeof (mopts_restrict[0])) - 1)
static int
inherit_options(char *opts, char **mapentopts)
{
u_int i;
char *new;
mntoptparse_t mtmap;
int mtmapflags, mtmapaltflags;
mntoptparse_t mtopt;
int mtoptflags, mtoptaltflags;
bool_t addopt;
size_t len;
len = strlen(*mapentopts);
for (i = 0; i < NROPTS; i++) {
len += strlen(mopts_restrict[i].m_option);
if (mopts_restrict[i].m_inverse)
len += 2;
}
len += NROPTS + 1;
new = malloc(len);
if (new == 0)
return (-1);
CHECK_STRCPY(new, *mapentopts, len);
mtmapflags = mtmapaltflags = 0;
getmnt_silent = 1;
mtmap = getmntopts(*mapentopts, mopts_restrict, &mtmapflags, &mtmapaltflags);
if (mtmap == NULL) {
syslog(LOG_ERR, "Couldn't parse mount options \"%s\": %m",
opts);
return (-1);
}
freemntopts(mtmap);
mtoptflags = mtoptaltflags = 0;
getmnt_silent = 1;
mtopt = getmntopts(opts, mopts_restrict, &mtoptflags, &mtoptaltflags);
if (mtopt == NULL) {
syslog(LOG_ERR, "Couldn't parse mount options \"%s\": %m",
opts);
return (-1);
}
freemntopts(mtopt);
for (i = 0; i < NROPTS; i++) {
if (mopts_restrict[i].m_altloc) {
addopt = ((mtoptaltflags & mopts_restrict[i].m_flag) &&
!(mtmapaltflags & mopts_restrict[i].m_flag));
} else {
addopt = ((mtoptflags & mopts_restrict[i].m_flag) &&
!(mtmapflags & mopts_restrict[i].m_flag));
}
if (addopt) {
if (*new != '\0')
CHECK_STRCAT(new, ",", len);
if (mopts_restrict[i].m_inverse)
CHECK_STRCAT(new, "no", len);
CHECK_STRCAT(new, mopts_restrict[i].m_option, len);
}
}
free(*mapentopts);
*mapentopts = new;
return (0);
}
bool_t
hasrestrictopt(char *opts)
{
mntoptparse_t mp;
int flags, altflags;
flags = altflags = 0;
getmnt_silent = 1;
mp = getmntopts(opts, mopts_restrict, &flags, &altflags);
if (mp == NULL)
return (FALSE);
freemntopts(mp);
return ((altflags & AUTOFS_MNT_RESTRICT) != 0);
}