diff options
Diffstat (limited to 'usr.sbin/mountd/mountd.c')
-rw-r--r-- | usr.sbin/mountd/mountd.c | 500 |
1 files changed, 339 insertions, 161 deletions
diff --git a/usr.sbin/mountd/mountd.c b/usr.sbin/mountd/mountd.c index 569ea0fbf707..7e2f9b6e73bd 100644 --- a/usr.sbin/mountd/mountd.c +++ b/usr.sbin/mountd/mountd.c @@ -32,21 +32,6 @@ * 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> @@ -72,12 +57,14 @@ __FBSDID("$FreeBSD$"); #include <arpa/inet.h> +#include <assert.h> #include <ctype.h> #include <err.h> #include <errno.h> #include <grp.h> #include <libutil.h> #include <limits.h> +#include <mntopts.h> #include <netdb.h> #include <pwd.h> #include <signal.h> @@ -85,8 +72,8 @@ __FBSDID("$FreeBSD$"); #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <vis.h> #include "pathnames.h" -#include "mntopts.h" #ifdef DEBUG #include <stdarg.h> @@ -144,10 +131,11 @@ struct exportlist { SLIST_ENTRY(exportlist) entries; }; /* ex_flag bits */ -#define EX_LINKED 0x1 -#define EX_DONE 0x2 -#define EX_DEFSET 0x4 -#define EX_PUBLICFH 0x8 +#define EX_LINKED 0x01 +#define EX_DONE 0x02 +#define EX_DEFSET 0x04 +#define EX_PUBLICFH 0x08 +#define EX_ADMINWARN 0x10 SLIST_HEAD(exportlisthead, exportlist); @@ -210,7 +198,9 @@ 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_path_component(const char *, char **); +static int check_dirpath(char *, char **); +static int check_statfs(const char *, struct statfs *, char **); static int check_options(struct dirlist *); static int checkmask(struct sockaddr *sa); static int chk_host(struct dirlist *, struct sockaddr *, int *, int *, @@ -225,7 +215,8 @@ 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 *); + struct grouplist *, int *, uint64_t *, struct expcred *, + char *); static struct exportlist *ex_search(fsid_t *, struct exportlisthead *); static struct exportlist *get_exp(void); static void free_dir(struct dirlist *); @@ -240,6 +231,7 @@ 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_addr(struct grouplist *, struct grouplist *); 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 *); @@ -256,7 +248,7 @@ 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 out_of_mem(void) __dead2; static void parsecred(char *, struct expcred *); static int parsesec(char *, struct exportlist *); static int put_exlist(struct dirlist *, XDR *, struct dirlist *, @@ -275,6 +267,8 @@ static int xdr_mlist(XDR *, caddr_t); static void terminate(int); static void cp_cred(struct expcred *, struct expcred *); +static gid_t nogroup(); + #define EXPHASH(f) (fnv_32_buf((f), sizeof(fsid_t), 0) % exphashsize) static struct exportlisthead *exphead = NULL; static struct exportlisthead *oldexphead = NULL; @@ -284,11 +278,12 @@ static char *exnames_default[2] = { _PATH_EXPORTS, NULL }; static char **exnames; static char **hosts = NULL; static int force_v2 = 0; +static int warn_admin = 1; static int resvport_only = 1; static int nhosts = 0; static int dir_only = 1; static int dolog = 0; -static int got_sighup = 0; +static _Atomic(int) got_sighup = 0; static int xcreated = 0; static char *svcport_str = NULL; @@ -297,6 +292,9 @@ static int *sock_fd; static int sock_fdcnt; static int sock_fdpos; static int suspend_nfsd = 0; +static int nofork = 0; +static int skiplocalhost = 0; +static int alldirs_fail = 0; static int opt_flags; static int have_v6 = 1; @@ -308,6 +306,11 @@ static int has_publicfh = 0; static int has_set_publicfh = 0; static struct pidfh *pfh = NULL; + +/* Temporary storage for credentials' groups. */ +static long tngroups_max; +static gid_t *tmp_groups = NULL; + /* Bits for opt_flags above */ #define OP_MAPROOT 0x01 #define OP_MAPALL 0x02 @@ -319,6 +322,8 @@ static struct pidfh *pfh = NULL; #define OP_QUIET 0x100 #define OP_MASKLEN 0x200 #define OP_SEC 0x400 +#define OP_CLASSMASK 0x800 /* mask not specified, is Class A/B/C default */ +#define OP_NOTROOT 0x1000 /* Mark the the mount path is not a fs root */ #ifdef DEBUG static int debug = 1; @@ -439,17 +444,35 @@ main(int argc, char **argv) warn("cannot open or create pidfile"); } + openlog("mountd", LOG_PID, LOG_DAEMON); + + /* How many groups do we support? */ + tngroups_max = sysconf(_SC_NGROUPS_MAX); + if (tngroups_max == -1) + tngroups_max = NGROUPS_MAX; + /* Add space for the effective GID. */ + ++tngroups_max; + tmp_groups = malloc(tngroups_max); + if (tmp_groups == NULL) + out_of_mem(); + 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) + while ((c = getopt(argc, argv, "2Aadeh:lNnp:RrSs")) != -1) switch (c) { case '2': force_v2 = 1; break; + case 'A': + warn_admin = 0; + break; + case 'a': + alldirs_fail = 1; + break; case 'e': /* now a no-op, since this is the default */ break; @@ -501,6 +524,12 @@ main(int argc, char **argv) case 'S': suspend_nfsd = 1; break; + case 'N': + nofork = 1; + break; + case 's': + skiplocalhost = 1; + break; default: usage(); } @@ -520,6 +549,9 @@ main(int argc, char **argv) } } + if (nhosts == 0 && skiplocalhost != 0) + warnx("-s without -h, ignored"); + if (modfind("nfsd") < 0) { /* Not present in kernel, try loading it */ if (kldload("nfsd") < 0 || modfind("nfsd") < 0) @@ -532,7 +564,6 @@ main(int argc, char **argv) exnames = argv; else exnames = exnames_default; - openlog("mountd", LOG_PID, LOG_DAEMON); if (debug) warnx("getting export list"); get_exportlist(0); @@ -541,7 +572,7 @@ main(int argc, char **argv) get_mountlist(); if (debug) warnx("here we go"); - if (debug == 0) { + if (debug == 0 && nofork == 0) { daemon(0, 0); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); @@ -577,7 +608,7 @@ main(int argc, char **argv) out_of_mem(); hosts[0] = "*"; nhosts = 1; - } else { + } else if (skiplocalhost == 0) { hosts_bak = hosts; if (have_v6) { hosts_bak = realloc(hosts, (nhosts + 2) * @@ -779,7 +810,7 @@ main(int argc, char **argv) /* * 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 + * It returns 0 upon success, 1 for ignore 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. @@ -1117,8 +1148,8 @@ static void usage(void) { fprintf(stderr, - "usage: mountd [-2] [-d] [-e] [-l] [-n] [-p <port>] [-r] " - "[-S] [-h <bindip>] [export_file ...]\n"); + "usage: mountd [-2] [-d] [-e] [-l] [-N] [-n] [-p <port>] [-r] [-S] " + "[-s] [-h <bindip>] [export_file ...]\n"); exit(1); } @@ -1557,12 +1588,17 @@ get_exportlist_one(int passno) struct statfs fsb; struct expcred anon; char *cp, *endcp, *dirp, *hst, *usr, *dom, savedc; + char *err_msg = NULL; int len, has_host, got_nondir, dirplen, netgrp; uint64_t exflags; + char unvis_dir[PATH_MAX + 1]; + int unvis_len; v4root_phase = 0; - anon.cr_groups = NULL; dirhead = (struct dirlist *)NULL; + unvis_dir[0] = '\0'; + fsb.f_mntonname[0] = '\0'; + while (get_line()) { if (debug) warnx("got line %s", line); @@ -1575,10 +1611,10 @@ get_exportlist_one(int passno) * 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; + anon.cr_groups = tmp_groups; + anon.cr_groups[0] = nogroup(); exflags = MNT_EXPORTED; got_nondir = 0; opt_flags = 0; @@ -1622,24 +1658,32 @@ get_exportlist_one(int passno) warnx("doing opt %s", cp); got_nondir = 1; if (do_opt(&cp, &endcp, ep, grp, &has_host, - &exflags, &anon)) { + &exflags, &anon, unvis_dir)) { getexp_err(ep, tgrp, NULL); goto nextline; } } else if (*cp == '/') { savedc = *endcp; *endcp = '\0'; + unvis_len = strnunvis(unvis_dir, sizeof(unvis_dir), + cp); + if (unvis_len <= 0) { + getexp_err(ep, tgrp, "Cannot strunvis " + "decode dir"); + goto nextline; + } 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 (check_dirpath(unvis_dir, &err_msg) && + check_statfs(unvis_dir, &fsb, &err_msg)) { if ((fsb.f_flags & MNT_AUTOMOUNTED) != 0) syslog(LOG_ERR, "Warning: exporting of " - "automounted fs %s not supported", cp); + "automounted fs %s not supported", + unvis_dir); if (got_nondir) { getexp_err(ep, tgrp, "dirs must be first"); goto nextline; @@ -1650,16 +1694,17 @@ get_exportlist_one(int passno) goto nextline; } if (strlen(v4root_dirpath) == 0) { - strlcpy(v4root_dirpath, cp, + strlcpy(v4root_dirpath, unvis_dir, sizeof (v4root_dirpath)); - } else if (strcmp(v4root_dirpath, cp) + } else if (strcmp(v4root_dirpath, unvis_dir) != 0) { syslog(LOG_ERR, - "different V4 dirpath %s", cp); + "different V4 dirpath %s", + unvis_dir); getexp_err(ep, tgrp, NULL); goto nextline; } - dirp = cp; + dirp = unvis_dir; v4root_phase = 2; got_nondir = 1; ep = get_exp(); @@ -1694,15 +1739,27 @@ get_exportlist_one(int passno) fsb.f_fsid.val[1]); } + if (strcmp(unvis_dir, fsb.f_mntonname) != + 0) + opt_flags |= OP_NOTROOT; + /* * Add dirpath to export mount point. */ - dirp = add_expdir(&dirhead, cp, len); - dirplen = len; + dirp = add_expdir(&dirhead, unvis_dir, + unvis_len); + dirplen = unvis_len; } } else { - getexp_err(ep, tgrp, - "symbolic link in export path or statfs failed"); + if (err_msg != NULL) { + getexp_err(ep, tgrp, err_msg); + free(err_msg); + err_msg = NULL; + } else { + getexp_err(ep, tgrp, + "symbolic link in export path or " + "statfs failed"); + } goto nextline; } *endcp = savedc; @@ -1749,6 +1806,22 @@ get_exportlist_one(int passno) nextfield(&cp, &endcp); len = endcp - cp; } + if (opt_flags & OP_CLASSMASK) + syslog(LOG_ERR, + "WARNING: No mask specified for %s, " + "using out-of-date default", + (&grp->gr_ptr.gt_net)->nt_name); + if ((opt_flags & OP_NOTROOT) != 0 && warn_admin != 0 && + (ep->ex_flag & EX_ADMINWARN) == 0 && unvis_dir[0] != '\0' && + fsb.f_mntonname[0] != '\0') { + if (debug) + warnx("exporting %s exports entire %s file " + "system", unvis_dir, fsb.f_mntonname); + syslog(LOG_ERR, "Warning: exporting %s exports " + "entire %s file system", unvis_dir, + fsb.f_mntonname); + ep->ex_flag |= EX_ADMINWARN; + } if (check_options(dirhead)) { getexp_err(ep, tgrp, NULL); goto nextline; @@ -1871,10 +1944,6 @@ nextline: free_dir(dirhead); dirhead = (struct dirlist *)NULL; } - if (anon.cr_groups != anon.cr_smallgrps) { - free(anon.cr_groups); - anon.cr_groups = NULL; - } } } @@ -2031,19 +2100,7 @@ get_exportlist(int passno) 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; - } + free_iovec(&iov, &iovlen); /* * If there was no public fh, clear any previous one set. @@ -2280,7 +2337,8 @@ compare_export(struct exportlist *ep, struct exportlist *oep) 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) + ogrp->gr_secflavors, grp->gr_numsecflavors) == 0 && + compare_addr(grp, ogrp) == 0) break; if (ogrp != NULL) ogrp->gr_flag |= GR_FND; @@ -2294,13 +2352,53 @@ compare_export(struct exportlist *ep, struct exportlist *oep) } /* + * Compare the addresses in the group. It is safe to return they are not + * the same when the are, so only return they are the same when they are + * exactly the same. + */ +static int +compare_addr(struct grouplist *grp, struct grouplist *ogrp) +{ + struct addrinfo *ai, *oai; + + if (grp->gr_type != ogrp->gr_type) + return (1); + switch (grp->gr_type) { + case GT_HOST: + ai = grp->gr_ptr.gt_addrinfo; + oai = ogrp->gr_ptr.gt_addrinfo; + for (; ai != NULL && oai != NULL; ai = ai->ai_next, + oai = oai->ai_next) { + if (sacmp(ai->ai_addr, oai->ai_addr, NULL) != 0) + return (1); + } + if (ai != NULL || oai != NULL) + return (1); + break; + case GT_NET: + /* First compare the masks and then the nets. */ + if (sacmp((struct sockaddr *)&grp->gr_ptr.gt_net.nt_mask, + (struct sockaddr *)&ogrp->gr_ptr.gt_net.nt_mask, NULL) != 0) + return (1); + if (sacmp((struct sockaddr *)&grp->gr_ptr.gt_net.nt_net, + (struct sockaddr *)&ogrp->gr_ptr.gt_net.nt_net, + (struct sockaddr *)&grp->gr_ptr.gt_net.nt_mask) != 0) + return (1); + break; + default: + 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 + * The algorithm is inefficient, but the common case of identical 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. @@ -2776,11 +2874,11 @@ parsesec(char *seclist, struct exportlist *ep) */ static int do_opt(char **cpp, char **endcpp, struct exportlist *ep, struct grouplist *grp, - int *has_hostp, uint64_t *exflagsp, struct expcred *cr) + int *has_hostp, uint64_t *exflagsp, struct expcred *cr, char *unvis_dir) { char *cpoptarg, *cpoptend; char *cp, *endcp, *cpopt, savedc, savedc2; - int allflag, usedarg; + int allflag, usedarg, fnd_equal; savedc2 = '\0'; cpopt = *cpp; @@ -2791,14 +2889,18 @@ do_opt(char **cpp, char **endcpp, struct exportlist *ep, struct grouplist *grp, while (cpopt && *cpopt) { allflag = 1; usedarg = -2; + fnd_equal = 0; if ((cpoptend = strchr(cpopt, ','))) { *cpoptend++ = '\0'; - if ((cpoptarg = strchr(cpopt, '='))) + if ((cpoptarg = strchr(cpopt, '='))) { *cpoptarg++ = '\0'; + fnd_equal = 1; + } } else { - if ((cpoptarg = strchr(cpopt, '='))) + if ((cpoptarg = strchr(cpopt, '='))) { *cpoptarg++ = '\0'; - else { + fnd_equal = 1; + } else { *cp = savedc; nextfield(&cp, &endcp); **endcpp = '\0'; @@ -2811,6 +2913,10 @@ do_opt(char **cpp, char **endcpp, struct exportlist *ep, struct grouplist *grp, } } if (!strcmp(cpopt, "ro") || !strcmp(cpopt, "o")) { + if (fnd_equal == 1) { + syslog(LOG_ERR, "= after op: %s", cpopt); + return (1); + } *exflagsp |= MNT_EXRDONLY; } else if (cpoptarg && (!strcmp(cpopt, "maproot") || !(allflag = strcmp(cpopt, "mapall")) || @@ -2849,15 +2955,37 @@ do_opt(char **cpp, char **endcpp, struct exportlist *ep, struct grouplist *grp, usedarg++; opt_flags |= OP_NET; } else if (!strcmp(cpopt, "alldirs")) { + if (fnd_equal == 1) { + syslog(LOG_ERR, "= after op: %s", cpopt); + return (1); + } + if ((opt_flags & OP_NOTROOT) != 0) { + syslog(LOG_ERR, "%s: path %s not mount point", + cpopt, unvis_dir); + if (alldirs_fail != 0) + return (1); + } opt_flags |= OP_ALLDIRS; } else if (!strcmp(cpopt, "public")) { + if (fnd_equal == 1) { + syslog(LOG_ERR, "= after op: %s", cpopt); + return (1); + } *exflagsp |= MNT_EXPUBLIC; } else if (!strcmp(cpopt, "webnfs")) { + if (fnd_equal == 1) { + syslog(LOG_ERR, "= after op: %s", cpopt); + return (1); + } *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")) { + if (fnd_equal == 1) { + syslog(LOG_ERR, "= after op: %s", cpopt); + return (1); + } opt_flags |= OP_QUIET; } else if (cpoptarg && !strcmp(cpopt, "sec")) { if (parsesec(cpoptarg, ep)) @@ -2865,10 +2993,22 @@ do_opt(char **cpp, char **endcpp, struct exportlist *ep, struct grouplist *grp, opt_flags |= OP_SEC; usedarg++; } else if (!strcmp(cpopt, "tls")) { + if (fnd_equal == 1) { + syslog(LOG_ERR, "= after op: %s", cpopt); + return (1); + } *exflagsp |= MNT_EXTLS; } else if (!strcmp(cpopt, "tlscert")) { + if (fnd_equal == 1) { + syslog(LOG_ERR, "= after op: %s", cpopt); + return (1); + } *exflagsp |= (MNT_EXTLS | MNT_EXTLSCERT); } else if (!strcmp(cpopt, "tlscertuser")) { + if (fnd_equal == 1) { + syslog(LOG_ERR, "= after op: %s", cpopt); + return (1); + } *exflagsp |= (MNT_EXTLS | MNT_EXTLSCERT | MNT_EXTLSCERTUSER); } else { @@ -3104,11 +3244,11 @@ do_mount(struct exportlist *ep, struct grouplist *grp, uint64_t exflags, 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)); - } + /* + * Use the memory pointed to by 'anoncrp', as it outlives 'eap' which is + * local to this function. + */ + eap->ex_groups = anoncrp->cr_groups; LOGDEBUG("do_mount exflags=0x%jx", (uintmax_t)exflags); eap->ex_indexfile = ep->ex_indexfile; if (grp->gr_type == GT_HOST) @@ -3221,7 +3361,8 @@ do_mount(struct exportlist *ep, struct grouplist *grp, uint64_t exflags, ret = 1; goto error_exit; } - if (opt_flags & OP_ALLDIRS) { + if ((opt_flags & OP_ALLDIRS) && + alldirs_fail != 0) { if (errno == EINVAL) syslog(LOG_ERR, "-alldirs requested but %s is not a filesystem mountpoint", @@ -3298,19 +3439,7 @@ skip: 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); - } + free_iovec(&iov, &iovlen); return (ret); } @@ -3385,6 +3514,7 @@ get_net(char *cp, struct netmsk *net, int maskflg) goto fail; bcopy(sa, &net->nt_mask, sa->sa_len); opt_flags |= OP_HAVEMASK; + opt_flags &= ~OP_CLASSMASK; } else { /* The specified sockaddr is a network address. */ bcopy(sa, &net->nt_net, sa->sa_len); @@ -3418,9 +3548,6 @@ get_net(char *cp, struct netmsk *net, int maskflg) (opt_flags & OP_MASK) == 0) { in_addr_t addr; - syslog(LOG_WARNING, - "WARNING: No mask specified for %s, " - "using out-of-date default", name); addr = ((struct sockaddr_in *)sa)->sin_addr.s_addr; if (IN_CLASSA(addr)) preflen = 8; @@ -3435,7 +3562,7 @@ get_net(char *cp, struct netmsk *net, int maskflg) bcopy(sa, &net->nt_mask, sa->sa_len); makemask(&net->nt_mask, (int)preflen); - opt_flags |= OP_HAVEMASK; + opt_flags |= OP_HAVEMASK | OP_CLASSMASK; } } @@ -3528,84 +3655,64 @@ get_line(void) * Parse a description of a credential. */ static void -parsecred(char *namelist, struct expcred *cr) +parsecred(char *names, struct expcred *cr) { char *name; - int inpos; - char *names; struct passwd *pw; - struct group *gr; - gid_t groups[NGROUPS_MAX + 1]; - int ngroups; unsigned long name_ul; char *end = NULL; + assert(cr->cr_groups == tmp_groups); + /* - * 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. + * Parse the user and if possible get its password table entry. + * 'cr_uid' is filled when exiting this block. */ - names = namelist; name = strsep_quote(&names, ":"); - /* Bug? name could be NULL here */ name_ul = strtoul(name, &end, 10); if (*end != '\0' || end == name) pw = getpwnam(name); else pw = getpwuid((uid_t)name_ul); + if (pw != NULL) { + cr->cr_uid = pw->pw_uid; + } else if (*end != '\0' || end == name) { + syslog(LOG_ERR, "unknown user: %s", name); + cr->cr_uid = UID_NOBODY; + goto nogroup; + } else { + cr->cr_uid = name_ul; + } + /* - * Credentials specified as those of a user. + * Credentials specified as those of a user (i.e., use its associated + * groups as specified in the password database). */ if (names == NULL) { if (pw == NULL) { - syslog(LOG_ERR, "unknown user: %s", name); - return; + syslog(LOG_ERR, "no passwd entry for user: %s, " + "can't determine groups", name); + goto nogroup; } - cr->cr_uid = pw->pw_uid; - ngroups = NGROUPS_MAX + 1; - if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups)) { + + cr->cr_ngroups = tngroups_max; + if (getgrouplist(pw->pw_name, pw->pw_gid, + cr->cr_groups, &cr->cr_ngroups) != 0) { syslog(LOG_ERR, "too many groups"); - ngroups = NGROUPS_MAX + 1; + cr->cr_ngroups = tngroups_max; } - - /* - * 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: + * Explicit credentials specified as a colon separated list: * uid:gid:gid:... */ - if (pw != NULL) { - cr->cr_uid = pw->pw_uid; - } else if (*end != '\0' || end == name) { - syslog(LOG_ERR, "unknown user: %s", name); - return; - } else { - cr->cr_uid = name_ul; - } cr->cr_ngroups = 0; - while (names != NULL && *names != '\0' && cr->cr_ngroups < NGROUPS_MAX) { + while (names != NULL && *names != '\0') { + const struct group *gr; + gid_t group; + name = strsep_quote(&names, ":"); name_ul = strtoul(name, &end, 10); if (*end != '\0' || end == name) { @@ -3613,16 +3720,23 @@ parsecred(char *namelist, struct expcred *cr) syslog(LOG_ERR, "unknown group: %s", name); continue; } - groups[cr->cr_ngroups++] = gr->gr_gid; + group = gr->gr_gid; } else { - groups[cr->cr_ngroups++] = name_ul; + group = name_ul; } + if (cr->cr_ngroups == tngroups_max) { + syslog(LOG_ERR, "too many groups"); + break; + } + cr->cr_groups[cr->cr_ngroups++] = group; } - 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)); + if (cr->cr_ngroups == 0) + goto nogroup; + return; + +nogroup: + cr->cr_ngroups = 1; + cr->cr_groups[0] = nogroup(); } #define STRSIZ (MNTNAMLEN+MNTPATHLEN+50) @@ -3786,29 +3900,76 @@ check_options(struct dirlist *dp) return (0); } +static int +check_path_component(const char *path, char **err) +{ + struct stat sb; + + if (lstat(path, &sb)) { + asprintf(err, "%s: lstat() failed: %s.\n", + path, strerror(errno)); + return (0); + } + + switch (sb.st_mode & S_IFMT) { + case S_IFDIR: + return (1); + case S_IFLNK: + asprintf(err, "%s: path is a symbolic link.\n", path); + break; + case S_IFREG: + asprintf(err, "%s: path is a file rather than a directory.\n", + path); + break; + default: + asprintf(err, "%s: path is not a directory.\n", path); + } + + return (0); +} + /* - * Check an absolute directory path for any symbolic links. Return true + * Check each path component for the presence of symbolic links. Return true */ static int -check_dirpath(char *dirp) +check_dirpath(char *dirp, char **err) { char *cp; - int ret = 1; - struct stat sb; cp = dirp + 1; - while (*cp && ret) { + while (*cp) { if (*cp == '/') { *cp = '\0'; - if (lstat(dirp, &sb) < 0 || !S_ISDIR(sb.st_mode)) - ret = 0; + + if (!check_path_component(dirp, err)) { + *cp = '/'; + return (0); + } + *cp = '/'; } cp++; } - if (lstat(dirp, &sb) < 0 || !S_ISDIR(sb.st_mode)) - ret = 0; - return (ret); + + if (!check_path_component(dirp, err)) + return (0); + + return (1); +} + +/* + * Populate statfs information. Return true on success. + */ +static int +check_statfs(const char *dirp, struct statfs *fsb, char **err) +{ + if (statfs(dirp, fsb)) { + asprintf(err, "%s: statfs() failed: %s\n", dirp, + strerror(errno)); + return (0); + } + + return (1); } /* @@ -3939,6 +4100,7 @@ huphandler(int sig __unused) static void terminate(int sig __unused) { + free(tmp_groups); pidfile_remove(pfh); rpcb_unset(MOUNTPROG, MOUNTVERS, NULL); rpcb_unset(MOUNTPROG, MOUNTVERS3, NULL); @@ -3958,3 +4120,19 @@ cp_cred(struct expcred *outcr, struct expcred *incr) memcpy(outcr->cr_groups, incr->cr_groups, incr->cr_ngroups * sizeof(gid_t)); } + +static gid_t +nogroup() +{ + static gid_t nogroup = 0; /* 0 means unset. */ + + if (nogroup == 0) { + const struct group *gr = getgrnam("nogroup"); + + if (gr != NULL && gr->gr_gid != 0) + nogroup = gr->gr_gid; + else + nogroup = GID_NOGROUP; + } + return (nogroup); +} |