freebsd-nq/usr.sbin/mountd/mountd.c
Rick Macklem 09673fc0f3 mountd(8): generate a syslog message when the "V4:" line is missing
Daniel reported that NFSv4 mounts were not working despite having
set "nfsv4_server_enable=YES" in /etc/rc.conf.  Mountd was logging a
message that there was no /etc/exports file.
He noted that creating a /etc/exports file with a "V4:" line in it
was needed make NFSv4 mounts work.
At least one "V4:" line in one of the exports(5) file(s) is needed to
make NFSv4 mounts work. This patch fixes mountd.c so that it logs a
message indicting that there is no "V4:" line in any exports(5)
file when NFSv4 mounts are enabled.
To avoid this message being generated erroneously, /etc/rc.d/mountd
is updated to make sure vfs.nfsd.server_max_nfsvers is properly set
before mountd(8) is started.

Reported by:	debdrup
PR:	253901
MFC after:	2 weeks
2021-03-08 16:08:02 -08:00

3953 lines
96 KiB
C

/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Herb Hasler and Rick Macklem at The University of Guelph.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1989, 1993\n\
The Regents of the University of California. All rights reserved.\n";
#endif /*not lint*/
#if 0
#ifndef lint
static char sccsid[] = "@(#)mountd.c 8.15 (Berkeley) 5/1/95";
#endif /*not lint*/
#endif
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/fnv_hash.h>
#include <sys/linker.h>
#include <sys/module.h>
#include <sys/mount.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <rpc/rpc.h>
#include <rpc/rpc_com.h>
#include <rpc/pmap_clnt.h>
#include <rpc/pmap_prot.h>
#include <rpcsvc/mount.h>
#include <nfs/nfsproto.h>
#include <nfs/nfssvc.h>
#include <nfsserver/nfs.h>
#include <fs/nfs/nfsport.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <grp.h>
#include <libutil.h>
#include <limits.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pathnames.h"
#include "mntopts.h"
#ifdef DEBUG
#include <stdarg.h>
#endif
/*
* Structures for keeping the mount list and export list
*/
struct mountlist {
char ml_host[MNTNAMLEN+1];
char ml_dirp[MNTPATHLEN+1];
SLIST_ENTRY(mountlist) next;
};
struct dirlist {
struct dirlist *dp_left;
struct dirlist *dp_right;
int dp_flag;
struct hostlist *dp_hosts; /* List of hosts this dir exported to */
char *dp_dirp;
};
/* dp_flag bits */
#define DP_DEFSET 0x1
#define DP_HOSTSET 0x2
/*
* maproot/mapall credentials.
* cr_smallgrps can be used for a group list up to SMALLNGROUPS in size.
* Larger group lists are malloc'd/free'd.
*/
#define SMALLNGROUPS 32
struct expcred {
uid_t cr_uid;
int cr_ngroups;
gid_t cr_smallgrps[SMALLNGROUPS];
gid_t *cr_groups;
};
struct exportlist {
struct dirlist *ex_dirl;
struct dirlist *ex_defdir;
struct grouplist *ex_grphead;
int ex_flag;
fsid_t ex_fs;
char *ex_fsdir;
char *ex_indexfile;
struct expcred ex_defanon;
uint64_t ex_defexflags;
int ex_numsecflavors;
int ex_secflavors[MAXSECFLAVORS];
int ex_defnumsecflavors;
int ex_defsecflavors[MAXSECFLAVORS];
SLIST_ENTRY(exportlist) entries;
};
/* ex_flag bits */
#define EX_LINKED 0x1
#define EX_DONE 0x2
#define EX_DEFSET 0x4
#define EX_PUBLICFH 0x8
SLIST_HEAD(exportlisthead, exportlist);
struct netmsk {
struct sockaddr_storage nt_net;
struct sockaddr_storage nt_mask;
char *nt_name;
};
union grouptypes {
struct addrinfo *gt_addrinfo;
struct netmsk gt_net;
};
struct grouplist {
int gr_type;
union grouptypes gr_ptr;
struct grouplist *gr_next;
struct expcred gr_anon;
uint64_t gr_exflags;
int gr_flag;
int gr_numsecflavors;
int gr_secflavors[MAXSECFLAVORS];
};
/* Group types */
#define GT_NULL 0x0
#define GT_HOST 0x1
#define GT_NET 0x2
#define GT_DEFAULT 0x3
#define GT_IGNORE 0x5
/* Group flags */
#define GR_FND 0x1
struct hostlist {
int ht_flag; /* Uses DP_xx bits */
struct grouplist *ht_grp;
struct hostlist *ht_next;
};
struct fhreturn {
int fhr_flag;
int fhr_vers;
nfsfh_t fhr_fh;
int fhr_numsecflavors;
int *fhr_secflavors;
};
#define GETPORT_MAXTRY 20 /* Max tries to get a port # */
/*
* How long to delay a reload of exports when there are RPC request(s)
* to process, in usec. Must be less than 1second.
*/
#define RELOADDELAY 250000
/* Global defs */
static char *add_expdir(struct dirlist **, char *, int);
static void add_dlist(struct dirlist **, struct dirlist *,
struct grouplist *, int, struct exportlist *,
struct expcred *, uint64_t);
static void add_mlist(char *, char *);
static int check_dirpath(char *);
static int check_options(struct dirlist *);
static int checkmask(struct sockaddr *sa);
static int chk_host(struct dirlist *, struct sockaddr *, int *, int *,
int *, int **);
static char *strsep_quote(char **stringp, const char *delim);
static int create_service(struct netconfig *nconf);
static void complete_service(struct netconfig *nconf, char *port_str);
static void clearout_service(void);
static void del_mlist(char *hostp, char *dirp);
static struct dirlist *dirp_search(struct dirlist *, char *);
static int do_export_mount(struct exportlist *, struct statfs *);
static int do_mount(struct exportlist *, struct grouplist *, uint64_t,
struct expcred *, char *, int, struct statfs *, int, int *);
static int do_opt(char **, char **, struct exportlist *,
struct grouplist *, int *, uint64_t *, struct expcred *);
static struct exportlist *ex_search(fsid_t *, struct exportlisthead *);
static struct exportlist *get_exp(void);
static void free_dir(struct dirlist *);
static void free_exp(struct exportlist *);
static void free_grp(struct grouplist *);
static void free_host(struct hostlist *);
static void free_v4rootexp(void);
static void get_exportlist_one(int);
static void get_exportlist(int);
static void insert_exports(struct exportlist *, struct exportlisthead *);
static void free_exports(struct exportlisthead *);
static void read_exportfile(int);
static int compare_nmount_exportlist(struct iovec *, int, char *);
static int compare_export(struct exportlist *, struct exportlist *);
static int compare_cred(struct expcred *, struct expcred *);
static int compare_secflavor(int *, int *, int);
static void delete_export(struct iovec *, int, struct statfs *, char *);
static int get_host(char *, struct grouplist *, struct grouplist *);
static struct hostlist *get_ht(void);
static int get_line(void);
static void get_mountlist(void);
static int get_net(char *, struct netmsk *, int);
static void getexp_err(struct exportlist *, struct grouplist *, const char *);
static struct grouplist *get_grp(void);
static void hang_dirp(struct dirlist *, struct grouplist *,
struct exportlist *, int, struct expcred *, uint64_t);
static void huphandler(int sig);
static int makemask(struct sockaddr_storage *ssp, int bitlen);
static void mntsrv(struct svc_req *, SVCXPRT *);
static void nextfield(char **, char **);
static void out_of_mem(void);
static void parsecred(char *, struct expcred *);
static int parsesec(char *, struct exportlist *);
static int put_exlist(struct dirlist *, XDR *, struct dirlist *,
int *, int);
static void *sa_rawaddr(struct sockaddr *sa, int *nbytes);
static int sacmp(struct sockaddr *sa1, struct sockaddr *sa2,
struct sockaddr *samask);
static int scan_tree(struct dirlist *, struct sockaddr *);
static void usage(void);
static int xdr_dir(XDR *, char *);
static int xdr_explist(XDR *, caddr_t);
static int xdr_explist_brief(XDR *, caddr_t);
static int xdr_explist_common(XDR *, caddr_t, int);
static int xdr_fhs(XDR *, caddr_t);
static int xdr_mlist(XDR *, caddr_t);
static void terminate(int);
static void cp_cred(struct expcred *, struct expcred *);
#define EXPHASH(f) (fnv_32_buf((f), sizeof(fsid_t), 0) % exphashsize)
static struct exportlisthead *exphead = NULL;
static struct exportlisthead *oldexphead = NULL;
static int exphashsize = 0;
static SLIST_HEAD(, mountlist) mlhead = SLIST_HEAD_INITIALIZER(&mlhead);
static char *exnames_default[2] = { _PATH_EXPORTS, NULL };
static char **exnames;
static char **hosts = NULL;
static int force_v2 = 0;
static int resvport_only = 1;
static int nhosts = 0;
static int dir_only = 1;
static int dolog = 0;
static int got_sighup = 0;
static int xcreated = 0;
static char *svcport_str = NULL;
static int mallocd_svcport = 0;
static int *sock_fd;
static int sock_fdcnt;
static int sock_fdpos;
static int suspend_nfsd = 0;
static int opt_flags;
static int have_v6 = 1;
static int v4root_phase = 0;
static char v4root_dirpath[PATH_MAX + 1];
static struct exportlist *v4root_ep = NULL;
static int has_publicfh = 0;
static int has_set_publicfh = 0;
static struct pidfh *pfh = NULL;
/* Bits for opt_flags above */
#define OP_MAPROOT 0x01
#define OP_MAPALL 0x02
/* 0x4 free */
#define OP_MASK 0x08
#define OP_NET 0x10
#define OP_ALLDIRS 0x40
#define OP_HAVEMASK 0x80 /* A mask was specified or inferred. */
#define OP_QUIET 0x100
#define OP_MASKLEN 0x200
#define OP_SEC 0x400
#ifdef DEBUG
static int debug = 1;
static void SYSLOG(int, const char *, ...) __printflike(2, 3);
#define syslog SYSLOG
#else
static int debug = 0;
#endif
/*
* The LOGDEBUG() syslog() calls are always compiled into the daemon.
* To enable them, create a file at _PATH_MOUNTDDEBUG. This file can be empty.
* To disable the logging, just delete the file at _PATH_MOUNTDDEBUG.
*/
static int logdebug = 0;
#define LOGDEBUG(format, ...) \
(logdebug ? syslog(LOG_DEBUG, format, ## __VA_ARGS__) : 0)
/*
* Similar to strsep(), but it allows for quoted strings
* and escaped characters.
*
* It returns the string (or NULL, if *stringp is NULL),
* which is a de-quoted version of the string if necessary.
*
* It modifies *stringp in place.
*/
static char *
strsep_quote(char **stringp, const char *delim)
{
char *srcptr, *dstptr, *retval;
char quot = 0;
if (stringp == NULL || *stringp == NULL)
return (NULL);
srcptr = dstptr = retval = *stringp;
while (*srcptr) {
/*
* We're looking for several edge cases here.
* First: if we're in quote state (quot != 0),
* then we ignore the delim characters, but otherwise
* process as normal, unless it is the quote character.
* Second: if the current character is a backslash,
* we take the next character as-is, without checking
* for delim, quote, or backslash. Exception: if the
* next character is a NUL, that's the end of the string.
* Third: if the character is a quote character, we toggle
* quote state.
* Otherwise: check the current character for NUL, or
* being in delim, and end the string if either is true.
*/
if (*srcptr == '\\') {
srcptr++;
/*
* The edge case here is if the next character
* is NUL, we want to stop processing. But if
* it's not NUL, then we simply want to copy it.
*/
if (*srcptr) {
*dstptr++ = *srcptr++;
}
continue;
}
if (quot == 0 && (*srcptr == '\'' || *srcptr == '"')) {
quot = *srcptr++;
continue;
}
if (quot && *srcptr == quot) {
/* End of the quoted part */
quot = 0;
srcptr++;
continue;
}
if (!quot && strchr(delim, *srcptr))
break;
*dstptr++ = *srcptr++;
}
*stringp = (*srcptr == '\0') ? NULL : srcptr + 1;
*dstptr = 0; /* Terminate the string */
return (retval);
}
/*
* Mountd server for NFS mount protocol as described in:
* NFS: Network File System Protocol Specification, RFC1094, Appendix A
* The optional arguments are the exports file name
* default: _PATH_EXPORTS
* and "-n" to allow nonroot mount.
*/
int
main(int argc, char **argv)
{
fd_set readfds;
struct netconfig *nconf;
char *endptr, **hosts_bak;
void *nc_handle;
pid_t otherpid;
in_port_t svcport;
int c, k, s;
int maxrec = RPC_MAXDATASIZE;
int attempt_cnt, port_len, port_pos, ret;
char **port_list;
uint64_t curtime, nexttime;
struct timeval tv;
struct timespec tp;
sigset_t sig_mask, sighup_mask;
int enable_rpcbind;
enable_rpcbind = 1;
/* Check that another mountd isn't already running. */
pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &otherpid);
if (pfh == NULL) {
if (errno == EEXIST)
errx(1, "mountd already running, pid: %d.", otherpid);
warn("cannot open or create pidfile");
}
s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (s < 0)
have_v6 = 0;
else
close(s);
while ((c = getopt(argc, argv, "2deh:lnp:RrS")) != -1)
switch (c) {
case '2':
force_v2 = 1;
break;
case 'e':
/* now a no-op, since this is the default */
break;
case 'n':
resvport_only = 0;
break;
case 'R':
/* Do not support Mount protocol */
enable_rpcbind = 0;
break;
case 'r':
dir_only = 0;
break;
case 'd':
debug = debug ? 0 : 1;
break;
case 'l':
dolog = 1;
break;
case 'p':
endptr = NULL;
svcport = (in_port_t)strtoul(optarg, &endptr, 10);
if (endptr == NULL || *endptr != '\0' ||
svcport == 0 || svcport >= IPPORT_MAX)
usage();
svcport_str = strdup(optarg);
break;
case 'h':
++nhosts;
hosts_bak = hosts;
hosts_bak = realloc(hosts, nhosts * sizeof(char *));
if (hosts_bak == NULL) {
if (hosts != NULL) {
for (k = 0; k < nhosts; k++)
free(hosts[k]);
free(hosts);
out_of_mem();
}
}
hosts = hosts_bak;
hosts[nhosts - 1] = strdup(optarg);
if (hosts[nhosts - 1] == NULL) {
for (k = 0; k < (nhosts - 1); k++)
free(hosts[k]);
free(hosts);
out_of_mem();
}
break;
case 'S':
suspend_nfsd = 1;
break;
default:
usage();
}
if (enable_rpcbind == 0) {
if (svcport_str != NULL) {
warnx("-p option not compatible with -R, ignored");
free(svcport_str);
svcport_str = NULL;
}
if (nhosts > 0) {
warnx("-h option not compatible with -R, ignored");
for (k = 0; k < nhosts; k++)
free(hosts[k]);
free(hosts);
hosts = NULL;
nhosts = 0;
}
}
if (modfind("nfsd") < 0) {
/* Not present in kernel, try loading it */
if (kldload("nfsd") < 0 || modfind("nfsd") < 0)
errx(1, "NFS server is not available");
}
argc -= optind;
argv += optind;
if (argc > 0)
exnames = argv;
else
exnames = exnames_default;
openlog("mountd", LOG_PID, LOG_DAEMON);
if (debug)
warnx("getting export list");
get_exportlist(0);
if (debug)
warnx("getting mount list");
get_mountlist();
if (debug)
warnx("here we go");
if (debug == 0) {
daemon(0, 0);
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, SIG_IGN);
}
signal(SIGHUP, huphandler);
signal(SIGTERM, terminate);
signal(SIGPIPE, SIG_IGN);
pidfile_write(pfh);
if (enable_rpcbind != 0) {
rpcb_unset(MOUNTPROG, MOUNTVERS, NULL);
rpcb_unset(MOUNTPROG, MOUNTVERS3, NULL);
rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec);
if (!resvport_only) {
if (sysctlbyname("vfs.nfsd.nfs_privport", NULL, NULL,
&resvport_only, sizeof(resvport_only)) != 0 &&
errno != ENOENT) {
syslog(LOG_ERR, "sysctl: %m");
exit(1);
}
}
/*
* If no hosts were specified, add a wildcard entry to bind to
* INADDR_ANY. Otherwise make sure 127.0.0.1 and ::1 are added
* to the list.
*/
if (nhosts == 0) {
hosts = malloc(sizeof(char *));
if (hosts == NULL)
out_of_mem();
hosts[0] = "*";
nhosts = 1;
} else {
hosts_bak = hosts;
if (have_v6) {
hosts_bak = realloc(hosts, (nhosts + 2) *
sizeof(char *));
if (hosts_bak == NULL) {
for (k = 0; k < nhosts; k++)
free(hosts[k]);
free(hosts);
out_of_mem();
} else
hosts = hosts_bak;
nhosts += 2;
hosts[nhosts - 2] = "::1";
} else {
hosts_bak = realloc(hosts, (nhosts + 1) *
sizeof(char *));
if (hosts_bak == NULL) {
for (k = 0; k < nhosts; k++)
free(hosts[k]);
free(hosts);
out_of_mem();
} else {
nhosts += 1;
hosts = hosts_bak;
}
}
hosts[nhosts - 1] = "127.0.0.1";
}
}
attempt_cnt = 1;
sock_fdcnt = 0;
sock_fd = NULL;
port_list = NULL;
port_len = 0;
if (enable_rpcbind != 0) {
nc_handle = setnetconfig();
while ((nconf = getnetconfig(nc_handle))) {
if (nconf->nc_flag & NC_VISIBLE) {
if (have_v6 == 0 && strcmp(nconf->nc_protofmly,
"inet6") == 0) {
/* DO NOTHING */
} else {
ret = create_service(nconf);
if (ret == 1)
/* Ignore this call */
continue;
if (ret < 0) {
/*
* Failed to bind port, so close
* off all sockets created and
* try again if the port# was
* dynamically assigned via
* bind(2).
*/
clearout_service();
if (mallocd_svcport != 0 &&
attempt_cnt <
GETPORT_MAXTRY) {
free(svcport_str);
svcport_str = NULL;
mallocd_svcport = 0;
} else {
errno = EADDRINUSE;
syslog(LOG_ERR,
"bindresvport_sa:"
" %m");
exit(1);
}
/*
* Start over at the first
* service.
*/
free(sock_fd);
sock_fdcnt = 0;
sock_fd = NULL;
nc_handle = setnetconfig();
attempt_cnt++;
} else if (mallocd_svcport != 0 &&
attempt_cnt == GETPORT_MAXTRY) {
/*
* For the last attempt, allow
* different port #s for each
* nconf by saving the
* svcport_str setting it back
* to NULL.
*/
port_list = realloc(port_list,
(port_len + 1) *
sizeof(char *));
if (port_list == NULL)
out_of_mem();
port_list[port_len++] =
svcport_str;
svcport_str = NULL;
mallocd_svcport = 0;
}
}
}
}
/*
* Successfully bound the ports, so call complete_service() to
* do the rest of the setup on the service(s).
*/
sock_fdpos = 0;
port_pos = 0;
nc_handle = setnetconfig();
while ((nconf = getnetconfig(nc_handle))) {
if (nconf->nc_flag & NC_VISIBLE) {
if (have_v6 == 0 && strcmp(nconf->nc_protofmly,
"inet6") == 0) {
/* DO NOTHING */
} else if (port_list != NULL) {
if (port_pos >= port_len) {
syslog(LOG_ERR, "too many"
" port#s");
exit(1);
}
complete_service(nconf,
port_list[port_pos++]);
} else
complete_service(nconf, svcport_str);
}
}
endnetconfig(nc_handle);
free(sock_fd);
if (port_list != NULL) {
for (port_pos = 0; port_pos < port_len; port_pos++)
free(port_list[port_pos]);
free(port_list);
}
if (xcreated == 0) {
syslog(LOG_ERR, "could not create any services");
exit(1);
}
}
/* Expand svc_run() here so that we can call get_exportlist(). */
curtime = nexttime = 0;
sigemptyset(&sighup_mask);
sigaddset(&sighup_mask, SIGHUP);
for (;;) {
clock_gettime(CLOCK_MONOTONIC, &tp);
curtime = tp.tv_sec;
curtime = curtime * 1000000 + tp.tv_nsec / 1000;
sigprocmask(SIG_BLOCK, &sighup_mask, &sig_mask);
if (got_sighup && curtime >= nexttime) {
got_sighup = 0;
sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
get_exportlist(1);
clock_gettime(CLOCK_MONOTONIC, &tp);
nexttime = tp.tv_sec;
nexttime = nexttime * 1000000 + tp.tv_nsec / 1000 +
RELOADDELAY;
} else
sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
/*
* If a reload is pending, poll for received request(s),
* otherwise set a RELOADDELAY timeout, since a SIGHUP
* could be processed between the got_sighup test and
* the select() system call.
*/
tv.tv_sec = 0;
if (got_sighup)
tv.tv_usec = 0;
else
tv.tv_usec = RELOADDELAY;
if (enable_rpcbind != 0) {
readfds = svc_fdset;
switch (select(svc_maxfd + 1, &readfds, NULL, NULL,
&tv)) {
case -1:
if (errno == EINTR) {
/* Allow a reload now. */
nexttime = 0;
continue;
}
syslog(LOG_ERR, "mountd died: select: %m");
exit(1);
case 0:
/* Allow a reload now. */
nexttime = 0;
continue;
default:
svc_getreqset(&readfds);
}
} else {
/* Simply wait for a signal. */
sigsuspend(&sig_mask);
}
}
}
/*
* This routine creates and binds sockets on the appropriate
* addresses. It gets called one time for each transport.
* It returns 0 upon success, 1 for ingore the call and -1 to indicate
* bind failed with EADDRINUSE.
* Any file descriptors that have been created are stored in sock_fd and
* the total count of them is maintained in sock_fdcnt.
*/
static int
create_service(struct netconfig *nconf)
{
struct addrinfo hints, *res = NULL;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
struct __rpc_sockinfo si;
int aicode;
int fd;
int nhostsbak;
int one = 1;
int r;
u_int32_t host_addr[4]; /* IPv4 or IPv6 */
int mallocd_res;
if ((nconf->nc_semantics != NC_TPI_CLTS) &&
(nconf->nc_semantics != NC_TPI_COTS) &&
(nconf->nc_semantics != NC_TPI_COTS_ORD))
return (1); /* not my type */
/*
* XXX - using RPC library internal functions.
*/
if (!__rpc_nconf2sockinfo(nconf, &si)) {
syslog(LOG_ERR, "cannot get information for %s",
nconf->nc_netid);
return (1);
}
/* Get mountd's address on this transport */
memset(&hints, 0, sizeof hints);
hints.ai_family = si.si_af;
hints.ai_socktype = si.si_socktype;
hints.ai_protocol = si.si_proto;
/*
* Bind to specific IPs if asked to
*/
nhostsbak = nhosts;
while (nhostsbak > 0) {
--nhostsbak;
sock_fd = realloc(sock_fd, (sock_fdcnt + 1) * sizeof(int));
if (sock_fd == NULL)
out_of_mem();
sock_fd[sock_fdcnt++] = -1; /* Set invalid for now. */
mallocd_res = 0;
hints.ai_flags = AI_PASSIVE;
/*
* XXX - using RPC library internal functions.
*/
if ((fd = __rpc_nconf2fd(nconf)) < 0) {
int non_fatal = 0;
if (errno == EAFNOSUPPORT &&
nconf->nc_semantics != NC_TPI_CLTS)
non_fatal = 1;
syslog(non_fatal ? LOG_DEBUG : LOG_ERR,
"cannot create socket for %s", nconf->nc_netid);
if (non_fatal != 0)
continue;
exit(1);
}
switch (hints.ai_family) {
case AF_INET:
if (inet_pton(AF_INET, hosts[nhostsbak],
host_addr) == 1) {
hints.ai_flags |= AI_NUMERICHOST;
} else {
/*
* Skip if we have an AF_INET6 address.
*/
if (inet_pton(AF_INET6, hosts[nhostsbak],
host_addr) == 1) {
close(fd);
continue;
}
}
break;
case AF_INET6:
if (inet_pton(AF_INET6, hosts[nhostsbak],
host_addr) == 1) {
hints.ai_flags |= AI_NUMERICHOST;
} else {
/*
* Skip if we have an AF_INET address.
*/
if (inet_pton(AF_INET, hosts[nhostsbak],
host_addr) == 1) {
close(fd);
continue;
}
}
/*
* We're doing host-based access checks here, so don't
* allow v4-in-v6 to confuse things. The kernel will
* disable it by default on NFS sockets too.
*/
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one,
sizeof one) < 0) {
syslog(LOG_ERR,
"can't disable v4-in-v6 on IPv6 socket");
exit(1);
}
break;
default:
break;
}
/*
* If no hosts were specified, just bind to INADDR_ANY
*/
if (strcmp("*", hosts[nhostsbak]) == 0) {
if (svcport_str == NULL) {
res = malloc(sizeof(struct addrinfo));
if (res == NULL)
out_of_mem();
mallocd_res = 1;
res->ai_flags = hints.ai_flags;
res->ai_family = hints.ai_family;
res->ai_protocol = hints.ai_protocol;
switch (res->ai_family) {
case AF_INET:
sin = malloc(sizeof(struct sockaddr_in));
if (sin == NULL)
out_of_mem();
sin->sin_family = AF_INET;
sin->sin_port = htons(0);
sin->sin_addr.s_addr = htonl(INADDR_ANY);
res->ai_addr = (struct sockaddr*) sin;
res->ai_addrlen = (socklen_t)
sizeof(struct sockaddr_in);
break;
case AF_INET6:
sin6 = malloc(sizeof(struct sockaddr_in6));
if (sin6 == NULL)
out_of_mem();
sin6->sin6_family = AF_INET6;
sin6->sin6_port = htons(0);
sin6->sin6_addr = in6addr_any;
res->ai_addr = (struct sockaddr*) sin6;
res->ai_addrlen = (socklen_t)
sizeof(struct sockaddr_in6);
break;
default:
syslog(LOG_ERR, "bad addr fam %d",
res->ai_family);
exit(1);
}
} else {
if ((aicode = getaddrinfo(NULL, svcport_str,
&hints, &res)) != 0) {
syslog(LOG_ERR,
"cannot get local address for %s: %s",
nconf->nc_netid,
gai_strerror(aicode));
close(fd);
continue;
}
}
} else {
if ((aicode = getaddrinfo(hosts[nhostsbak], svcport_str,
&hints, &res)) != 0) {
syslog(LOG_ERR,
"cannot get local address for %s: %s",
nconf->nc_netid, gai_strerror(aicode));
close(fd);
continue;
}
}
/* Store the fd. */
sock_fd[sock_fdcnt - 1] = fd;
/* Now, attempt the bind. */
r = bindresvport_sa(fd, res->ai_addr);
if (r != 0) {
if (errno == EADDRINUSE && mallocd_svcport != 0) {
if (mallocd_res != 0) {
free(res->ai_addr);
free(res);
} else
freeaddrinfo(res);
return (-1);
}
syslog(LOG_ERR, "bindresvport_sa: %m");
exit(1);
}
if (svcport_str == NULL) {
svcport_str = malloc(NI_MAXSERV * sizeof(char));
if (svcport_str == NULL)
out_of_mem();
mallocd_svcport = 1;
if (getnameinfo(res->ai_addr,
res->ai_addr->sa_len, NULL, NI_MAXHOST,
svcport_str, NI_MAXSERV * sizeof(char),
NI_NUMERICHOST | NI_NUMERICSERV))
errx(1, "Cannot get port number");
}
if (mallocd_res != 0) {
free(res->ai_addr);
free(res);
} else
freeaddrinfo(res);
res = NULL;
}
return (0);
}
/*
* Called after all the create_service() calls have succeeded, to complete
* the setup and registration.
*/
static void
complete_service(struct netconfig *nconf, char *port_str)
{
struct addrinfo hints, *res = NULL;
struct __rpc_sockinfo si;
struct netbuf servaddr;
SVCXPRT *transp = NULL;
int aicode, fd, nhostsbak;
int registered = 0;
if ((nconf->nc_semantics != NC_TPI_CLTS) &&
(nconf->nc_semantics != NC_TPI_COTS) &&
(nconf->nc_semantics != NC_TPI_COTS_ORD))
return; /* not my type */
/*
* XXX - using RPC library internal functions.
*/
if (!__rpc_nconf2sockinfo(nconf, &si)) {
syslog(LOG_ERR, "cannot get information for %s",
nconf->nc_netid);
return;
}
nhostsbak = nhosts;
while (nhostsbak > 0) {
--nhostsbak;
if (sock_fdpos >= sock_fdcnt) {
/* Should never happen. */
syslog(LOG_ERR, "Ran out of socket fd's");
return;
}
fd = sock_fd[sock_fdpos++];
if (fd < 0)
continue;
/*
* Using -1 tells listen(2) to use
* kern.ipc.soacceptqueue for the backlog.
*/
if (nconf->nc_semantics != NC_TPI_CLTS)
listen(fd, -1);
if (nconf->nc_semantics == NC_TPI_CLTS )
transp = svc_dg_create(fd, 0, 0);
else
transp = svc_vc_create(fd, RPC_MAXDATASIZE,
RPC_MAXDATASIZE);
if (transp != (SVCXPRT *) NULL) {
if (!svc_reg(transp, MOUNTPROG, MOUNTVERS, mntsrv,
NULL))
syslog(LOG_ERR,
"can't register %s MOUNTVERS service",
nconf->nc_netid);
if (!force_v2) {
if (!svc_reg(transp, MOUNTPROG, MOUNTVERS3,
mntsrv, NULL))
syslog(LOG_ERR,
"can't register %s MOUNTVERS3 service",
nconf->nc_netid);
}
} else
syslog(LOG_WARNING, "can't create %s services",
nconf->nc_netid);
if (registered == 0) {
registered = 1;
memset(&hints, 0, sizeof hints);
hints.ai_flags = AI_PASSIVE;
hints.ai_family = si.si_af;
hints.ai_socktype = si.si_socktype;
hints.ai_protocol = si.si_proto;
if ((aicode = getaddrinfo(NULL, port_str, &hints,
&res)) != 0) {
syslog(LOG_ERR, "cannot get local address: %s",
gai_strerror(aicode));
exit(1);
}
servaddr.buf = malloc(res->ai_addrlen);
memcpy(servaddr.buf, res->ai_addr, res->ai_addrlen);
servaddr.len = res->ai_addrlen;
rpcb_set(MOUNTPROG, MOUNTVERS, nconf, &servaddr);
rpcb_set(MOUNTPROG, MOUNTVERS3, nconf, &servaddr);
xcreated++;
freeaddrinfo(res);
}
} /* end while */
}
/*
* Clear out sockets after a failure to bind one of them, so that the
* cycle of socket creation/binding can start anew.
*/
static void
clearout_service(void)
{
int i;
for (i = 0; i < sock_fdcnt; i++) {
if (sock_fd[i] >= 0) {
shutdown(sock_fd[i], SHUT_RDWR);
close(sock_fd[i]);
}
}
}
static void
usage(void)
{
fprintf(stderr,
"usage: mountd [-2] [-d] [-e] [-l] [-n] [-p <port>] [-r] "
"[-S] [-h <bindip>] [export_file ...]\n");
exit(1);
}
/*
* The mount rpc service
*/
void
mntsrv(struct svc_req *rqstp, SVCXPRT *transp)
{
struct exportlist *ep;
struct dirlist *dp;
struct fhreturn fhr;
struct stat stb;
struct statfs fsb;
char host[NI_MAXHOST], numerichost[NI_MAXHOST];
int lookup_failed = 1;
struct sockaddr *saddr;
u_short sport;
char rpcpath[MNTPATHLEN + 1], dirpath[MAXPATHLEN];
int defset, hostset;
long bad = 0;
sigset_t sighup_mask;
int numsecflavors, *secflavorsp;
sigemptyset(&sighup_mask);
sigaddset(&sighup_mask, SIGHUP);
saddr = svc_getrpccaller(transp)->buf;
switch (saddr->sa_family) {
case AF_INET6:
sport = ntohs(((struct sockaddr_in6 *)saddr)->sin6_port);
break;
case AF_INET:
sport = ntohs(((struct sockaddr_in *)saddr)->sin_port);
break;
default:
syslog(LOG_ERR, "request from unknown address family");
return;
}
switch (rqstp->rq_proc) {
case MOUNTPROC_MNT:
case MOUNTPROC_UMNT:
case MOUNTPROC_UMNTALL:
lookup_failed = getnameinfo(saddr, saddr->sa_len, host,
sizeof host, NULL, 0, 0);
}
getnameinfo(saddr, saddr->sa_len, numerichost,
sizeof numerichost, NULL, 0, NI_NUMERICHOST);
switch (rqstp->rq_proc) {
case NULLPROC:
if (!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL))
syslog(LOG_ERR, "can't send reply");
return;
case MOUNTPROC_MNT:
if (sport >= IPPORT_RESERVED && resvport_only) {
syslog(LOG_NOTICE,
"mount request from %s from unprivileged port",
numerichost);
svcerr_weakauth(transp);
return;
}
if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) {
syslog(LOG_NOTICE, "undecodable mount request from %s",
numerichost);
svcerr_decode(transp);
return;
}
/*
* Get the real pathname and make sure it is a directory
* or a regular file if the -r option was specified
* and it exists.
*/
if (realpath(rpcpath, dirpath) == NULL ||
stat(dirpath, &stb) < 0 ||
statfs(dirpath, &fsb) < 0) {
chdir("/"); /* Just in case realpath doesn't */
syslog(LOG_NOTICE,
"mount request from %s for non existent path %s",
numerichost, dirpath);
if (debug)
warnx("stat failed on %s", dirpath);
bad = ENOENT; /* We will send error reply later */
}
if (!bad &&
!S_ISDIR(stb.st_mode) &&
(dir_only || !S_ISREG(stb.st_mode))) {
syslog(LOG_NOTICE,
"mount request from %s for non-directory path %s",
numerichost, dirpath);
if (debug)
warnx("mounting non-directory %s", dirpath);
bad = ENOTDIR; /* We will send error reply later */
}
/* Check in the exports list */
sigprocmask(SIG_BLOCK, &sighup_mask, NULL);
if (bad)
ep = NULL;
else
ep = ex_search(&fsb.f_fsid, exphead);
hostset = defset = 0;
if (ep && (chk_host(ep->ex_defdir, saddr, &defset, &hostset,
&numsecflavors, &secflavorsp) ||
((dp = dirp_search(ep->ex_dirl, dirpath)) &&
chk_host(dp, saddr, &defset, &hostset, &numsecflavors,
&secflavorsp)) ||
(defset && scan_tree(ep->ex_defdir, saddr) == 0 &&
scan_tree(ep->ex_dirl, saddr) == 0))) {
if (bad) {
if (!svc_sendreply(transp, (xdrproc_t)xdr_long,
(caddr_t)&bad))
syslog(LOG_ERR, "can't send reply");
sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
return;
}
if (hostset & DP_HOSTSET) {
fhr.fhr_flag = hostset;
fhr.fhr_numsecflavors = numsecflavors;
fhr.fhr_secflavors = secflavorsp;
} else {
fhr.fhr_flag = defset;
fhr.fhr_numsecflavors = ep->ex_defnumsecflavors;
fhr.fhr_secflavors = ep->ex_defsecflavors;
}
fhr.fhr_vers = rqstp->rq_vers;
/* Get the file handle */
memset(&fhr.fhr_fh, 0, sizeof(nfsfh_t));
if (getfh(dirpath, (fhandle_t *)&fhr.fhr_fh) < 0) {
bad = errno;
syslog(LOG_ERR, "can't get fh for %s", dirpath);
if (!svc_sendreply(transp, (xdrproc_t)xdr_long,
(caddr_t)&bad))
syslog(LOG_ERR, "can't send reply");
sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
return;
}
if (!svc_sendreply(transp, (xdrproc_t)xdr_fhs,
(caddr_t)&fhr))
syslog(LOG_ERR, "can't send reply");
if (!lookup_failed)
add_mlist(host, dirpath);
else
add_mlist(numerichost, dirpath);
if (debug)
warnx("mount successful");
if (dolog)
syslog(LOG_NOTICE,
"mount request succeeded from %s for %s",
numerichost, dirpath);
} else {
if (!bad)
bad = EACCES;
syslog(LOG_NOTICE,
"mount request denied from %s for %s",
numerichost, dirpath);
}
if (bad && !svc_sendreply(transp, (xdrproc_t)xdr_long,
(caddr_t)&bad))
syslog(LOG_ERR, "can't send reply");
sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
return;
case MOUNTPROC_DUMP:
if (!svc_sendreply(transp, (xdrproc_t)xdr_mlist, (caddr_t)NULL))
syslog(LOG_ERR, "can't send reply");
else if (dolog)
syslog(LOG_NOTICE,
"dump request succeeded from %s",
numerichost);
return;
case MOUNTPROC_UMNT:
if (sport >= IPPORT_RESERVED && resvport_only) {
syslog(LOG_NOTICE,
"umount request from %s from unprivileged port",
numerichost);
svcerr_weakauth(transp);
return;
}
if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) {
syslog(LOG_NOTICE, "undecodable umount request from %s",
numerichost);
svcerr_decode(transp);
return;
}
if (realpath(rpcpath, dirpath) == NULL) {
syslog(LOG_NOTICE, "umount request from %s "
"for non existent path %s",
numerichost, dirpath);
}
if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL))
syslog(LOG_ERR, "can't send reply");
if (!lookup_failed)
del_mlist(host, dirpath);
del_mlist(numerichost, dirpath);
if (dolog)
syslog(LOG_NOTICE,
"umount request succeeded from %s for %s",
numerichost, dirpath);
return;
case MOUNTPROC_UMNTALL:
if (sport >= IPPORT_RESERVED && resvport_only) {
syslog(LOG_NOTICE,
"umountall request from %s from unprivileged port",
numerichost);
svcerr_weakauth(transp);
return;
}
if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL))
syslog(LOG_ERR, "can't send reply");
if (!lookup_failed)
del_mlist(host, NULL);
del_mlist(numerichost, NULL);
if (dolog)
syslog(LOG_NOTICE,
"umountall request succeeded from %s",
numerichost);
return;
case MOUNTPROC_EXPORT:
if (!svc_sendreply(transp, (xdrproc_t)xdr_explist, (caddr_t)NULL))
if (!svc_sendreply(transp, (xdrproc_t)xdr_explist_brief,
(caddr_t)NULL))
syslog(LOG_ERR, "can't send reply");
if (dolog)
syslog(LOG_NOTICE,
"export request succeeded from %s",
numerichost);
return;
default:
svcerr_noproc(transp);
return;
}
}
/*
* Xdr conversion for a dirpath string
*/
static int
xdr_dir(XDR *xdrsp, char *dirp)
{
return (xdr_string(xdrsp, &dirp, MNTPATHLEN));
}
/*
* Xdr routine to generate file handle reply
*/
static int
xdr_fhs(XDR *xdrsp, caddr_t cp)
{
struct fhreturn *fhrp = (struct fhreturn *)cp;
u_long ok = 0, len, auth;
int i;
if (!xdr_long(xdrsp, &ok))
return (0);
switch (fhrp->fhr_vers) {
case 1:
return (xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, NFSX_V2FH));
case 3:
len = NFSX_V3FH;
if (!xdr_long(xdrsp, &len))
return (0);
if (!xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, len))
return (0);
if (fhrp->fhr_numsecflavors) {
if (!xdr_int(xdrsp, &fhrp->fhr_numsecflavors))
return (0);
for (i = 0; i < fhrp->fhr_numsecflavors; i++)
if (!xdr_int(xdrsp, &fhrp->fhr_secflavors[i]))
return (0);
return (1);
} else {
auth = AUTH_SYS;
len = 1;
if (!xdr_long(xdrsp, &len))
return (0);
return (xdr_long(xdrsp, &auth));
}
}
return (0);
}
static int
xdr_mlist(XDR *xdrsp, caddr_t cp __unused)
{
struct mountlist *mlp;
int true = 1;
int false = 0;
char *strp;
SLIST_FOREACH(mlp, &mlhead, next) {
if (!xdr_bool(xdrsp, &true))
return (0);
strp = &mlp->ml_host[0];
if (!xdr_string(xdrsp, &strp, MNTNAMLEN))
return (0);
strp = &mlp->ml_dirp[0];
if (!xdr_string(xdrsp, &strp, MNTPATHLEN))
return (0);
}
if (!xdr_bool(xdrsp, &false))
return (0);
return (1);
}
/*
* Xdr conversion for export list
*/
static int
xdr_explist_common(XDR *xdrsp, caddr_t cp __unused, int brief)
{
struct exportlist *ep;
int false = 0;
int putdef;
sigset_t sighup_mask;
int i;
sigemptyset(&sighup_mask);
sigaddset(&sighup_mask, SIGHUP);
sigprocmask(SIG_BLOCK, &sighup_mask, NULL);
for (i = 0; i < exphashsize; i++)
SLIST_FOREACH(ep, &exphead[i], entries) {
putdef = 0;
if (put_exlist(ep->ex_dirl, xdrsp, ep->ex_defdir,
&putdef, brief))
goto errout;
if (ep->ex_defdir && putdef == 0 &&
put_exlist(ep->ex_defdir, xdrsp, NULL,
&putdef, brief))
goto errout;
}
sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
if (!xdr_bool(xdrsp, &false))
return (0);
return (1);
errout:
sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL);
return (0);
}
/*
* Called from xdr_explist() to traverse the tree and export the
* directory paths.
*/
static int
put_exlist(struct dirlist *dp, XDR *xdrsp, struct dirlist *adp, int *putdefp,
int brief)
{
struct grouplist *grp;
struct hostlist *hp;
int true = 1;
int false = 0;
int gotalldir = 0;
char *strp;
if (dp) {
if (put_exlist(dp->dp_left, xdrsp, adp, putdefp, brief))
return (1);
if (!xdr_bool(xdrsp, &true))
return (1);
strp = dp->dp_dirp;
if (!xdr_string(xdrsp, &strp, MNTPATHLEN))
return (1);
if (adp && !strcmp(dp->dp_dirp, adp->dp_dirp)) {
gotalldir = 1;
*putdefp = 1;
}
if (brief) {
if (!xdr_bool(xdrsp, &true))
return (1);
strp = "(...)";
if (!xdr_string(xdrsp, &strp, MNTPATHLEN))
return (1);
} else if ((dp->dp_flag & DP_DEFSET) == 0 &&
(gotalldir == 0 || (adp->dp_flag & DP_DEFSET) == 0)) {
hp = dp->dp_hosts;
while (hp) {
grp = hp->ht_grp;
if (grp->gr_type == GT_HOST) {
if (!xdr_bool(xdrsp, &true))
return (1);
strp = grp->gr_ptr.gt_addrinfo->ai_canonname;
if (!xdr_string(xdrsp, &strp,
MNTNAMLEN))
return (1);
} else if (grp->gr_type == GT_NET) {
if (!xdr_bool(xdrsp, &true))
return (1);
strp = grp->gr_ptr.gt_net.nt_name;
if (!xdr_string(xdrsp, &strp,
MNTNAMLEN))
return (1);
}
hp = hp->ht_next;
if (gotalldir && hp == (struct hostlist *)NULL) {
hp = adp->dp_hosts;
gotalldir = 0;
}
}
}
if (!xdr_bool(xdrsp, &false))
return (1);
if (put_exlist(dp->dp_right, xdrsp, adp, putdefp, brief))
return (1);
}
return (0);
}
static int
xdr_explist(XDR *xdrsp, caddr_t cp)
{
return xdr_explist_common(xdrsp, cp, 0);
}
static int
xdr_explist_brief(XDR *xdrsp, caddr_t cp)
{
return xdr_explist_common(xdrsp, cp, 1);
}
static char *line;
static size_t linesize;
static FILE *exp_file;
/*
* Get the export list from one, currently open file
*/
static void
get_exportlist_one(int passno)
{
struct exportlist *ep;
struct grouplist *grp, *tgrp, *savgrp;
struct dirlist *dirhead;
struct statfs fsb;
struct expcred anon;
char *cp, *endcp, *dirp, *hst, *usr, *dom, savedc;
int len, has_host, got_nondir, dirplen, netgrp;
uint64_t exflags;
v4root_phase = 0;
anon.cr_groups = NULL;
dirhead = (struct dirlist *)NULL;
while (get_line()) {
if (debug)
warnx("got line %s", line);
cp = line;
nextfield(&cp, &endcp);
if (*cp == '#')
goto nextline;
/*
* Set defaults.
*/
has_host = FALSE;
anon.cr_groups = anon.cr_smallgrps;
anon.cr_uid = UID_NOBODY;
anon.cr_ngroups = 1;
anon.cr_groups[0] = GID_NOGROUP;
exflags = MNT_EXPORTED;
got_nondir = 0;
opt_flags = 0;
ep = (struct exportlist *)NULL;
dirp = NULL;
/*
* Handle the V4 root dir.
*/
if (*cp == 'V' && *(cp + 1) == '4' && *(cp + 2) == ':') {
/*
* V4: just indicates that it is the v4 root point,
* so skip over that and set v4root_phase.
*/
if (v4root_phase > 0) {
syslog(LOG_ERR, "V4:duplicate line, ignored");
goto nextline;
}
v4root_phase = 1;
cp += 3;
nextfield(&cp, &endcp);
}
/*
* Create new exports list entry
*/
len = endcp-cp;
tgrp = grp = get_grp();
while (len > 0) {
if (len > MNTNAMLEN) {
getexp_err(ep, tgrp, "mountpoint too long");
goto nextline;
}
if (*cp == '-') {
if (ep == (struct exportlist *)NULL) {
getexp_err(ep, tgrp,
"flag before export path definition");
goto nextline;
}
if (debug)
warnx("doing opt %s", cp);
got_nondir = 1;
if (do_opt(&cp, &endcp, ep, grp, &has_host,
&exflags, &anon)) {
getexp_err(ep, tgrp, NULL);
goto nextline;
}
} else if (*cp == '/') {
savedc = *endcp;
*endcp = '\0';
if (v4root_phase > 1) {
if (dirp != NULL) {
getexp_err(ep, tgrp, "Multiple V4 dirs");
goto nextline;
}
}
if (check_dirpath(cp) &&
statfs(cp, &fsb) >= 0) {
if ((fsb.f_flags & MNT_AUTOMOUNTED) != 0)
syslog(LOG_ERR, "Warning: exporting of "
"automounted fs %s not supported", cp);
if (got_nondir) {
getexp_err(ep, tgrp, "dirs must be first");
goto nextline;
}
if (v4root_phase == 1) {
if (dirp != NULL) {
getexp_err(ep, tgrp, "Multiple V4 dirs");
goto nextline;
}
if (strlen(v4root_dirpath) == 0) {
strlcpy(v4root_dirpath, cp,
sizeof (v4root_dirpath));
} else if (strcmp(v4root_dirpath, cp)
!= 0) {
syslog(LOG_ERR,
"different V4 dirpath %s", cp);
getexp_err(ep, tgrp, NULL);
goto nextline;
}
dirp = cp;
v4root_phase = 2;
got_nondir = 1;
ep = get_exp();
} else {
if (ep) {
if (fsidcmp(&ep->ex_fs, &fsb.f_fsid)
!= 0) {
getexp_err(ep, tgrp,
"fsid mismatch");
goto nextline;
}
} else {
/*
* See if this directory is already
* in the list.
*/
ep = ex_search(&fsb.f_fsid, exphead);
if (ep == (struct exportlist *)NULL) {
ep = get_exp();
ep->ex_fs = fsb.f_fsid;
ep->ex_fsdir = strdup(fsb.f_mntonname);
if (ep->ex_fsdir == NULL)
out_of_mem();
if (debug)
warnx(
"making new ep fs=0x%x,0x%x",
fsb.f_fsid.val[0],
fsb.f_fsid.val[1]);
} else if (debug)
warnx("found ep fs=0x%x,0x%x",
fsb.f_fsid.val[0],
fsb.f_fsid.val[1]);
}
/*
* Add dirpath to export mount point.
*/
dirp = add_expdir(&dirhead, cp, len);
dirplen = len;
}
} else {
getexp_err(ep, tgrp,
"symbolic link in export path or statfs failed");
goto nextline;
}
*endcp = savedc;
} else {
savedc = *endcp;
*endcp = '\0';
got_nondir = 1;
if (ep == (struct exportlist *)NULL) {
getexp_err(ep, tgrp,
"host(s) before export path definition");
goto nextline;
}
/*
* Get the host or netgroup.
*/
setnetgrent(cp);
netgrp = getnetgrent(&hst, &usr, &dom);
do {
if (has_host) {
grp->gr_next = get_grp();
grp = grp->gr_next;
}
if (netgrp) {
if (hst == 0) {
syslog(LOG_ERR,
"null hostname in netgroup %s, skipping", cp);
grp->gr_type = GT_IGNORE;
} else if (get_host(hst, grp, tgrp)) {
syslog(LOG_ERR,
"bad host %s in netgroup %s, skipping", hst, cp);
grp->gr_type = GT_IGNORE;
}
} else if (get_host(cp, grp, tgrp)) {
syslog(LOG_ERR, "bad host %s, skipping", cp);
grp->gr_type = GT_IGNORE;
}
has_host = TRUE;
} while (netgrp && getnetgrent(&hst, &usr, &dom));
endnetgrent();
*endcp = savedc;
}
cp = endcp;
nextfield(&cp, &endcp);
len = endcp - cp;
}
if (check_options(dirhead)) {
getexp_err(ep, tgrp, NULL);
goto nextline;
}
if (!has_host) {
grp->gr_type = GT_DEFAULT;
if (debug)
warnx("adding a default entry");
/*
* Don't allow a network export coincide with a list of
* host(s) on the same line.
*/
} else if ((opt_flags & OP_NET) && tgrp->gr_next) {
getexp_err(ep, tgrp, "network/host conflict");
goto nextline;
/*
* If an export list was specified on this line, make sure
* that we have at least one valid entry, otherwise skip it.
*/
} else {
grp = tgrp;
while (grp && grp->gr_type == GT_IGNORE)
grp = grp->gr_next;
if (! grp) {
getexp_err(ep, tgrp, "no valid entries");
goto nextline;
}
}
if (v4root_phase == 1) {
getexp_err(ep, tgrp, "V4:root, no dirp, ignored");
goto nextline;
}
/*
* Loop through hosts, pushing the exports into the kernel.
* After loop, tgrp points to the start of the list and
* grp points to the last entry in the list.
* Do not do the do_mount() for passno == 1, since the
* second pass will do it, as required.
*/
grp = tgrp;
do {
grp->gr_exflags = exflags;
cp_cred(&grp->gr_anon, &anon);
if (v4root_phase == 2 && passno == 0)
LOGDEBUG("do_mount v4root");
if (passno == 0 && do_mount(ep, grp, exflags, &anon,
dirp, dirplen, &fsb, ep->ex_numsecflavors,
ep->ex_secflavors)) {
getexp_err(ep, tgrp, NULL);
goto nextline;
}
} while (grp->gr_next && (grp = grp->gr_next));
/*
* For V4: don't enter in mount lists.
*/
if (v4root_phase > 0 && v4root_phase <= 2) {
/*
* These structures are used for the reload,
* so save them for that case. Otherwise, just
* free them up now.
*/
if (passno == 1 && ep != NULL) {
savgrp = tgrp;
while (tgrp != NULL) {
/*
* Save the security flavors and exflags
* for this host set in the groups.
*/
tgrp->gr_numsecflavors =
ep->ex_numsecflavors;
if (ep->ex_numsecflavors > 0)
memcpy(tgrp->gr_secflavors,
ep->ex_secflavors,
sizeof(ep->ex_secflavors));
tgrp = tgrp->gr_next;
}
if (v4root_ep == NULL) {
v4root_ep = ep;
ep = NULL; /* Don't free below. */
}
grp->gr_next = v4root_ep->ex_grphead;
v4root_ep->ex_grphead = savgrp;
}
if (ep != NULL)
free_exp(ep);
while (tgrp != NULL) {
grp = tgrp;
tgrp = tgrp->gr_next;
free_grp(grp);
}
goto nextline;
}
/*
* Success. Update the data structures.
*/
if (has_host) {
hang_dirp(dirhead, tgrp, ep, opt_flags, &anon, exflags);
grp->gr_next = ep->ex_grphead;
ep->ex_grphead = tgrp;
} else {
hang_dirp(dirhead, (struct grouplist *)NULL, ep,
opt_flags, &anon, exflags);
free_grp(grp);
}
dirhead = (struct dirlist *)NULL;
if ((ep->ex_flag & EX_LINKED) == 0) {
insert_exports(ep, exphead);
ep->ex_flag |= EX_LINKED;
}
nextline:
v4root_phase = 0;
if (dirhead) {
free_dir(dirhead);
dirhead = (struct dirlist *)NULL;
}
if (anon.cr_groups != anon.cr_smallgrps) {
free(anon.cr_groups);
anon.cr_groups = NULL;
}
}
}
/*
* Get the export list from all specified files
*/
static void
get_exportlist(int passno)
{
struct export_args export;
struct iovec *iov;
struct statfs *mntbufp;
char errmsg[255];
int error, i, nfs_maxvers, num;
int iovlen;
struct nfsex_args eargs;
FILE *debug_file;
size_t nfs_maxvers_size;
if ((debug_file = fopen(_PATH_MOUNTDDEBUG, "r")) != NULL) {
fclose(debug_file);
logdebug = 1;
} else
logdebug = 0;
LOGDEBUG("passno=%d", passno);
v4root_dirpath[0] = '\0';
free_v4rootexp();
if (passno == 1) {
/*
* Save the current lists as old ones, so that the new lists
* can be compared with the old ones in the 2nd pass.
*/
for (i = 0; i < exphashsize; i++) {
SLIST_FIRST(&oldexphead[i]) = SLIST_FIRST(&exphead[i]);
SLIST_INIT(&exphead[i]);
}
/* Note that the public fh has not yet been set. */
has_set_publicfh = 0;
/* Read the export file(s) and process them */
read_exportfile(passno);
} else {
/*
* Just make the old lists empty.
* exphashsize == 0 for the first call, before oldexphead
* has been initialized-->loop won't be executed.
*/
for (i = 0; i < exphashsize; i++)
SLIST_INIT(&oldexphead[i]);
}
bzero(&export, sizeof(export));
export.ex_flags = MNT_DELEXPORT;
iov = NULL;
iovlen = 0;
bzero(errmsg, sizeof(errmsg));
if (suspend_nfsd != 0)
(void)nfssvc(NFSSVC_SUSPENDNFSD, NULL);
/*
* Delete the old V4 root dir.
*/
bzero(&eargs, sizeof (eargs));
eargs.export.ex_flags = MNT_DELEXPORT;
if (nfssvc(NFSSVC_V4ROOTEXPORT | NFSSVC_NEWSTRUCT, (caddr_t)&eargs) < 0 &&
errno != ENOENT)
syslog(LOG_ERR, "Can't delete exports for V4:");
build_iovec(&iov, &iovlen, "fstype", NULL, 0);
build_iovec(&iov, &iovlen, "fspath", NULL, 0);
build_iovec(&iov, &iovlen, "from", NULL, 0);
build_iovec(&iov, &iovlen, "update", NULL, 0);
build_iovec(&iov, &iovlen, "export", &export,
sizeof(export));
build_iovec(&iov, &iovlen, "errmsg", errmsg,
sizeof(errmsg));
/*
* For passno == 1, compare the old and new lists updating the kernel
* exports for any cases that have changed.
* This call is doing the second pass through the lists.
* If it fails, fall back on the bulk reload.
*/
if (passno == 1 && compare_nmount_exportlist(iov, iovlen, errmsg) ==
0) {
LOGDEBUG("compareok");
/* Free up the old lists. */
free_exports(oldexphead);
} else {
LOGDEBUG("doing passno=0");
/*
* Clear flag that notes if a public fh has been exported.
* It is set by do_mount() if MNT_EXPUBLIC is set for the entry.
*/
has_publicfh = 0;
/* exphead == NULL if not yet allocated (first call). */
if (exphead != NULL) {
/*
* First, get rid of the old lists.
*/
free_exports(exphead);
free_exports(oldexphead);
}
/*
* And delete exports that are in the kernel for all local
* filesystems.
* XXX: Should know how to handle all local exportable
* filesystems.
*/
num = getmntinfo(&mntbufp, MNT_NOWAIT);
/* Allocate hash tables, for first call. */
if (exphead == NULL) {
/* Target an average linked list length of 10. */
exphashsize = num / 10;
if (exphashsize < 1)
exphashsize = 1;
else if (exphashsize > 100000)
exphashsize = 100000;
exphead = malloc(exphashsize * sizeof(*exphead));
oldexphead = malloc(exphashsize * sizeof(*oldexphead));
if (exphead == NULL || oldexphead == NULL)
errx(1, "Can't malloc hash tables");
for (i = 0; i < exphashsize; i++) {
SLIST_INIT(&exphead[i]);
SLIST_INIT(&oldexphead[i]);
}
}
for (i = 0; i < num; i++)
delete_export(iov, iovlen, &mntbufp[i], errmsg);
/* Read the export file(s) and process them */
read_exportfile(0);
}
if (strlen(v4root_dirpath) == 0) {
/* Check to see if a V4: line is needed. */
nfs_maxvers_size = sizeof(nfs_maxvers);
error = sysctlbyname("vfs.nfsd.server_max_nfsvers",
&nfs_maxvers, &nfs_maxvers_size, NULL, 0);
if (error != 0 || nfs_maxvers < NFS_VER2 || nfs_maxvers >
NFS_VER4) {
syslog(LOG_ERR, "sysctlbyname(vfs.nfsd."
"server_max_nfsvers) failed, defaulting to NFSv3");
nfs_maxvers = NFS_VER3;
}
if (nfs_maxvers == NFS_VER4)
syslog(LOG_ERR, "NFSv4 requires at least one V4: line");
}
if (iov != NULL) {
/* Free strings allocated by strdup() in getmntopts.c */
free(iov[0].iov_base); /* fstype */
free(iov[2].iov_base); /* fspath */
free(iov[4].iov_base); /* from */
free(iov[6].iov_base); /* update */
free(iov[8].iov_base); /* export */
free(iov[10].iov_base); /* errmsg */
/* free iov, allocated by realloc() */
free(iov);
iovlen = 0;
}
/*
* If there was no public fh, clear any previous one set.
*/
if (has_publicfh == 0) {
LOGDEBUG("clear public fh");
(void) nfssvc(NFSSVC_NOPUBLICFH, NULL);
}
/* Resume the nfsd. If they weren't suspended, this is harmless. */
(void)nfssvc(NFSSVC_RESUMENFSD, NULL);
LOGDEBUG("eo get_exportlist");
}
/*
* Insert an export entry in the appropriate list.
*/
static void
insert_exports(struct exportlist *ep, struct exportlisthead *exhp)
{
uint32_t i;
i = EXPHASH(&ep->ex_fs);
LOGDEBUG("fs=%s hash=%i", ep->ex_fsdir, i);
SLIST_INSERT_HEAD(&exhp[i], ep, entries);
}
/*
* Free up the exports lists passed in as arguments.
*/
static void
free_exports(struct exportlisthead *exhp)
{
struct exportlist *ep, *ep2;
int i;
for (i = 0; i < exphashsize; i++) {
SLIST_FOREACH_SAFE(ep, &exhp[i], entries, ep2) {
SLIST_REMOVE(&exhp[i], ep, exportlist, entries);
free_exp(ep);
}
SLIST_INIT(&exhp[i]);
}
}
/*
* Read the exports file(s) and call get_exportlist_one() for each line.
*/
static void
read_exportfile(int passno)
{
int done, i;
/*
* Read in the exports file and build the list, calling
* nmount() as we go along to push the export rules into the kernel.
*/
done = 0;
for (i = 0; exnames[i] != NULL; i++) {
if (debug)
warnx("reading exports from %s", exnames[i]);
if ((exp_file = fopen(exnames[i], "r")) == NULL) {
syslog(LOG_WARNING, "can't open %s", exnames[i]);
continue;
}
get_exportlist_one(passno);
fclose(exp_file);
done++;
}
if (done == 0) {
syslog(LOG_ERR, "can't open any exports file");
exit(2);
}
}
/*
* Compare the export lists against the old ones and do nmount() operations
* for any cases that have changed. This avoids doing nmount() for entries
* that have not changed.
* Return 0 upon success, 1 otherwise.
*/
static int
compare_nmount_exportlist(struct iovec *iov, int iovlen, char *errmsg)
{
struct exportlist *ep, *oep;
struct grouplist *grp;
struct statfs fs, ofs;
int i, ret;
/*
* Loop through the current list and look for an entry in the old
* list.
* If found, check to see if it the same.
* If it is not the same, delete and re-export.
* Then mark it done on the old list.
* else (not found)
* export it.
* Any entries left in the old list after processing must have their
* exports deleted.
*/
for (i = 0; i < exphashsize; i++)
SLIST_FOREACH(ep, &exphead[i], entries) {
LOGDEBUG("foreach ep=%s", ep->ex_fsdir);
oep = ex_search(&ep->ex_fs, oldexphead);
if (oep != NULL) {
/*
* Check the mount paths are the same.
* If not, return 1 so that the reload of the
* exports will be done in bulk, the
* passno == 0 way.
*/
LOGDEBUG("found old exp");
if (strcmp(ep->ex_fsdir, oep->ex_fsdir) != 0)
return (1);
LOGDEBUG("same fsdir");
/*
* Test to see if the entry is the same.
* If not the same delete exports and
* re-export.
*/
if (compare_export(ep, oep) != 0) {
/*
* Clear has_publicfh if if was set
* in the old exports, but only if it
* has not been set during processing of
* the exports for this pass, as
* indicated by has_set_publicfh.
*/
if (has_set_publicfh == 0 &&
(oep->ex_flag & EX_PUBLICFH) != 0)
has_publicfh = 0;
/* Delete and re-export. */
if (statfs(ep->ex_fsdir, &fs) < 0)
return (1);
delete_export(iov, iovlen, &fs, errmsg);
ret = do_export_mount(ep, &fs);
if (ret != 0)
return (ret);
}
oep->ex_flag |= EX_DONE;
LOGDEBUG("exdone");
} else {
LOGDEBUG("not found so export");
/* Not found, so do export. */
if (statfs(ep->ex_fsdir, &fs) < 0)
return (1);
ret = do_export_mount(ep, &fs);
if (ret != 0)
return (ret);
}
}
/* Delete exports not done. */
for (i = 0; i < exphashsize; i++)
SLIST_FOREACH(oep, &oldexphead[i], entries) {
if ((oep->ex_flag & EX_DONE) == 0) {
LOGDEBUG("not done delete=%s", oep->ex_fsdir);
if (statfs(oep->ex_fsdir, &ofs) >= 0 &&
fsidcmp(&oep->ex_fs, &ofs.f_fsid) == 0) {
LOGDEBUG("do delete");
/*
* Clear has_publicfh if if was set
* in the old exports, but only if it
* has not been set during processing of
* the exports for this pass, as
* indicated by has_set_publicfh.
*/
if (has_set_publicfh == 0 &&
(oep->ex_flag & EX_PUBLICFH) != 0)
has_publicfh = 0;
delete_export(iov, iovlen, &ofs,
errmsg);
}
}
}
/* Do the V4 root exports, as required. */
grp = NULL;
if (v4root_ep != NULL)
grp = v4root_ep->ex_grphead;
v4root_phase = 2;
while (v4root_ep != NULL && grp != NULL) {
LOGDEBUG("v4root expath=%s", v4root_dirpath);
ret = do_mount(v4root_ep, grp, grp->gr_exflags, &grp->gr_anon,
v4root_dirpath, strlen(v4root_dirpath), &fs,
grp->gr_numsecflavors, grp->gr_secflavors);
if (ret != 0) {
v4root_phase = 0;
return (ret);
}
grp = grp->gr_next;
}
v4root_phase = 0;
free_v4rootexp();
return (0);
}
/*
* Compare old and current exportlist entries for the fsid and return 0
* if they are the same, 1 otherwise.
*/
static int
compare_export(struct exportlist *ep, struct exportlist *oep)
{
struct grouplist *grp, *ogrp;
if (strcmp(ep->ex_fsdir, oep->ex_fsdir) != 0)
return (1);
if ((ep->ex_flag & EX_DEFSET) != (oep->ex_flag & EX_DEFSET))
return (1);
if ((ep->ex_defdir != NULL && oep->ex_defdir == NULL) ||
(ep->ex_defdir == NULL && oep->ex_defdir != NULL))
return (1);
if (ep->ex_defdir != NULL && (ep->ex_defdir->dp_flag & DP_DEFSET) !=
(oep->ex_defdir->dp_flag & DP_DEFSET))
return (1);
if ((ep->ex_flag & EX_DEFSET) != 0 && (ep->ex_defnumsecflavors !=
oep->ex_defnumsecflavors || ep->ex_defexflags !=
oep->ex_defexflags || compare_cred(&ep->ex_defanon,
&oep->ex_defanon) != 0 || compare_secflavor(ep->ex_defsecflavors,
oep->ex_defsecflavors, ep->ex_defnumsecflavors) != 0))
return (1);
/* Now, check all the groups. */
for (ogrp = oep->ex_grphead; ogrp != NULL; ogrp = ogrp->gr_next)
ogrp->gr_flag = 0;
for (grp = ep->ex_grphead; grp != NULL; grp = grp->gr_next) {
for (ogrp = oep->ex_grphead; ogrp != NULL; ogrp =
ogrp->gr_next)
if ((ogrp->gr_flag & GR_FND) == 0 &&
grp->gr_numsecflavors == ogrp->gr_numsecflavors &&
grp->gr_exflags == ogrp->gr_exflags &&
compare_cred(&grp->gr_anon, &ogrp->gr_anon) == 0 &&
compare_secflavor(grp->gr_secflavors,
ogrp->gr_secflavors, grp->gr_numsecflavors) == 0)
break;
if (ogrp != NULL)
ogrp->gr_flag |= GR_FND;
else
return (1);
}
for (ogrp = oep->ex_grphead; ogrp != NULL; ogrp = ogrp->gr_next)
if ((ogrp->gr_flag & GR_FND) == 0)
return (1);
return (0);
}
/*
* This algorithm compares two arrays of "n" items. It returns 0 if they are
* the "same" and 1 otherwise. Although suboptimal, it is always safe to
* return 1, which makes compare_nmount_export() reload the exports entry.
* "same" refers to having the same set of values in the two arrays.
* The arrays are in no particular order and duplicates (multiple entries
* in an array with the same value) is allowed.
* The algorithm is inefficient, but the common case of indentical arrays is
* handled first and "n" is normally fairly small.
* Since the two functions need the same algorithm but for arrays of
* different types (gid_t vs int), this is done as a macro.
*/
#define COMPARE_ARRAYS(a1, a2, n) \
do { \
int fnd, fndarray[(n)], i, j; \
/* Handle common case of identical arrays. */ \
for (i = 0; i < (n); i++) \
if ((a1)[i] != (a2)[i]) \
break; \
if (i == (n)) \
return (0); \
for (i = 0; i < (n); i++) \
fndarray[i] = 0; \
for (i = 0; i < (n); i++) { \
fnd = 0; \
for (j = 0; j < (n); j++) { \
if ((a1)[i] == (a2)[j]) { \
fndarray[j] = 1; \
fnd = 1; \
} \
} \
if (fnd == 0) \
return (1); \
} \
for (i = 0; i < (n); i++) \
if (fndarray[i] == 0) \
return (1); \
return (0); \
} while (0)
/*
* Compare two struct expcred's. Return 0 if the same and 1 otherwise.
*/
static int
compare_cred(struct expcred *cr0, struct expcred *cr1)
{
if (cr0->cr_uid != cr1->cr_uid || cr0->cr_ngroups != cr1->cr_ngroups)
return (1);
COMPARE_ARRAYS(cr0->cr_groups, cr1->cr_groups, cr0->cr_ngroups);
}
/*
* Compare two lists of security flavors. Return 0 if the same and 1 otherwise.
*/
static int
compare_secflavor(int *sec1, int *sec2, int nsec)
{
COMPARE_ARRAYS(sec1, sec2, nsec);
}
/*
* Delete an exports entry.
*/
static void
delete_export(struct iovec *iov, int iovlen, struct statfs *fsp, char *errmsg)
{
struct xvfsconf vfc;
if (getvfsbyname(fsp->f_fstypename, &vfc) != 0) {
syslog(LOG_ERR, "getvfsbyname() failed for %s",
fsp->f_fstypename);
return;
}
/*
* We do not need to delete "export" flag from
* filesystems that do not have it set.
*/
if (!(fsp->f_flags & MNT_EXPORTED))
return;
/*
* Do not delete export for network filesystem by
* passing "export" arg to nmount().
* It only makes sense to do this for local filesystems.
*/
if (vfc.vfc_flags & VFCF_NETWORK)
return;
iov[1].iov_base = fsp->f_fstypename;
iov[1].iov_len = strlen(fsp->f_fstypename) + 1;
iov[3].iov_base = fsp->f_mntonname;
iov[3].iov_len = strlen(fsp->f_mntonname) + 1;
iov[5].iov_base = fsp->f_mntfromname;
iov[5].iov_len = strlen(fsp->f_mntfromname) + 1;
errmsg[0] = '\0';
/*
* EXDEV is returned when path exists but is not a
* mount point. May happens if raced with unmount.
*/
if (nmount(iov, iovlen, fsp->f_flags) < 0 && errno != ENOENT &&
errno != ENOTSUP && errno != EXDEV) {
syslog(LOG_ERR,
"can't delete exports for %s: %m %s",
fsp->f_mntonname, errmsg);
}
}
/*
* Allocate an export list element
*/
static struct exportlist *
get_exp(void)
{
struct exportlist *ep;
ep = (struct exportlist *)calloc(1, sizeof (struct exportlist));
if (ep == (struct exportlist *)NULL)
out_of_mem();
return (ep);
}
/*
* Allocate a group list element
*/
static struct grouplist *
get_grp(void)
{
struct grouplist *gp;
gp = (struct grouplist *)calloc(1, sizeof (struct grouplist));
if (gp == (struct grouplist *)NULL)
out_of_mem();
return (gp);
}
/*
* Clean up upon an error in get_exportlist().
*/
static void
getexp_err(struct exportlist *ep, struct grouplist *grp, const char *reason)
{
struct grouplist *tgrp;
if (!(opt_flags & OP_QUIET)) {
if (reason != NULL)
syslog(LOG_ERR, "bad exports list line '%s': %s", line,
reason);
else
syslog(LOG_ERR, "bad exports list line '%s'", line);
}
if (ep && (ep->ex_flag & EX_LINKED) == 0)
free_exp(ep);
while (grp) {
tgrp = grp;
grp = grp->gr_next;
free_grp(tgrp);
}
}
/*
* Search the export list for a matching fs.
*/
static struct exportlist *
ex_search(fsid_t *fsid, struct exportlisthead *exhp)
{
struct exportlist *ep;
uint32_t i;
i = EXPHASH(fsid);
SLIST_FOREACH(ep, &exhp[i], entries) {
if (fsidcmp(&ep->ex_fs, fsid) == 0)
return (ep);
}
return (ep);
}
/*
* Add a directory path to the list.
*/
static char *
add_expdir(struct dirlist **dpp, char *cp, int len)
{
struct dirlist *dp;
dp = malloc(sizeof (struct dirlist));
if (dp == (struct dirlist *)NULL)
out_of_mem();
dp->dp_left = *dpp;
dp->dp_right = (struct dirlist *)NULL;
dp->dp_flag = 0;
dp->dp_hosts = (struct hostlist *)NULL;
dp->dp_dirp = strndup(cp, len);
if (dp->dp_dirp == NULL)
out_of_mem();
*dpp = dp;
return (dp->dp_dirp);
}
/*
* Hang the dir list element off the dirpath binary tree as required
* and update the entry for host.
*/
static void
hang_dirp(struct dirlist *dp, struct grouplist *grp, struct exportlist *ep,
int flags, struct expcred *anoncrp, uint64_t exflags)
{
struct hostlist *hp;
struct dirlist *dp2;
if (flags & OP_ALLDIRS) {
if (ep->ex_defdir)
free((caddr_t)dp);
else
ep->ex_defdir = dp;
if (grp == (struct grouplist *)NULL) {
ep->ex_flag |= EX_DEFSET;
ep->ex_defdir->dp_flag |= DP_DEFSET;
/* Save the default security flavors list. */
ep->ex_defnumsecflavors = ep->ex_numsecflavors;
if (ep->ex_numsecflavors > 0)
memcpy(ep->ex_defsecflavors, ep->ex_secflavors,
sizeof(ep->ex_secflavors));
cp_cred(&ep->ex_defanon, anoncrp);
ep->ex_defexflags = exflags;
} else while (grp) {
hp = get_ht();
hp->ht_grp = grp;
hp->ht_next = ep->ex_defdir->dp_hosts;
ep->ex_defdir->dp_hosts = hp;
/* Save the security flavors list for this host set. */
grp->gr_numsecflavors = ep->ex_numsecflavors;
if (ep->ex_numsecflavors > 0)
memcpy(grp->gr_secflavors, ep->ex_secflavors,
sizeof(ep->ex_secflavors));
grp = grp->gr_next;
}
} else {
/*
* Loop through the directories adding them to the tree.
*/
while (dp) {
dp2 = dp->dp_left;
add_dlist(&ep->ex_dirl, dp, grp, flags, ep, anoncrp,
exflags);
dp = dp2;
}
}
}
/*
* Traverse the binary tree either updating a node that is already there
* for the new directory or adding the new node.
*/
static void
add_dlist(struct dirlist **dpp, struct dirlist *newdp, struct grouplist *grp,
int flags, struct exportlist *ep, struct expcred *anoncrp,
uint64_t exflags)
{
struct dirlist *dp;
struct hostlist *hp;
int cmp;
dp = *dpp;
if (dp) {
cmp = strcmp(dp->dp_dirp, newdp->dp_dirp);
if (cmp > 0) {
add_dlist(&dp->dp_left, newdp, grp, flags, ep, anoncrp,
exflags);
return;
} else if (cmp < 0) {
add_dlist(&dp->dp_right, newdp, grp, flags, ep, anoncrp,
exflags);
return;
} else
free((caddr_t)newdp);
} else {
dp = newdp;
dp->dp_left = (struct dirlist *)NULL;
*dpp = dp;
}
if (grp) {
/*
* Hang all of the host(s) off of the directory point.
*/
do {
hp = get_ht();
hp->ht_grp = grp;
hp->ht_next = dp->dp_hosts;
dp->dp_hosts = hp;
/* Save the security flavors list for this host set. */
grp->gr_numsecflavors = ep->ex_numsecflavors;
if (ep->ex_numsecflavors > 0)
memcpy(grp->gr_secflavors, ep->ex_secflavors,
sizeof(ep->ex_secflavors));
grp = grp->gr_next;
} while (grp);
} else {
ep->ex_flag |= EX_DEFSET;
dp->dp_flag |= DP_DEFSET;
/* Save the default security flavors list. */
ep->ex_defnumsecflavors = ep->ex_numsecflavors;
if (ep->ex_numsecflavors > 0)
memcpy(ep->ex_defsecflavors, ep->ex_secflavors,
sizeof(ep->ex_secflavors));
cp_cred(&ep->ex_defanon, anoncrp);
ep->ex_defexflags = exflags;
}
}
/*
* Search for a dirpath on the export point.
*/
static struct dirlist *
dirp_search(struct dirlist *dp, char *dirp)
{
int cmp;
if (dp) {
cmp = strcmp(dp->dp_dirp, dirp);
if (cmp > 0)
return (dirp_search(dp->dp_left, dirp));
else if (cmp < 0)
return (dirp_search(dp->dp_right, dirp));
else
return (dp);
}
return (dp);
}
/*
* Scan for a host match in a directory tree.
*/
static int
chk_host(struct dirlist *dp, struct sockaddr *saddr, int *defsetp,
int *hostsetp, int *numsecflavors, int **secflavorsp)
{
struct hostlist *hp;
struct grouplist *grp;
struct addrinfo *ai;
if (dp) {
if (dp->dp_flag & DP_DEFSET)
*defsetp = dp->dp_flag;
hp = dp->dp_hosts;
while (hp) {
grp = hp->ht_grp;
switch (grp->gr_type) {
case GT_HOST:
ai = grp->gr_ptr.gt_addrinfo;
for (; ai; ai = ai->ai_next) {
if (!sacmp(ai->ai_addr, saddr, NULL)) {
*hostsetp =
(hp->ht_flag | DP_HOSTSET);
if (numsecflavors != NULL) {
*numsecflavors =
grp->gr_numsecflavors;
*secflavorsp =
grp->gr_secflavors;
}
return (1);
}
}
break;
case GT_NET:
if (!sacmp(saddr, (struct sockaddr *)
&grp->gr_ptr.gt_net.nt_net,
(struct sockaddr *)
&grp->gr_ptr.gt_net.nt_mask)) {
*hostsetp = (hp->ht_flag | DP_HOSTSET);
if (numsecflavors != NULL) {
*numsecflavors =
grp->gr_numsecflavors;
*secflavorsp =
grp->gr_secflavors;
}
return (1);
}
break;
}
hp = hp->ht_next;
}
}
return (0);
}
/*
* Scan tree for a host that matches the address.
*/
static int
scan_tree(struct dirlist *dp, struct sockaddr *saddr)
{
int defset, hostset;
if (dp) {
if (scan_tree(dp->dp_left, saddr))
return (1);
if (chk_host(dp, saddr, &defset, &hostset, NULL, NULL))
return (1);
if (scan_tree(dp->dp_right, saddr))
return (1);
}
return (0);
}
/*
* Traverse the dirlist tree and free it up.
*/
static void
free_dir(struct dirlist *dp)
{
if (dp) {
free_dir(dp->dp_left);
free_dir(dp->dp_right);
free_host(dp->dp_hosts);
free(dp->dp_dirp);
free(dp);
}
}
/*
* Parse a colon separated list of security flavors
*/
static int
parsesec(char *seclist, struct exportlist *ep)
{
char *cp, savedc;
int flavor;
ep->ex_numsecflavors = 0;
for (;;) {
cp = strchr(seclist, ':');
if (cp) {
savedc = *cp;
*cp = '\0';
}
if (!strcmp(seclist, "sys"))
flavor = AUTH_SYS;
else if (!strcmp(seclist, "krb5"))
flavor = RPCSEC_GSS_KRB5;
else if (!strcmp(seclist, "krb5i"))
flavor = RPCSEC_GSS_KRB5I;
else if (!strcmp(seclist, "krb5p"))
flavor = RPCSEC_GSS_KRB5P;
else {
if (cp)
*cp = savedc;
syslog(LOG_ERR, "bad sec flavor: %s", seclist);
return (1);
}
if (ep->ex_numsecflavors == MAXSECFLAVORS) {
if (cp)
*cp = savedc;
syslog(LOG_ERR, "too many sec flavors: %s", seclist);
return (1);
}
ep->ex_secflavors[ep->ex_numsecflavors] = flavor;
ep->ex_numsecflavors++;
if (cp) {
*cp = savedc;
seclist = cp + 1;
} else {
break;
}
}
return (0);
}
/*
* Parse the option string and update fields.
* Option arguments may either be -<option>=<value> or
* -<option> <value>
*/
static int
do_opt(char **cpp, char **endcpp, struct exportlist *ep, struct grouplist *grp,
int *has_hostp, uint64_t *exflagsp, struct expcred *cr)
{
char *cpoptarg, *cpoptend;
char *cp, *endcp, *cpopt, savedc, savedc2;
int allflag, usedarg;
savedc2 = '\0';
cpopt = *cpp;
cpopt++;
cp = *endcpp;
savedc = *cp;
*cp = '\0';
while (cpopt && *cpopt) {
allflag = 1;
usedarg = -2;
if ((cpoptend = strchr(cpopt, ','))) {
*cpoptend++ = '\0';
if ((cpoptarg = strchr(cpopt, '=')))
*cpoptarg++ = '\0';
} else {
if ((cpoptarg = strchr(cpopt, '=')))
*cpoptarg++ = '\0';
else {
*cp = savedc;
nextfield(&cp, &endcp);
**endcpp = '\0';
if (endcp > cp && *cp != '-') {
cpoptarg = cp;
savedc2 = *endcp;
*endcp = '\0';
usedarg = 0;
}
}
}
if (!strcmp(cpopt, "ro") || !strcmp(cpopt, "o")) {
*exflagsp |= MNT_EXRDONLY;
} else if (cpoptarg && (!strcmp(cpopt, "maproot") ||
!(allflag = strcmp(cpopt, "mapall")) ||
!strcmp(cpopt, "root") || !strcmp(cpopt, "r"))) {
usedarg++;
parsecred(cpoptarg, cr);
if (allflag == 0) {
*exflagsp |= MNT_EXPORTANON;
opt_flags |= OP_MAPALL;
} else
opt_flags |= OP_MAPROOT;
} else if (cpoptarg && (!strcmp(cpopt, "mask") ||
!strcmp(cpopt, "m"))) {
if (get_net(cpoptarg, &grp->gr_ptr.gt_net, 1)) {
syslog(LOG_ERR, "bad mask: %s", cpoptarg);
return (1);
}
usedarg++;
opt_flags |= OP_MASK;
} else if (cpoptarg && (!strcmp(cpopt, "network") ||
!strcmp(cpopt, "n"))) {
if (strchr(cpoptarg, '/') != NULL) {
if (debug)
fprintf(stderr, "setting OP_MASKLEN\n");
opt_flags |= OP_MASKLEN;
}
if (grp->gr_type != GT_NULL) {
syslog(LOG_ERR, "network/host conflict");
return (1);
} else if (get_net(cpoptarg, &grp->gr_ptr.gt_net, 0)) {
syslog(LOG_ERR, "bad net: %s", cpoptarg);
return (1);
}
grp->gr_type = GT_NET;
*has_hostp = 1;
usedarg++;
opt_flags |= OP_NET;
} else if (!strcmp(cpopt, "alldirs")) {
opt_flags |= OP_ALLDIRS;
} else if (!strcmp(cpopt, "public")) {
*exflagsp |= MNT_EXPUBLIC;
} else if (!strcmp(cpopt, "webnfs")) {
*exflagsp |= (MNT_EXPUBLIC|MNT_EXRDONLY|MNT_EXPORTANON);
opt_flags |= OP_MAPALL;
} else if (cpoptarg && !strcmp(cpopt, "index")) {
ep->ex_indexfile = strdup(cpoptarg);
} else if (!strcmp(cpopt, "quiet")) {
opt_flags |= OP_QUIET;
} else if (cpoptarg && !strcmp(cpopt, "sec")) {
if (parsesec(cpoptarg, ep))
return (1);
opt_flags |= OP_SEC;
usedarg++;
} else if (!strcmp(cpopt, "tls")) {
*exflagsp |= MNT_EXTLS;
} else if (!strcmp(cpopt, "tlscert")) {
*exflagsp |= (MNT_EXTLS | MNT_EXTLSCERT);
} else if (!strcmp(cpopt, "tlscertuser")) {
*exflagsp |= (MNT_EXTLS | MNT_EXTLSCERT |
MNT_EXTLSCERTUSER);
} else {
syslog(LOG_ERR, "bad opt %s", cpopt);
return (1);
}
if (usedarg >= 0) {
*endcp = savedc2;
**endcpp = savedc;
if (usedarg > 0) {
*cpp = cp;
*endcpp = endcp;
}
return (0);
}
cpopt = cpoptend;
}
**endcpp = savedc;
return (0);
}
/*
* Translate a character string to the corresponding list of network
* addresses for a hostname.
*/
static int
get_host(char *cp, struct grouplist *grp, struct grouplist *tgrp)
{
struct grouplist *checkgrp;
struct addrinfo *ai, *tai, hints;
int ecode;
char host[NI_MAXHOST];
if (grp->gr_type != GT_NULL) {
syslog(LOG_ERR, "Bad netgroup type for ip host %s", cp);
return (1);
}
memset(&hints, 0, sizeof hints);
hints.ai_flags = AI_CANONNAME;
hints.ai_protocol = IPPROTO_UDP;
ecode = getaddrinfo(cp, NULL, &hints, &ai);
if (ecode != 0) {
syslog(LOG_ERR,"can't get address info for host %s", cp);
return 1;
}
grp->gr_ptr.gt_addrinfo = ai;
while (ai != NULL) {
if (ai->ai_canonname == NULL) {
if (getnameinfo(ai->ai_addr, ai->ai_addrlen, host,
sizeof host, NULL, 0, NI_NUMERICHOST) != 0)
strlcpy(host, "?", sizeof(host));
ai->ai_canonname = strdup(host);
ai->ai_flags |= AI_CANONNAME;
}
if (debug)
fprintf(stderr, "got host %s\n", ai->ai_canonname);
/*
* Sanity check: make sure we don't already have an entry
* for this host in the grouplist.
*/
for (checkgrp = tgrp; checkgrp != NULL;
checkgrp = checkgrp->gr_next) {
if (checkgrp->gr_type != GT_HOST)
continue;
for (tai = checkgrp->gr_ptr.gt_addrinfo; tai != NULL;
tai = tai->ai_next) {
if (sacmp(tai->ai_addr, ai->ai_addr, NULL) != 0)
continue;
if (debug)
fprintf(stderr,
"ignoring duplicate host %s\n",
ai->ai_canonname);
grp->gr_type = GT_IGNORE;
return (0);
}
}
ai = ai->ai_next;
}
grp->gr_type = GT_HOST;
return (0);
}
/*
* Free up an exports list component
*/
static void
free_exp(struct exportlist *ep)
{
struct grouplist *grp, *tgrp;
if (ep->ex_defdir) {
free_host(ep->ex_defdir->dp_hosts);
free((caddr_t)ep->ex_defdir);
}
if (ep->ex_fsdir)
free(ep->ex_fsdir);
if (ep->ex_indexfile)
free(ep->ex_indexfile);
free_dir(ep->ex_dirl);
grp = ep->ex_grphead;
while (grp) {
tgrp = grp;
grp = grp->gr_next;
free_grp(tgrp);
}
if (ep->ex_defanon.cr_groups != ep->ex_defanon.cr_smallgrps)
free(ep->ex_defanon.cr_groups);
free((caddr_t)ep);
}
/*
* Free up the v4root exports.
*/
static void
free_v4rootexp(void)
{
if (v4root_ep != NULL) {
free_exp(v4root_ep);
v4root_ep = NULL;
}
}
/*
* Free hosts.
*/
static void
free_host(struct hostlist *hp)
{
struct hostlist *hp2;
while (hp) {
hp2 = hp;
hp = hp->ht_next;
free((caddr_t)hp2);
}
}
static struct hostlist *
get_ht(void)
{
struct hostlist *hp;
hp = (struct hostlist *)malloc(sizeof (struct hostlist));
if (hp == (struct hostlist *)NULL)
out_of_mem();
hp->ht_next = (struct hostlist *)NULL;
hp->ht_flag = 0;
return (hp);
}
/*
* Out of memory, fatal
*/
static void
out_of_mem(void)
{
syslog(LOG_ERR, "out of memory");
exit(2);
}
/*
* Call do_mount() from the struct exportlist, for each case needed.
*/
static int
do_export_mount(struct exportlist *ep, struct statfs *fsp)
{
struct grouplist *grp, defgrp;
int ret;
size_t dirlen;
LOGDEBUG("do_export_mount=%s", ep->ex_fsdir);
dirlen = strlen(ep->ex_fsdir);
if ((ep->ex_flag & EX_DEFSET) != 0) {
defgrp.gr_type = GT_DEFAULT;
defgrp.gr_next = NULL;
/* We have an entry for all other hosts/nets. */
LOGDEBUG("ex_defexflags=0x%jx", (uintmax_t)ep->ex_defexflags);
ret = do_mount(ep, &defgrp, ep->ex_defexflags, &ep->ex_defanon,
ep->ex_fsdir, dirlen, fsp, ep->ex_defnumsecflavors,
ep->ex_defsecflavors);
if (ret != 0)
return (ret);
}
/* Do a mount for each group. */
grp = ep->ex_grphead;
while (grp != NULL) {
LOGDEBUG("do mount gr_type=0x%x gr_exflags=0x%jx",
grp->gr_type, (uintmax_t)grp->gr_exflags);
ret = do_mount(ep, grp, grp->gr_exflags, &grp->gr_anon,
ep->ex_fsdir, dirlen, fsp, grp->gr_numsecflavors,
grp->gr_secflavors);
if (ret != 0)
return (ret);
grp = grp->gr_next;
}
return (0);
}
/*
* Do the nmount() syscall with the update flag to push the export info into
* the kernel.
*/
static int
do_mount(struct exportlist *ep, struct grouplist *grp, uint64_t exflags,
struct expcred *anoncrp, char *dirp, int dirplen, struct statfs *fsb,
int numsecflavors, int *secflavors)
{
struct statfs fsb1;
struct addrinfo *ai;
struct export_args *eap;
char errmsg[255];
char *cp;
int done;
char savedc;
struct iovec *iov;
int i, iovlen;
int ret;
struct nfsex_args nfsea;
eap = &nfsea.export;
cp = NULL;
savedc = '\0';
iov = NULL;
iovlen = 0;
ret = 0;
bzero(eap, sizeof (struct export_args));
bzero(errmsg, sizeof(errmsg));
eap->ex_flags = exflags;
eap->ex_uid = anoncrp->cr_uid;
eap->ex_ngroups = anoncrp->cr_ngroups;
if (eap->ex_ngroups > 0) {
eap->ex_groups = malloc(eap->ex_ngroups * sizeof(gid_t));
memcpy(eap->ex_groups, anoncrp->cr_groups, eap->ex_ngroups *
sizeof(gid_t));
}
LOGDEBUG("do_mount exflags=0x%jx", (uintmax_t)exflags);
eap->ex_indexfile = ep->ex_indexfile;
if (grp->gr_type == GT_HOST)
ai = grp->gr_ptr.gt_addrinfo;
else
ai = NULL;
eap->ex_numsecflavors = numsecflavors;
LOGDEBUG("do_mount numsec=%d", numsecflavors);
for (i = 0; i < eap->ex_numsecflavors; i++)
eap->ex_secflavors[i] = secflavors[i];
if (eap->ex_numsecflavors == 0) {
eap->ex_numsecflavors = 1;
eap->ex_secflavors[0] = AUTH_SYS;
}
done = FALSE;
if (v4root_phase == 0) {
build_iovec(&iov, &iovlen, "fstype", NULL, 0);
build_iovec(&iov, &iovlen, "fspath", NULL, 0);
build_iovec(&iov, &iovlen, "from", NULL, 0);
build_iovec(&iov, &iovlen, "update", NULL, 0);
build_iovec(&iov, &iovlen, "export", eap,
sizeof (struct export_args));
build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
}
while (!done) {
switch (grp->gr_type) {
case GT_HOST:
if (ai->ai_addr->sa_family == AF_INET6 && have_v6 == 0)
goto skip;
eap->ex_addr = ai->ai_addr;
eap->ex_addrlen = ai->ai_addrlen;
eap->ex_masklen = 0;
break;
case GT_NET:
if (grp->gr_ptr.gt_net.nt_net.ss_family == AF_INET6 &&
have_v6 == 0)
goto skip;
eap->ex_addr =
(struct sockaddr *)&grp->gr_ptr.gt_net.nt_net;
eap->ex_addrlen =
((struct sockaddr *)&grp->gr_ptr.gt_net.nt_net)->sa_len;
eap->ex_mask =
(struct sockaddr *)&grp->gr_ptr.gt_net.nt_mask;
eap->ex_masklen = ((struct sockaddr *)&grp->gr_ptr.gt_net.nt_mask)->sa_len;
break;
case GT_DEFAULT:
eap->ex_addr = NULL;
eap->ex_addrlen = 0;
eap->ex_mask = NULL;
eap->ex_masklen = 0;
break;
case GT_IGNORE:
ret = 0;
goto error_exit;
break;
default:
syslog(LOG_ERR, "bad grouptype");
if (cp)
*cp = savedc;
ret = 1;
goto error_exit;
}
/*
* For V4:, use the nfssvc() syscall, instead of mount().
*/
if (v4root_phase == 2) {
nfsea.fspec = v4root_dirpath;
if (nfssvc(NFSSVC_V4ROOTEXPORT | NFSSVC_NEWSTRUCT,
(caddr_t)&nfsea) < 0) {
syslog(LOG_ERR, "Exporting V4: failed");
ret = 2;
goto error_exit;
}
} else {
/*
* XXX:
* Maybe I should just use the fsb->f_mntonname path
* instead of looping back up the dirp to the mount
* point??
* Also, needs to know how to export all types of local
* exportable filesystems and not just "ufs".
*/
iov[1].iov_base = fsb->f_fstypename; /* "fstype" */
iov[1].iov_len = strlen(fsb->f_fstypename) + 1;
iov[3].iov_base = fsb->f_mntonname; /* "fspath" */
iov[3].iov_len = strlen(fsb->f_mntonname) + 1;
iov[5].iov_base = fsb->f_mntfromname; /* "from" */
iov[5].iov_len = strlen(fsb->f_mntfromname) + 1;
errmsg[0] = '\0';
while (nmount(iov, iovlen, fsb->f_flags) < 0) {
if (cp)
*cp-- = savedc;
else
cp = dirp + dirplen - 1;
if (opt_flags & OP_QUIET) {
ret = 1;
goto error_exit;
}
if (errno == EPERM) {
if (debug)
warnx("can't change attributes for %s: %s",
dirp, errmsg);
syslog(LOG_ERR,
"can't change attributes for %s: %s",
dirp, errmsg);
ret = 1;
goto error_exit;
}
if (opt_flags & OP_ALLDIRS) {
if (errno == EINVAL)
syslog(LOG_ERR,
"-alldirs requested but %s is not a filesystem mountpoint",
dirp);
else
syslog(LOG_ERR,
"could not remount %s: %m",
dirp);
ret = 1;
goto error_exit;
}
/* back up over the last component */
while (cp > dirp && *cp == '/')
cp--;
while (cp > dirp && *(cp - 1) != '/')
cp--;
if (cp == dirp) {
if (debug)
warnx("mnt unsucc");
syslog(LOG_ERR, "can't export %s %s",
dirp, errmsg);
ret = 1;
goto error_exit;
}
savedc = *cp;
*cp = '\0';
/*
* Check that we're still on the same
* filesystem.
*/
if (statfs(dirp, &fsb1) != 0 ||
fsidcmp(&fsb1.f_fsid, &fsb->f_fsid) != 0) {
*cp = savedc;
syslog(LOG_ERR,
"can't export %s %s", dirp,
errmsg);
ret = 1;
goto error_exit;
}
}
}
/*
* For the experimental server:
* If this is the public directory, get the file handle
* and load it into the kernel via the nfssvc() syscall.
*/
if ((exflags & MNT_EXPUBLIC) != 0) {
fhandle_t fh;
char *public_name;
if (eap->ex_indexfile != NULL)
public_name = eap->ex_indexfile;
else
public_name = dirp;
if (getfh(public_name, &fh) < 0)
syslog(LOG_ERR,
"Can't get public fh for %s", public_name);
else if (nfssvc(NFSSVC_PUBLICFH, (caddr_t)&fh) < 0)
syslog(LOG_ERR,
"Can't set public fh for %s", public_name);
else {
has_publicfh = 1;
has_set_publicfh = 1;
ep->ex_flag |= EX_PUBLICFH;
}
}
skip:
if (ai != NULL)
ai = ai->ai_next;
if (ai == NULL)
done = TRUE;
}
if (cp)
*cp = savedc;
error_exit:
free(eap->ex_groups);
/* free strings allocated by strdup() in getmntopts.c */
if (iov != NULL) {
free(iov[0].iov_base); /* fstype */
free(iov[2].iov_base); /* fspath */
free(iov[4].iov_base); /* from */
free(iov[6].iov_base); /* update */
free(iov[8].iov_base); /* export */
free(iov[10].iov_base); /* errmsg */
/* free iov, allocated by realloc() */
free(iov);
}
return (ret);
}
/*
* Translate a net address.
*
* If `maskflg' is nonzero, then `cp' is a netmask, not a network address.
*/
static int
get_net(char *cp, struct netmsk *net, int maskflg)
{
struct netent *np = NULL;
char *name, *p, *prefp;
struct sockaddr_in sin;
struct sockaddr *sa = NULL;
struct addrinfo hints, *ai = NULL;
char netname[NI_MAXHOST];
long preflen;
p = prefp = NULL;
if ((opt_flags & OP_MASKLEN) && !maskflg) {
p = strchr(cp, '/');
*p = '\0';
prefp = p + 1;
}
/*
* Check for a numeric address first. We wish to avoid
* possible DNS lookups in getnetbyname().
*/
if (isxdigit(*cp) || *cp == ':') {
memset(&hints, 0, sizeof hints);
/* Ensure the mask and the network have the same family. */
if (maskflg && (opt_flags & OP_NET))
hints.ai_family = net->nt_net.ss_family;
else if (!maskflg && (opt_flags & OP_HAVEMASK))
hints.ai_family = net->nt_mask.ss_family;
else
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
if (getaddrinfo(cp, NULL, &hints, &ai) == 0)
sa = ai->ai_addr;
if (sa != NULL && ai->ai_family == AF_INET) {
/*
* The address in `cp' is really a network address, so
* use inet_network() to re-interpret this correctly.
* e.g. "127.1" means 127.1.0.0, not 127.0.0.1.
*/
bzero(&sin, sizeof sin);
sin.sin_family = AF_INET;
sin.sin_len = sizeof sin;
sin.sin_addr = inet_makeaddr(inet_network(cp), 0);
if (debug)
fprintf(stderr, "get_net: v4 addr %s\n",
inet_ntoa(sin.sin_addr));
sa = (struct sockaddr *)&sin;
}
}
if (sa == NULL && (np = getnetbyname(cp)) != NULL) {
bzero(&sin, sizeof sin);
sin.sin_family = AF_INET;
sin.sin_len = sizeof sin;
sin.sin_addr = inet_makeaddr(np->n_net, 0);
sa = (struct sockaddr *)&sin;
}
if (sa == NULL)
goto fail;
if (maskflg) {
/* The specified sockaddr is a mask. */
if (checkmask(sa) != 0)
goto fail;
bcopy(sa, &net->nt_mask, sa->sa_len);
opt_flags |= OP_HAVEMASK;
} else {
/* The specified sockaddr is a network address. */
bcopy(sa, &net->nt_net, sa->sa_len);
/* Get a network name for the export list. */
if (np) {
name = np->n_name;
} else if (getnameinfo(sa, sa->sa_len, netname, sizeof netname,
NULL, 0, NI_NUMERICHOST) == 0) {
name = netname;
} else {
goto fail;
}
if ((net->nt_name = strdup(name)) == NULL)
out_of_mem();
/*
* Extract a mask from either a "/<masklen>" suffix, or
* from the class of an IPv4 address.
*/
if (opt_flags & OP_MASKLEN) {
preflen = strtol(prefp, NULL, 10);
if (preflen < 0L || preflen == LONG_MAX)
goto fail;
bcopy(sa, &net->nt_mask, sa->sa_len);
if (makemask(&net->nt_mask, (int)preflen) != 0)
goto fail;
opt_flags |= OP_HAVEMASK;
*p = '/';
} else if (sa->sa_family == AF_INET &&
(opt_flags & OP_MASK) == 0) {
in_addr_t addr;
addr = ((struct sockaddr_in *)sa)->sin_addr.s_addr;
if (IN_CLASSA(addr))
preflen = 8;
else if (IN_CLASSB(addr))
preflen = 16;
else if (IN_CLASSC(addr))
preflen = 24;
else if (IN_CLASSD(addr))
preflen = 28;
else
preflen = 32; /* XXX */
bcopy(sa, &net->nt_mask, sa->sa_len);
makemask(&net->nt_mask, (int)preflen);
opt_flags |= OP_HAVEMASK;
}
}
if (ai)
freeaddrinfo(ai);
return 0;
fail:
if (ai)
freeaddrinfo(ai);
return 1;
}
/*
* Parse out the next white space separated field
*/
static void
nextfield(char **cp, char **endcp)
{
char *p;
char quot = 0;
p = *cp;
while (*p == ' ' || *p == '\t')
p++;
*cp = p;
while (*p != '\0') {
if (quot) {
if (*p == quot)
quot = 0;
} else {
if (*p == '\\' && *(p + 1) != '\0')
p++;
else if (*p == '\'' || *p == '"')
quot = *p;
else if (*p == ' ' || *p == '\t')
break;
}
p++;
};
*endcp = p;
}
/*
* Get an exports file line. Skip over blank lines and handle line
* continuations.
*/
static int
get_line(void)
{
char *p, *cp;
size_t len;
int totlen, cont_line;
/*
* Loop around ignoring blank lines and getting all continuation lines.
*/
p = line;
totlen = 0;
do {
if ((p = fgetln(exp_file, &len)) == NULL)
return (0);
cp = p + len - 1;
cont_line = 0;
while (cp >= p &&
(*cp == ' ' || *cp == '\t' || *cp == '\n' || *cp == '\\')) {
if (*cp == '\\')
cont_line = 1;
cp--;
len--;
}
if (cont_line) {
*++cp = ' ';
len++;
}
if (linesize < len + totlen + 1) {
linesize = len + totlen + 1;
line = realloc(line, linesize);
if (line == NULL)
out_of_mem();
}
memcpy(line + totlen, p, len);
totlen += len;
line[totlen] = '\0';
} while (totlen == 0 || cont_line);
return (1);
}
/*
* Parse a description of a credential.
*/
static void
parsecred(char *namelist, struct expcred *cr)
{
char *name;
int inpos;
char *names;
struct passwd *pw;
struct group *gr;
gid_t groups[NGROUPS_MAX + 1];
int ngroups;
/*
* Set up the unprivileged user.
*/
cr->cr_groups = cr->cr_smallgrps;
cr->cr_uid = UID_NOBODY;
cr->cr_groups[0] = GID_NOGROUP;
cr->cr_ngroups = 1;
/*
* Get the user's password table entry.
*/
names = namelist;
name = strsep_quote(&names, ":");
/* Bug? name could be NULL here */
if (isdigit(*name) || *name == '-')
pw = getpwuid(atoi(name));
else
pw = getpwnam(name);
/*
* Credentials specified as those of a user.
*/
if (names == NULL) {
if (pw == NULL) {
syslog(LOG_ERR, "unknown user: %s", name);
return;
}
cr->cr_uid = pw->pw_uid;
ngroups = NGROUPS_MAX + 1;
if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups)) {
syslog(LOG_ERR, "too many groups");
ngroups = NGROUPS_MAX + 1;
}
/*
* Compress out duplicate.
*/
if (ngroups > 1 && groups[0] == groups[1]) {
ngroups--;
inpos = 2;
} else
inpos = 1;
if (ngroups > NGROUPS_MAX)
ngroups = NGROUPS_MAX;
if (ngroups > SMALLNGROUPS)
cr->cr_groups = malloc(ngroups * sizeof(gid_t));
cr->cr_ngroups = ngroups;
cr->cr_groups[0] = groups[0];
memcpy(&cr->cr_groups[1], &groups[inpos], (ngroups - 1) *
sizeof(gid_t));
return;
}
/*
* Explicit credential specified as a colon separated list:
* uid:gid:gid:...
*/
if (pw != NULL)
cr->cr_uid = pw->pw_uid;
else if (isdigit(*name) || *name == '-')
cr->cr_uid = atoi(name);
else {
syslog(LOG_ERR, "unknown user: %s", name);
return;
}
cr->cr_ngroups = 0;
while (names != NULL && *names != '\0' && cr->cr_ngroups < NGROUPS_MAX) {
name = strsep_quote(&names, ":");
if (isdigit(*name) || *name == '-') {
groups[cr->cr_ngroups++] = atoi(name);
} else {
if ((gr = getgrnam(name)) == NULL) {
syslog(LOG_ERR, "unknown group: %s", name);
continue;
}
groups[cr->cr_ngroups++] = gr->gr_gid;
}
}
if (names != NULL && *names != '\0' && cr->cr_ngroups == NGROUPS_MAX)
syslog(LOG_ERR, "too many groups");
if (cr->cr_ngroups > SMALLNGROUPS)
cr->cr_groups = malloc(cr->cr_ngroups * sizeof(gid_t));
memcpy(cr->cr_groups, groups, cr->cr_ngroups * sizeof(gid_t));
}
#define STRSIZ (MNTNAMLEN+MNTPATHLEN+50)
/*
* Routines that maintain the remote mounttab
*/
static void
get_mountlist(void)
{
struct mountlist *mlp;
char *host, *dirp, *cp;
char str[STRSIZ];
FILE *mlfile;
if ((mlfile = fopen(_PATH_RMOUNTLIST, "r")) == NULL) {
if (errno == ENOENT)
return;
else {
syslog(LOG_ERR, "can't open %s", _PATH_RMOUNTLIST);
return;
}
}
while (fgets(str, STRSIZ, mlfile) != NULL) {
cp = str;
host = strsep(&cp, " \t\n");
dirp = strsep(&cp, " \t\n");
if (host == NULL || dirp == NULL)
continue;
mlp = (struct mountlist *)malloc(sizeof (*mlp));
if (mlp == (struct mountlist *)NULL)
out_of_mem();
strncpy(mlp->ml_host, host, MNTNAMLEN);
mlp->ml_host[MNTNAMLEN] = '\0';
strncpy(mlp->ml_dirp, dirp, MNTPATHLEN);
mlp->ml_dirp[MNTPATHLEN] = '\0';
SLIST_INSERT_HEAD(&mlhead, mlp, next);
}
fclose(mlfile);
}
static void
del_mlist(char *hostp, char *dirp)
{
struct mountlist *mlp, *mlp2;
FILE *mlfile;
int fnd = 0;
SLIST_FOREACH_SAFE(mlp, &mlhead, next, mlp2) {
if (!strcmp(mlp->ml_host, hostp) &&
(!dirp || !strcmp(mlp->ml_dirp, dirp))) {
fnd = 1;
SLIST_REMOVE(&mlhead, mlp, mountlist, next);
free((caddr_t)mlp);
}
}
if (fnd) {
if ((mlfile = fopen(_PATH_RMOUNTLIST, "w")) == NULL) {
syslog(LOG_ERR,"can't update %s", _PATH_RMOUNTLIST);
return;
}
SLIST_FOREACH(mlp, &mlhead, next) {
fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dirp);
}
fclose(mlfile);
}
}
static void
add_mlist(char *hostp, char *dirp)
{
struct mountlist *mlp;
FILE *mlfile;
SLIST_FOREACH(mlp, &mlhead, next) {
if (!strcmp(mlp->ml_host, hostp) && !strcmp(mlp->ml_dirp, dirp))
return;
}
mlp = (struct mountlist *)malloc(sizeof (*mlp));
if (mlp == (struct mountlist *)NULL)
out_of_mem();
strncpy(mlp->ml_host, hostp, MNTNAMLEN);
mlp->ml_host[MNTNAMLEN] = '\0';
strncpy(mlp->ml_dirp, dirp, MNTPATHLEN);
mlp->ml_dirp[MNTPATHLEN] = '\0';
SLIST_INSERT_HEAD(&mlhead, mlp, next);
if ((mlfile = fopen(_PATH_RMOUNTLIST, "a")) == NULL) {
syslog(LOG_ERR, "can't update %s", _PATH_RMOUNTLIST);
return;
}
fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dirp);
fclose(mlfile);
}
/*
* Free up a group list.
*/
static void
free_grp(struct grouplist *grp)
{
if (grp->gr_type == GT_HOST) {
if (grp->gr_ptr.gt_addrinfo != NULL)
freeaddrinfo(grp->gr_ptr.gt_addrinfo);
} else if (grp->gr_type == GT_NET) {
if (grp->gr_ptr.gt_net.nt_name)
free(grp->gr_ptr.gt_net.nt_name);
}
if (grp->gr_anon.cr_groups != grp->gr_anon.cr_smallgrps)
free(grp->gr_anon.cr_groups);
free((caddr_t)grp);
}
#ifdef DEBUG
static void
SYSLOG(int pri, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
}
#endif /* DEBUG */
/*
* Check options for consistency.
*/
static int
check_options(struct dirlist *dp)
{
if (v4root_phase == 0 && dp == NULL)
return (1);
if ((opt_flags & (OP_MAPROOT | OP_MAPALL)) == (OP_MAPROOT | OP_MAPALL)) {
syslog(LOG_ERR, "-mapall and -maproot mutually exclusive");
return (1);
}
if ((opt_flags & OP_MASK) && (opt_flags & OP_NET) == 0) {
syslog(LOG_ERR, "-mask requires -network");
return (1);
}
if ((opt_flags & OP_NET) && (opt_flags & OP_HAVEMASK) == 0) {
syslog(LOG_ERR, "-network requires mask specification");
return (1);
}
if ((opt_flags & OP_MASK) && (opt_flags & OP_MASKLEN)) {
syslog(LOG_ERR, "-mask and /masklen are mutually exclusive");
return (1);
}
if (v4root_phase > 0 &&
(opt_flags &
~(OP_SEC | OP_MASK | OP_NET | OP_HAVEMASK | OP_MASKLEN)) != 0) {
syslog(LOG_ERR,"only -sec,-net,-mask options allowed on V4:");
return (1);
}
if ((opt_flags & OP_ALLDIRS) && dp->dp_left) {
syslog(LOG_ERR, "-alldirs has multiple directories");
return (1);
}
return (0);
}
/*
* Check an absolute directory path for any symbolic links. Return true
*/
static int
check_dirpath(char *dirp)
{
char *cp;
int ret = 1;
struct stat sb;
cp = dirp + 1;
while (*cp && ret) {
if (*cp == '/') {
*cp = '\0';
if (lstat(dirp, &sb) < 0 || !S_ISDIR(sb.st_mode))
ret = 0;
*cp = '/';
}
cp++;
}
if (lstat(dirp, &sb) < 0 || !S_ISDIR(sb.st_mode))
ret = 0;
return (ret);
}
/*
* Make a netmask according to the specified prefix length. The ss_family
* and other non-address fields must be initialised before calling this.
*/
static int
makemask(struct sockaddr_storage *ssp, int bitlen)
{
u_char *p;
int bits, i, len;
if ((p = sa_rawaddr((struct sockaddr *)ssp, &len)) == NULL)
return (-1);
if (bitlen > len * CHAR_BIT)
return (-1);
for (i = 0; i < len; i++) {
bits = MIN(CHAR_BIT, bitlen);
*p++ = (u_char)~0 << (CHAR_BIT - bits);
bitlen -= bits;
}
return 0;
}
/*
* Check that the sockaddr is a valid netmask. Returns 0 if the mask
* is acceptable (i.e. of the form 1...10....0).
*/
static int
checkmask(struct sockaddr *sa)
{
u_char *mask;
int i, len;
if ((mask = sa_rawaddr(sa, &len)) == NULL)
return (-1);
for (i = 0; i < len; i++)
if (mask[i] != 0xff)
break;
if (i < len) {
if (~mask[i] & (u_char)(~mask[i] + 1))
return (-1);
i++;
}
for (; i < len; i++)
if (mask[i] != 0)
return (-1);
return (0);
}
/*
* Compare two sockaddrs according to a specified mask. Return zero if
* `sa1' matches `sa2' when filtered by the netmask in `samask'.
* If samask is NULL, perform a full comparison.
*/
static int
sacmp(struct sockaddr *sa1, struct sockaddr *sa2, struct sockaddr *samask)
{
unsigned char *p1, *p2, *mask;
int len, i;
if (sa1->sa_family != sa2->sa_family ||
(p1 = sa_rawaddr(sa1, &len)) == NULL ||
(p2 = sa_rawaddr(sa2, NULL)) == NULL)
return (1);
switch (sa1->sa_family) {
case AF_INET6:
if (((struct sockaddr_in6 *)sa1)->sin6_scope_id !=
((struct sockaddr_in6 *)sa2)->sin6_scope_id)
return (1);
break;
}
/* Simple binary comparison if no mask specified. */
if (samask == NULL)
return (memcmp(p1, p2, len));
/* Set up the mask, and do a mask-based comparison. */
if (sa1->sa_family != samask->sa_family ||
(mask = sa_rawaddr(samask, NULL)) == NULL)
return (1);
for (i = 0; i < len; i++)
if ((p1[i] & mask[i]) != (p2[i] & mask[i]))
return (1);
return (0);
}
/*
* Return a pointer to the part of the sockaddr that contains the
* raw address, and set *nbytes to its length in bytes. Returns
* NULL if the address family is unknown.
*/
static void *
sa_rawaddr(struct sockaddr *sa, int *nbytes) {
void *p;
int len;
switch (sa->sa_family) {
case AF_INET:
len = sizeof(((struct sockaddr_in *)sa)->sin_addr);
p = &((struct sockaddr_in *)sa)->sin_addr;
break;
case AF_INET6:
len = sizeof(((struct sockaddr_in6 *)sa)->sin6_addr);
p = &((struct sockaddr_in6 *)sa)->sin6_addr;
break;
default:
p = NULL;
len = 0;
}
if (nbytes != NULL)
*nbytes = len;
return (p);
}
static void
huphandler(int sig __unused)
{
got_sighup = 1;
}
static void
terminate(int sig __unused)
{
pidfile_remove(pfh);
rpcb_unset(MOUNTPROG, MOUNTVERS, NULL);
rpcb_unset(MOUNTPROG, MOUNTVERS3, NULL);
exit (0);
}
static void
cp_cred(struct expcred *outcr, struct expcred *incr)
{
outcr->cr_uid = incr->cr_uid;
outcr->cr_ngroups = incr->cr_ngroups;
if (outcr->cr_ngroups > SMALLNGROUPS)
outcr->cr_groups = malloc(outcr->cr_ngroups * sizeof(gid_t));
else
outcr->cr_groups = outcr->cr_smallgrps;
memcpy(outcr->cr_groups, incr->cr_groups, incr->cr_ngroups *
sizeof(gid_t));
}