/* $NetBSD: mountd.c,v 1.8 2013/10/19 17:45:00 christos Exp $ */ /* * 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. */ #include <sys/cdefs.h> #ifndef lint __COPYRIGHT("@(#) Copyright (c) 1989, 1993\ The Regents of the University of California. All rights reserved."); #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)mountd.c 8.15 (Berkeley) 5/1/95"; #else __RCSID("$NetBSD: mountd.c,v 1.8 2013/10/19 17:45:00 christos Exp $"); #endif #endif /* not lint */ #include <sys/param.h> #include <sys/file.h> #include <sys/ioctl.h> #include <sys/mount.h> #include <sys/socket.h> #include <sys/stat.h> #include <syslog.h> #include <sys/ucred.h> #include <rpc/rpc.h> #include <rpc/pmap_clnt.h> #include <rpc/pmap_prot.h> #include <rpcsvc/mount.h> #include <nfs/rpcv2.h> #include <nfs/nfsproto.h> #include <nfs/nfs.h> #include <nfs/nfsmount.h> #include <arpa/inet.h> #include <rump/rump.h> #include <rump/rump_syscalls.h> #include <ctype.h> #include <errno.h> #include <grp.h> #include <netdb.h> #include <pwd.h> #include <netgroup.h> #include <pthread.h> #include <semaphore.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <err.h> #include <util.h> #include "pathnames.h" #ifdef IPSEC #include <netinet6/ipsec.h> #ifndef IPSEC_POLICY_IPSEC /* no ipsec support on old ipsec */ #undef IPSEC #endif #include "ipsec.h" #endif #include "svc_fdset.h" #include <stdarg.h> /* * Structures for keeping the mount list and export list */ struct mountlist { struct mountlist *ml_next; char ml_host[RPCMNT_NAMELEN + 1]; char ml_dirp[RPCMNT_PATHLEN + 1]; int ml_flag;/* XXX more flags (same as dp_flag) */ }; 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[1]; /* Actually malloc'd to size of dir */ }; /* dp_flag bits */ #define DP_DEFSET 0x1 #define DP_HOSTSET 0x2 #define DP_KERB 0x4 #define DP_NORESMNT 0x8 struct exportlist { struct exportlist *ex_next; struct dirlist *ex_dirl; struct dirlist *ex_defdir; int ex_flag; fsid_t ex_fs; char *ex_fsdir; char *ex_indexfile; }; /* ex_flag bits */ #define EX_LINKED 0x1 struct netmsk { struct sockaddr_storage nt_net; int nt_len; 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; }; /* Group types */ #define GT_NULL 0x0 #define GT_HOST 0x1 #define GT_NET 0x2 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; size_t fhr_fhsize; union { uint8_t v2[NFSX_V2FH]; uint8_t v3[NFSX_V3FHMAX]; } fhr_fh; }; /* Global defs */ static char *add_expdir __P((struct dirlist **, char *, int)); static void add_dlist __P((struct dirlist **, struct dirlist *, struct grouplist *, int)); static void add_mlist __P((char *, char *, int)); static int check_dirpath __P((const char *, size_t, char *)); static int check_options __P((const char *, size_t, struct dirlist *)); static int chk_host __P((struct dirlist *, struct sockaddr *, int *, int *)); static int del_mlist __P((char *, char *, struct sockaddr *)); static struct dirlist *dirp_search __P((struct dirlist *, char *)); static int do_nfssvc __P((const char *, size_t, struct exportlist *, struct grouplist *, int, struct uucred *, char *, int, struct statvfs *)); static int do_opt __P((const char *, size_t, char **, char **, struct exportlist *, struct grouplist *, int *, int *, struct uucred *)); static struct exportlist *ex_search __P((fsid_t *)); static int parse_directory __P((const char *, size_t, struct grouplist *, int, char *, struct exportlist **, struct statvfs *)); static int parse_host_netgroup __P((const char *, size_t, struct exportlist *, struct grouplist *, char *, int *, struct grouplist **)); static struct exportlist *get_exp __P((void)); static void free_dir __P((struct dirlist *)); static void free_exp __P((struct exportlist *)); static void free_grp __P((struct grouplist *)); static void free_host __P((struct hostlist *)); void get_exportlist __P((int)); static int get_host __P((const char *, size_t, const char *, struct grouplist *)); static struct hostlist *get_ht __P((void)); static void get_mountlist __P((void)); static int get_net __P((char *, struct netmsk *, int)); static void free_exp_grp __P((struct exportlist *, struct grouplist *)); static struct grouplist *get_grp __P((void)); static void hang_dirp __P((struct dirlist *, struct grouplist *, struct exportlist *, int)); static void mntsrv __P((struct svc_req *, SVCXPRT *)); static void nextfield __P((char **, char **)); static void parsecred __P((char *, struct uucred *)); static int put_exlist __P((struct dirlist *, XDR *, struct dirlist *, int *)); static int scan_tree __P((struct dirlist *, struct sockaddr *)); static void send_umntall __P((int)); #if 0 static int umntall_each __P((caddr_t, struct sockaddr_in *)); #endif static int xdr_dir __P((XDR *, char *)); static int xdr_explist __P((XDR *, caddr_t)); static int xdr_fhs __P((XDR *, caddr_t)); static int xdr_mlist __P((XDR *, caddr_t)); static int bitcmp __P((void *, void *, int)); static int netpartcmp __P((struct sockaddr *, struct sockaddr *, int)); static int sacmp __P((struct sockaddr *, struct sockaddr *)); static int allones __P((struct sockaddr_storage *, int)); static int countones __P((struct sockaddr *)); static void bind_resv_port __P((int, sa_family_t, in_port_t)); static void no_nfs(int); static struct exportlist *exphead; static struct mountlist *mlhead; static struct grouplist *grphead; static char *exname; static struct uucred def_anon = { 1, (uid_t) -2, (gid_t) -2, 0, { 0 } }; static int opt_flags; static int have_v6 = 1; static const int ninumeric = NI_NUMERICHOST; /* Bits for above */ #define OP_MAPROOT 0x001 #define OP_MAPALL 0x002 #define OP_KERB 0x004 #define OP_MASK 0x008 #define OP_NET 0x010 #define OP_ALLDIRS 0x040 #define OP_NORESPORT 0x080 #define OP_NORESMNT 0x100 #define OP_MASKLEN 0x200 static int debug = 1; #if 0 static void SYSLOG __P((int, const char *,...)); #endif int main __P((int, char *[])); /* * If this is non-zero, -noresvport and -noresvmnt are implied for * each export. */ static int noprivports; #define C2FD(_c_) ((int)(uintptr_t)(_c_)) static int rumpread(void *cookie, char *buf, int count) { return rump_sys_read(C2FD(cookie), buf, count); } static int rumpwrite(void *cookie, const char *buf, int count) { return rump_sys_write(C2FD(cookie), buf, count); } static off_t rumpseek(void *cookie, off_t off, int whence) { return rump_sys_lseek(C2FD(cookie), off, whence); } static int rumpclose(void *cookie) { return rump_sys_close(C2FD(cookie)); } int __sflags(const char *, int *); /* XXX */ static FILE * rumpfopen(const char *path, const char *opts) { int fd, oflags; __sflags(opts, &oflags); fd = rump_sys_open(path, oflags, 0777); if (fd == -1) return NULL; return funopen((void *)(uintptr_t)fd, rumpread, rumpwrite, rumpseek, rumpclose); } /* * Make sure mountd signal handler is executed from a thread context * instead of the signal handler. This avoids the signal handler * ruining our kernel context. */ static sem_t exportsem; static void signal_get_exportlist(int sig) { sem_post(&exportsem); } static void * exportlist_thread(void *arg) { for (;;) { sem_wait(&exportsem); get_exportlist(0); } return NULL; } /* * 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 * "-d" to enable debugging * and "-n" to allow nonroot mount. */ void *mountd_main(void *); void * mountd_main(void *arg) { SVCXPRT *udptransp, *tcptransp; struct netconfig *udpconf, *tcpconf; int udpsock, tcpsock; int xcreated = 0; int maxrec = RPC_MAXDATASIZE; in_port_t forcedport = 0; extern sem_t gensem; pthread_t ptdummy; alloc_fdset(); #if 0 while ((c = getopt(argc, argv, "dNnrp:" ADDOPTS)) != -1) switch (c) { #ifdef IPSEC case 'P': if (ipsecsetup_test(policy = optarg)) errx(1, "Invalid ipsec policy `%s'", policy); break; #endif case 'p': /* A forced port "0" will dynamically allocate a port */ forcedport = atoi(optarg); break; case 'd': debug = 1; break; case 'N': noprivports = 1; break; /* Compatibility */ case 'n': case 'r': break; default: fprintf(stderr, "usage: %s [-dNn]" #ifdef IPSEC " [-P policy]" #endif " [-p port] [exportsfile]\n", getprogname()); exit(1); }; argc -= optind; argv += optind; #endif sem_init(&exportsem, 0, 0); pthread_create(&ptdummy, NULL, exportlist_thread, NULL); grphead = NULL; exphead = NULL; mlhead = NULL; exname = _PATH_EXPORTS; openlog("mountd", LOG_PID | (debug ? LOG_PERROR : 0), LOG_DAEMON); (void)signal(SIGSYS, no_nfs); if (debug) (void)fprintf(stderr, "Getting export list.\n"); get_exportlist(0); if (debug) (void)fprintf(stderr, "Getting mount list.\n"); get_mountlist(); if (debug) (void)fprintf(stderr, "Here we go.\n"); if (debug == 0) { daemon(0, 0); (void)signal(SIGINT, SIG_IGN); (void)signal(SIGQUIT, SIG_IGN); } (void)signal(SIGHUP, signal_get_exportlist); (void)signal(SIGTERM, send_umntall); pidfile(NULL); rpcb_unset(RPCPROG_MNT, RPCMNT_VER1, NULL); rpcb_unset(RPCPROG_MNT, RPCMNT_VER3, NULL); udpsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); tcpsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); udpconf = getnetconfigent("udp"); tcpconf = getnetconfigent("tcp"); rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec); if (udpsock != -1 && udpconf != NULL) { bind_resv_port(udpsock, AF_INET, forcedport); #ifdef IPSEC if (policy) ipsecsetup(AF_INET, udpsock, policy); #endif udptransp = svc_dg_create(udpsock, 0, 0); if (udptransp != NULL) { if (!svc_reg(udptransp, RPCPROG_MNT, RPCMNT_VER1, mntsrv, udpconf) || !svc_reg(udptransp, RPCPROG_MNT, RPCMNT_VER3, mntsrv, udpconf)) { syslog(LOG_WARNING, "can't register UDP service"); } else { xcreated++; } } else { syslog(LOG_WARNING, "can't create UDP service"); } } if (tcpsock != -1 && tcpconf != NULL) { bind_resv_port(tcpsock, AF_INET, forcedport); #ifdef IPSEC if (policy) ipsecsetup(AF_INET, tcpsock, policy); #endif listen(tcpsock, SOMAXCONN); tcptransp = svc_vc_create(tcpsock, RPC_MAXDATASIZE, RPC_MAXDATASIZE); if (tcptransp != NULL) { if (!svc_reg(tcptransp, RPCPROG_MNT, RPCMNT_VER1, mntsrv, tcpconf) || !svc_reg(tcptransp, RPCPROG_MNT, RPCMNT_VER3, mntsrv, tcpconf)) syslog(LOG_WARNING, "can't register TCP service"); else xcreated++; } else syslog(LOG_WARNING, "can't create TCP service"); } if (xcreated == 0) { syslog(LOG_ERR, "could not create any services"); exit(1); } sem_post(&gensem); svc_run(); syslog(LOG_ERR, "Mountd died"); exit(1); } /* * The mount rpc service */ void mntsrv(rqstp, transp) struct svc_req *rqstp; SVCXPRT *transp; { struct exportlist *ep; struct dirlist *dp; struct fhreturn fhr; struct stat stb; struct statvfs fsb; char host[NI_MAXHOST], numerichost[NI_MAXHOST]; int lookup_failed = 1; struct sockaddr *saddr; u_short sport; char rpcpath[RPCMNT_PATHLEN + 1], dpath[MAXPATHLEN]; long bad = EACCES; int defset, hostset, ret; sigset_t sighup_mask; struct sockaddr_in6 *sin6; struct sockaddr_in *sin; size_t fh_size; int error = 0; (void)sigemptyset(&sighup_mask); (void)sigaddset(&sighup_mask, SIGHUP); saddr = svc_getrpccaller(transp)->buf; switch (saddr->sa_family) { case AF_INET6: sin6 = (struct sockaddr_in6 *)saddr; sport = ntohs(sin6->sin6_port); break; case AF_INET: sin = (struct sockaddr_in *)saddr; sport = ntohs(sin->sin_port); break; default: syslog(LOG_ERR, "request from unknown address family"); return; } lookup_failed = getnameinfo(saddr, saddr->sa_len, host, sizeof host, NULL, 0, 0); if (getnameinfo(saddr, saddr->sa_len, numerichost, sizeof numerichost, NULL, 0, ninumeric) != 0) strlcpy(numerichost, "?", sizeof(numerichost)); ret = 0; 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 (debug) fprintf(stderr, "got mount request from %s\n", numerichost); if (!svc_getargs(transp, xdr_dir, rpcpath)) { if (debug) fprintf(stderr, "-> garbage args\n"); svcerr_decode(transp); return; } if (debug) fprintf(stderr, "-> rpcpath: %s\n", rpcpath); /* * Get the real pathname and make sure it is a file or * directory that exists. */ #if 0 if (realpath(rpcpath, dpath) == 0 || #endif strcpy(dpath, rpcpath); if (rump_sys_stat(dpath, &stb) < 0 || (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) || rump_sys_statvfs1(dpath, &fsb, ST_WAIT) < 0) { (void)chdir("/"); /* Just in case realpath doesn't */ if (debug) (void)fprintf(stderr, "-> stat failed on %s\n", dpath); if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t) &bad)) syslog(LOG_ERR, "Can't send reply"); return; } if (debug) fprintf(stderr, "-> dpath: %s\n", dpath); /* Check in the exports list */ (void)sigprocmask(SIG_BLOCK, &sighup_mask, NULL); ep = ex_search(&fsb.f_fsidx); hostset = defset = 0; if (ep && (chk_host(ep->ex_defdir, saddr, &defset, &hostset) || ((dp = dirp_search(ep->ex_dirl, dpath)) && chk_host(dp, saddr, &defset, &hostset)) || (defset && scan_tree(ep->ex_defdir, saddr) == 0 && scan_tree(ep->ex_dirl, saddr) == 0))) { if ((hostset & DP_HOSTSET) == 0) { hostset = defset; } if (sport >= IPPORT_RESERVED && !(hostset & DP_NORESMNT)) { syslog(LOG_NOTICE, "Refused mount RPC from host %s port %d", numerichost, sport); svcerr_weakauth(transp); goto out; } fhr.fhr_flag = hostset; fhr.fhr_vers = rqstp->rq_vers; /* Get the file handle */ memset(&fhr.fhr_fh, 0, sizeof(fhr.fhr_fh)); /* for v2 */ fh_size = sizeof(fhr.fhr_fh); error = 0; if (rump_sys_getfh(dpath, &fhr.fhr_fh, &fh_size) < 0) { bad = error; //syslog(LOG_ERR, "Can't get fh for %s %d %d", dpath, error, fh_size); if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (char *)&bad)) syslog(LOG_ERR, "Can't send reply"); goto out; } if ((fhr.fhr_vers == 1 && fh_size > NFSX_V2FH) || fh_size > NFSX_V3FHMAX) { bad = EINVAL; /* XXX */ if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (char *)&bad)) syslog(LOG_ERR, "Can't send reply"); goto out; } fhr.fhr_fhsize = fh_size; if (!svc_sendreply(transp, (xdrproc_t)xdr_fhs, (char *) &fhr)) syslog(LOG_ERR, "Can't send reply"); if (!lookup_failed) add_mlist(host, dpath, hostset); else add_mlist(numerichost, dpath, hostset); if (debug) (void)fprintf(stderr, "Mount successful.\n"); } else { if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t) &bad)) syslog(LOG_ERR, "Can't send reply"); } out: (void)sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; case MOUNTPROC_DUMP: if (!svc_sendreply(transp, (xdrproc_t)xdr_mlist, NULL)) syslog(LOG_ERR, "Can't send reply"); return; case MOUNTPROC_UMNT: if (!svc_getargs(transp, xdr_dir, dpath)) { svcerr_decode(transp); return; } if (!lookup_failed) ret = del_mlist(host, dpath, saddr); ret |= del_mlist(numerichost, dpath, saddr); if (ret) { svcerr_weakauth(transp); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL)) syslog(LOG_ERR, "Can't send reply"); return; case MOUNTPROC_UMNTALL: if (!lookup_failed) ret = del_mlist(host, NULL, saddr); ret |= del_mlist(numerichost, NULL, saddr); if (ret) { svcerr_weakauth(transp); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL)) syslog(LOG_ERR, "Can't send reply"); return; case MOUNTPROC_EXPORT: case MOUNTPROC_EXPORTALL: if (!svc_sendreply(transp, (xdrproc_t)xdr_explist, NULL)) syslog(LOG_ERR, "Can't send reply"); return; default: svcerr_noproc(transp); return; } } /* * Xdr conversion for a dpath string */ static int xdr_dir(xdrsp, dirp) XDR *xdrsp; char *dirp; { return (xdr_string(xdrsp, &dirp, RPCMNT_PATHLEN)); } /* * Xdr routine to generate file handle reply */ static int xdr_fhs(xdrsp, cp) XDR *xdrsp; caddr_t cp; { struct fhreturn *fhrp = (struct fhreturn *) cp; long ok = 0, len, auth; 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 = fhrp->fhr_fhsize; if (!xdr_long(xdrsp, &len)) return (0); if (!xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, len)) return (0); if (fhrp->fhr_flag & DP_KERB) auth = RPCAUTH_KERB4; else auth = RPCAUTH_UNIX; len = 1; if (!xdr_long(xdrsp, &len)) return (0); return (xdr_long(xdrsp, &auth)); }; return (0); } int xdr_mlist(xdrsp, cp) XDR *xdrsp; caddr_t cp; { struct mountlist *mlp; int trueval = 1; int falseval = 0; char *strp; mlp = mlhead; while (mlp) { if (!xdr_bool(xdrsp, &trueval)) return (0); strp = &mlp->ml_host[0]; if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN)) return (0); strp = &mlp->ml_dirp[0]; if (!xdr_string(xdrsp, &strp, RPCMNT_PATHLEN)) return (0); mlp = mlp->ml_next; } if (!xdr_bool(xdrsp, &falseval)) return (0); return (1); } /* * Xdr conversion for export list */ int xdr_explist(xdrsp, cp) XDR *xdrsp; caddr_t cp; { struct exportlist *ep; int falseval = 0; int putdef; sigset_t sighup_mask; (void)sigemptyset(&sighup_mask); (void)sigaddset(&sighup_mask, SIGHUP); (void)sigprocmask(SIG_BLOCK, &sighup_mask, NULL); ep = exphead; while (ep) { putdef = 0; if (put_exlist(ep->ex_dirl, xdrsp, ep->ex_defdir, &putdef)) goto errout; if (ep->ex_defdir && putdef == 0 && put_exlist(ep->ex_defdir, xdrsp, NULL, &putdef)) goto errout; ep = ep->ex_next; } (void)sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); if (!xdr_bool(xdrsp, &falseval)) return (0); return (1); errout: (void)sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return (0); } /* * Called from xdr_explist() to traverse the tree and export the * directory paths. Assumes SIGHUP has already been masked. */ int put_exlist(dp, xdrsp, adp, putdefp) struct dirlist *dp; XDR *xdrsp; struct dirlist *adp; int *putdefp; { struct grouplist *grp; struct hostlist *hp; int trueval = 1; int falseval = 0; int gotalldir = 0; char *strp; if (dp) { if (put_exlist(dp->dp_left, xdrsp, adp, putdefp)) return (1); if (!xdr_bool(xdrsp, &trueval)) return (1); strp = dp->dp_dirp; if (!xdr_string(xdrsp, &strp, RPCMNT_PATHLEN)) return (1); if (adp && !strcmp(dp->dp_dirp, adp->dp_dirp)) { gotalldir = 1; *putdefp = 1; } 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, &trueval)) return (1); strp = grp->gr_ptr.gt_addrinfo->ai_canonname; if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN)) return (1); } else if (grp->gr_type == GT_NET) { if (!xdr_bool(xdrsp, &trueval)) return (1); strp = grp->gr_ptr.gt_net.nt_name; if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN)) return (1); } hp = hp->ht_next; if (gotalldir && hp == NULL) { hp = adp->dp_hosts; gotalldir = 0; } } } if (!xdr_bool(xdrsp, &falseval)) return (1); if (put_exlist(dp->dp_right, xdrsp, adp, putdefp)) return (1); } return (0); } static int parse_host_netgroup(line, lineno, ep, tgrp, cp, has_host, grp) const char *line; size_t lineno; struct exportlist *ep; struct grouplist *tgrp; char *cp; int *has_host; struct grouplist **grp; { const char *hst, *usr, *dom; int netgrp; if (ep == NULL) { syslog(LOG_ERR, "\"%s\", line %ld: No current export", line, (unsigned long)lineno); return 0; } setnetgrent(cp); netgrp = getnetgrent(&hst, &usr, &dom); do { if (*has_host) { (*grp)->gr_next = get_grp(); *grp = (*grp)->gr_next; } if (netgrp) { if (hst == NULL) { syslog(LOG_ERR, "\"%s\", line %ld: No host in netgroup %s", line, (unsigned long)lineno, cp); goto bad; } if (get_host(line, lineno, hst, *grp)) goto bad; } else if (get_host(line, lineno, cp, *grp)) goto bad; *has_host = TRUE; } while (netgrp && getnetgrent(&hst, &usr, &dom)); endnetgrent(); return 1; bad: endnetgrent(); return 0; } static int parse_directory(line, lineno, tgrp, got_nondir, cp, ep, fsp) const char *line; size_t lineno; struct grouplist *tgrp; int got_nondir; char *cp; struct exportlist **ep; struct statvfs *fsp; { int error = 0; if (!check_dirpath(line, lineno, cp)) return 0; if (rump_sys_statvfs1(cp, fsp, ST_WAIT) == -1) { syslog(LOG_ERR, "\"%s\", line %ld: statvfs for `%s' failed: %m %d", line, (unsigned long)lineno, cp, error); return 0; } if (got_nondir) { syslog(LOG_ERR, "\"%s\", line %ld: Directories must precede files", line, (unsigned long)lineno); return 0; } if (*ep) { if ((*ep)->ex_fs.__fsid_val[0] != fsp->f_fsidx.__fsid_val[0] || (*ep)->ex_fs.__fsid_val[1] != fsp->f_fsidx.__fsid_val[1]) { syslog(LOG_ERR, "\"%s\", line %ld: filesystem ids disagree", line, (unsigned long)lineno); return 0; } } else { /* * See if this directory is already * in the list. */ *ep = ex_search(&fsp->f_fsidx); if (*ep == NULL) { *ep = get_exp(); (*ep)->ex_fs = fsp->f_fsidx; (*ep)->ex_fsdir = estrdup(fsp->f_mntonname); if (debug) (void)fprintf(stderr, "Making new ep fs=0x%x,0x%x\n", fsp->f_fsidx.__fsid_val[0], fsp->f_fsidx.__fsid_val[1]); } else { if (debug) (void)fprintf(stderr, "Found ep fs=0x%x,0x%x\n", fsp->f_fsidx.__fsid_val[0], fsp->f_fsidx.__fsid_val[1]); } } return 1; } /* * Get the export list */ /* ARGSUSED */ void get_exportlist(n) int n; { struct exportlist *ep, *ep2; struct grouplist *grp, *tgrp; struct exportlist **epp; struct dirlist *dirhead; struct statvfs fsb, *fsp; struct addrinfo *ai; struct uucred anon; char *cp, *endcp, *dirp, savedc; int has_host, exflags, got_nondir, dirplen, num, i; FILE *exp_file; char *line; size_t lineno = 0, len; /* * First, get rid of the old list */ ep = exphead; while (ep) { ep2 = ep; ep = ep->ex_next; free_exp(ep2); } exphead = NULL; dirp = NULL; dirplen = 0; grp = grphead; while (grp) { tgrp = grp; grp = grp->gr_next; free_grp(tgrp); } grphead = NULL; /* * And delete exports that are in the kernel for all local * file systems. */ num = getmntinfo(&fsp, MNT_NOWAIT); for (i = 0; i < num; i++) { struct mountd_exports_list mel; /* Delete all entries from the export list. */ mel.mel_path = fsp->f_mntonname; mel.mel_nexports = 0; if (rump_sys_nfssvc(NFSSVC_SETEXPORTSLIST, &mel) == -1 && errno != EOPNOTSUPP) syslog(LOG_ERR, "Can't delete exports for %s (%m)", fsp->f_mntonname); fsp++; } /* * Read in the exports file and build the list, calling * mount() as we go along to push the export rules into the kernel. */ exname = _PATH_EXPORTS; if ((exp_file = rumpfopen(exname, "r")) == NULL) { /* * Don't exit here; we can still reload the config * after a SIGHUP. */ if (debug) (void)fprintf(stderr, "Can't open %s: %s\n", exname, strerror(errno)); return; } dirhead = NULL; while ((line = fparseln(exp_file, &len, &lineno, NULL, 0)) != NULL) { if (debug) (void)fprintf(stderr, "Got line %s\n", line); cp = line; nextfield(&cp, &endcp); if (cp == endcp) goto nextline; /* skip empty line */ /* * Set defaults. */ has_host = FALSE; anon = def_anon; exflags = MNT_EXPORTED; got_nondir = 0; opt_flags = 0; ep = NULL; if (noprivports) { opt_flags |= OP_NORESMNT | OP_NORESPORT; exflags |= MNT_EXNORESPORT; } /* * Create new exports list entry */ len = endcp - cp; tgrp = grp = get_grp(); while (len > 0) { if (len > RPCMNT_NAMELEN) { *endcp = '\0'; syslog(LOG_ERR, "\"%s\", line %ld: name `%s' is too long", line, (unsigned long)lineno, cp); goto badline; } switch (*cp) { case '-': /* * Option */ if (ep == NULL) { syslog(LOG_ERR, "\"%s\", line %ld: No current export list", line, (unsigned long)lineno); goto badline; } if (debug) (void)fprintf(stderr, "doing opt %s\n", cp); got_nondir = 1; if (do_opt(line, lineno, &cp, &endcp, ep, grp, &has_host, &exflags, &anon)) goto badline; break; case '/': /* * Directory */ savedc = *endcp; *endcp = '\0'; if (!parse_directory(line, lineno, tgrp, got_nondir, cp, &ep, &fsb)) goto badline; /* * Add dirpath to export mount point. */ dirp = add_expdir(&dirhead, cp, len); dirplen = len; *endcp = savedc; break; default: /* * Host or netgroup. */ savedc = *endcp; *endcp = '\0'; if (!parse_host_netgroup(line, lineno, ep, tgrp, cp, &has_host, &grp)) goto badline; got_nondir = 1; *endcp = savedc; break; } cp = endcp; nextfield(&cp, &endcp); len = endcp - cp; } if (check_options(line, lineno, dirhead)) goto badline; if (!has_host) { grp->gr_type = GT_HOST; if (debug) (void)fprintf(stderr, "Adding a default entry\n"); /* add a default group and make the grp list NULL */ ai = emalloc(sizeof(struct addrinfo)); ai->ai_flags = 0; ai->ai_family = AF_INET; /* XXXX */ ai->ai_socktype = SOCK_DGRAM; /* setting the length to 0 will match anything */ ai->ai_addrlen = 0; ai->ai_flags = AI_CANONNAME; ai->ai_canonname = estrdup("Default"); ai->ai_addr = NULL; ai->ai_next = NULL; grp->gr_ptr.gt_addrinfo = ai; } else if ((opt_flags & OP_NET) && tgrp->gr_next) { /* * Don't allow a network export coincide with a list of * host(s) on the same line. */ syslog(LOG_ERR, "\"%s\", line %ld: Mixed exporting of networks and hosts is disallowed", line, (unsigned long)lineno); goto badline; } /* * 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. */ grp = tgrp; do { if (do_nfssvc(line, lineno, ep, grp, exflags, &anon, dirp, dirplen, &fsb)) goto badline; } while (grp->gr_next && (grp = grp->gr_next)); /* * Success. Update the data structures. */ if (has_host) { hang_dirp(dirhead, tgrp, ep, opt_flags); grp->gr_next = grphead; grphead = tgrp; } else { hang_dirp(dirhead, NULL, ep, opt_flags); free_grp(tgrp); } tgrp = NULL; dirhead = NULL; if ((ep->ex_flag & EX_LINKED) == 0) { ep2 = exphead; epp = &exphead; /* * Insert in the list in alphabetical order. */ while (ep2 && strcmp(ep2->ex_fsdir, ep->ex_fsdir) < 0) { epp = &ep2->ex_next; ep2 = ep2->ex_next; } if (ep2) ep->ex_next = ep2; *epp = ep; ep->ex_flag |= EX_LINKED; } goto nextline; badline: free_exp_grp(ep, grp); nextline: if (dirhead) { free_dir(dirhead); dirhead = NULL; } free(line); } (void)fclose(exp_file); } /* * Allocate an export list element */ static struct exportlist * get_exp() { struct exportlist *ep; ep = emalloc(sizeof(struct exportlist)); (void)memset(ep, 0, sizeof(struct exportlist)); return (ep); } /* * Allocate a group list element */ static struct grouplist * get_grp() { struct grouplist *gp; gp = emalloc(sizeof(struct grouplist)); (void)memset(gp, 0, sizeof(struct grouplist)); return (gp); } /* * Clean up upon an error in get_exportlist(). */ static void free_exp_grp(ep, grp) struct exportlist *ep; struct grouplist *grp; { struct grouplist *tgrp; 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) fsid_t *fsid; { struct exportlist *ep; ep = exphead; return ep; while (ep) { if (ep->ex_fs.__fsid_val[0] == fsid->__fsid_val[0] && ep->ex_fs.__fsid_val[1] == fsid->__fsid_val[1]) return (ep); ep = ep->ex_next; } return (ep); } /* * Add a directory path to the list. */ static char * add_expdir(dpp, cp, len) struct dirlist **dpp; char *cp; int len; { struct dirlist *dp; dp = emalloc(sizeof(struct dirlist) + len); dp->dp_left = *dpp; dp->dp_right = NULL; dp->dp_flag = 0; dp->dp_hosts = NULL; (void)strcpy(dp->dp_dirp, cp); *dpp = dp; return (dp->dp_dirp); } /* * Hang the dir list element off the dirpath binary tree as required * and update the entry for host. */ void hang_dirp(dp, grp, ep, flags) struct dirlist *dp; struct grouplist *grp; struct exportlist *ep; int flags; { struct hostlist *hp; struct dirlist *dp2; if (flags & OP_ALLDIRS) { if (ep->ex_defdir) free(dp); else ep->ex_defdir = dp; if (grp == NULL) { ep->ex_defdir->dp_flag |= DP_DEFSET; if (flags & OP_KERB) ep->ex_defdir->dp_flag |= DP_KERB; if (flags & OP_NORESMNT) ep->ex_defdir->dp_flag |= DP_NORESMNT; } else while (grp) { hp = get_ht(); if (flags & OP_KERB) hp->ht_flag |= DP_KERB; if (flags & OP_NORESMNT) hp->ht_flag |= DP_NORESMNT; hp->ht_grp = grp; hp->ht_next = ep->ex_defdir->dp_hosts; ep->ex_defdir->dp_hosts = hp; 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); 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(dpp, newdp, grp, flags) struct dirlist **dpp; struct dirlist *newdp; struct grouplist *grp; int flags; { 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); return; } else if (cmp < 0) { add_dlist(&dp->dp_right, newdp, grp, flags); return; } else free(newdp); } else { dp = newdp; dp->dp_left = NULL; *dpp = dp; } if (grp) { /* * Hang all of the host(s) off of the directory point. */ do { hp = get_ht(); if (flags & OP_KERB) hp->ht_flag |= DP_KERB; if (flags & OP_NORESMNT) hp->ht_flag |= DP_NORESMNT; hp->ht_grp = grp; hp->ht_next = dp->dp_hosts; dp->dp_hosts = hp; grp = grp->gr_next; } while (grp); } else { dp->dp_flag |= DP_DEFSET; if (flags & OP_KERB) dp->dp_flag |= DP_KERB; if (flags & OP_NORESMNT) dp->dp_flag |= DP_NORESMNT; } } /* * Search for a dirpath on the export point. */ static struct dirlist * dirp_search(dp, dirp) 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); } /* * Some helper functions for netmasks. They all assume masks in network * order (big endian). */ static int bitcmp(void *dst, void *src, int bitlen) { int i; u_int8_t *p1 = dst, *p2 = src; u_int8_t bitmask; int bytelen, bitsleft; bytelen = bitlen / 8; bitsleft = bitlen % 8; if (debug) { printf("comparing:\n"); for (i = 0; i < (bitsleft ? bytelen + 1 : bytelen); i++) printf("%02x", p1[i]); printf("\n"); for (i = 0; i < (bitsleft ? bytelen + 1 : bytelen); i++) printf("%02x", p2[i]); printf("\n"); } for (i = 0; i < bytelen; i++) { if (*p1 != *p2) return 1; p1++; p2++; } for (i = 0; i < bitsleft; i++) { bitmask = 1 << (7 - i); if ((*p1 & bitmask) != (*p2 & bitmask)) return 1; } return 0; } static int netpartcmp(struct sockaddr *s1, struct sockaddr *s2, int bitlen) { void *src, *dst; if (s1->sa_family != s2->sa_family) return 1; switch (s1->sa_family) { case AF_INET: src = &((struct sockaddr_in *)s1)->sin_addr; dst = &((struct sockaddr_in *)s2)->sin_addr; if (bitlen > sizeof(((struct sockaddr_in *)s1)->sin_addr) * 8) return 1; break; case AF_INET6: src = &((struct sockaddr_in6 *)s1)->sin6_addr; dst = &((struct sockaddr_in6 *)s2)->sin6_addr; if (((struct sockaddr_in6 *)s1)->sin6_scope_id != ((struct sockaddr_in6 *)s2)->sin6_scope_id) return 1; if (bitlen > sizeof(((struct sockaddr_in6 *)s1)->sin6_addr) * 8) return 1; break; default: return 1; } return bitcmp(src, dst, bitlen); } static int allones(struct sockaddr_storage *ssp, int bitlen) { u_int8_t *p; int bytelen, bitsleft, i; int zerolen; switch (ssp->ss_family) { case AF_INET: p = (u_int8_t *)&((struct sockaddr_in *)ssp)->sin_addr; zerolen = sizeof (((struct sockaddr_in *)ssp)->sin_addr); break; case AF_INET6: p = (u_int8_t *)&((struct sockaddr_in6 *)ssp)->sin6_addr; zerolen = sizeof (((struct sockaddr_in6 *)ssp)->sin6_addr); break; default: return -1; } memset(p, 0, zerolen); bytelen = bitlen / 8; bitsleft = bitlen % 8; if (bytelen > zerolen) return -1; for (i = 0; i < bytelen; i++) *p++ = 0xff; for (i = 0; i < bitsleft; i++) *p |= 1 << (7 - i); return 0; } static int countones(struct sockaddr *sa) { void *mask; int i, bits = 0, bytelen; u_int8_t *p; switch (sa->sa_family) { case AF_INET: mask = (u_int8_t *)&((struct sockaddr_in *)sa)->sin_addr; bytelen = 4; break; case AF_INET6: mask = (u_int8_t *)&((struct sockaddr_in6 *)sa)->sin6_addr; bytelen = 16; break; default: return 0; } p = mask; for (i = 0; i < bytelen; i++, p++) { if (*p != 0xff) { for (bits = 0; bits < 8; bits++) { if (!(*p & (1 << (7 - bits)))) break; } break; } } return (i * 8 + bits); } static int sacmp(struct sockaddr *sa1, struct sockaddr *sa2) { void *p1, *p2; int len; if (sa1->sa_family != sa2->sa_family) return 1; switch (sa1->sa_family) { case AF_INET: p1 = &((struct sockaddr_in *)sa1)->sin_addr; p2 = &((struct sockaddr_in *)sa2)->sin_addr; len = 4; break; case AF_INET6: p1 = &((struct sockaddr_in6 *)sa1)->sin6_addr; p2 = &((struct sockaddr_in6 *)sa2)->sin6_addr; len = 16; if (((struct sockaddr_in6 *)sa1)->sin6_scope_id != ((struct sockaddr_in6 *)sa2)->sin6_scope_id) return 1; break; default: return 1; } return memcmp(p1, p2, len); } /* * Scan for a host match in a directory tree. */ static int chk_host(dp, saddr, defsetp, hostsetp) struct dirlist *dp; struct sockaddr *saddr; int *defsetp; int *hostsetp; { 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)) { *hostsetp = (hp->ht_flag | DP_HOSTSET); return (1); } } break; case GT_NET: if (!netpartcmp(saddr, (struct sockaddr *) &grp->gr_ptr.gt_net.nt_net, grp->gr_ptr.gt_net.nt_len)) { *hostsetp = (hp->ht_flag | DP_HOSTSET); return (1); } break; }; hp = hp->ht_next; } } return (0); } /* * Scan tree for a host that matches the address. */ static int scan_tree(dp, saddr) 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)) 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(dp) struct dirlist *dp; { if (dp) { free_dir(dp->dp_left); free_dir(dp->dp_right); free_host(dp->dp_hosts); free(dp); } } /* * Parse the option string and update fields. * Option arguments may either be -<option>=<value> or * -<option> <value> */ static int do_opt(line, lineno, cpp, endcpp, ep, grp, has_hostp, exflagsp, cr) const char *line; size_t lineno; char **cpp, **endcpp; struct exportlist *ep; struct grouplist *grp; int *has_hostp; int *exflagsp; struct uucred *cr; { char *cpoptarg, *cpoptend; char *cp, *cpopt, savedc, savedc2; char *endcp = NULL; /* XXX: GCC */ int allflag, usedarg; cpopt = *cpp; cpopt++; cp = *endcpp; savedc = *cp; *cp = '\0'; while (cpopt && *cpopt) { allflag = 1; usedarg = -2; savedc2 = '\0'; if ((cpoptend = strchr(cpopt, ',')) != NULL) { *cpoptend++ = '\0'; if ((cpoptarg = strchr(cpopt, '=')) != NULL) *cpoptarg++ = '\0'; } else { if ((cpoptarg = strchr(cpopt, '=')) != NULL) *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 (!strcmp(cpopt, "kerb") || !strcmp(cpopt, "k")) { *exflagsp |= MNT_EXKERB; opt_flags |= OP_KERB; } else if (cpoptarg && (!strcmp(cpopt, "mask") || !strcmp(cpopt, "m"))) { if (get_net(cpoptarg, &grp->gr_ptr.gt_net, 1)) { syslog(LOG_ERR, "\"%s\", line %ld: Bad mask: %s", line, (unsigned long)lineno, 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, "\"%s\", line %ld: Network/host conflict", line, (unsigned long)lineno); return (1); } else if (get_net(cpoptarg, &grp->gr_ptr.gt_net, 0)) { syslog(LOG_ERR, "\"%s\", line %ld: Bad net: %s", line, (unsigned long)lineno, 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, "noresvmnt")) { opt_flags |= OP_NORESMNT; } else if (!strcmp(cpopt, "noresvport")) { opt_flags |= OP_NORESPORT; *exflagsp |= MNT_EXNORESPORT; } else if (!strcmp(cpopt, "public")) { *exflagsp |= (MNT_EXNORESPORT | MNT_EXPUBLIC); opt_flags |= OP_NORESPORT; } else if (!strcmp(cpopt, "webnfs")) { *exflagsp |= (MNT_EXNORESPORT | MNT_EXPUBLIC | MNT_EXRDONLY | MNT_EXPORTANON); opt_flags |= (OP_MAPALL | OP_NORESPORT); } else if (cpoptarg && !strcmp(cpopt, "index")) { ep->ex_indexfile = strdup(cpoptarg); } else { syslog(LOG_ERR, "\"%s\", line %ld: Bad opt %s", line, (unsigned long)lineno, 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(line, lineno, cp, grp) const char *line; size_t lineno; const char *cp; struct grouplist *grp; { struct addrinfo *ai, hints; int ecode; char host[NI_MAXHOST]; if (grp->gr_type != GT_NULL) { syslog(LOG_ERR, "\"%s\", line %ld: Bad netgroup type for ip host %s", line, (unsigned long)lineno, 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, "\"%s\", line %ld: can't get address info for " "host %s", line, (long)lineno, cp); return 1; } grp->gr_type = GT_HOST; 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, ninumeric) != 0) strlcpy(host, "?", sizeof(host)); ai->ai_canonname = estrdup(host); ai->ai_flags |= AI_CANONNAME; } else ai->ai_flags &= ~AI_CANONNAME; if (debug) (void)fprintf(stderr, "got host %s\n", ai->ai_canonname); ai = ai->ai_next; } return (0); } /* * Free up an exports list component */ static void free_exp(ep) struct exportlist *ep; { if (ep->ex_defdir) { free_host(ep->ex_defdir->dp_hosts); free(ep->ex_defdir); } if (ep->ex_fsdir) free(ep->ex_fsdir); if (ep->ex_indexfile) free(ep->ex_indexfile); free_dir(ep->ex_dirl); free(ep); } /* * Free hosts. */ static void free_host(hp) struct hostlist *hp; { struct hostlist *hp2; while (hp) { hp2 = hp; hp = hp->ht_next; free(hp2); } } static struct hostlist * get_ht() { struct hostlist *hp; hp = emalloc(sizeof(struct hostlist)); hp->ht_next = NULL; hp->ht_flag = 0; return (hp); } /* * Do the nfssvc syscall to push the export info into the kernel. */ static int do_nfssvc(line, lineno, ep, grp, exflags, anoncrp, dirp, dirplen, fsb) const char *line; size_t lineno; struct exportlist *ep; struct grouplist *grp; int exflags; struct uucred *anoncrp; char *dirp; int dirplen; struct statvfs *fsb; { struct sockaddr *addrp; struct sockaddr_storage ss; struct addrinfo *ai; int addrlen; int done; struct export_args export; export.ex_flags = exflags; export.ex_anon = *anoncrp; export.ex_indexfile = ep->ex_indexfile; if (grp->gr_type == GT_HOST) { ai = grp->gr_ptr.gt_addrinfo; addrp = ai->ai_addr; addrlen = ai->ai_addrlen; } else { addrp = NULL; ai = NULL; /* XXXGCC -Wuninitialized */ addrlen = 0; /* XXXGCC -Wuninitialized */ } done = FALSE; while (!done) { struct mountd_exports_list mel; switch (grp->gr_type) { case GT_HOST: if (addrp != NULL && addrp->sa_family == AF_INET6 && have_v6 == 0) goto skip; export.ex_addr = addrp; export.ex_addrlen = addrlen; export.ex_masklen = 0; break; case GT_NET: export.ex_addr = (struct sockaddr *) &grp->gr_ptr.gt_net.nt_net; if (export.ex_addr->sa_family == AF_INET6 && have_v6 == 0) goto skip; export.ex_addrlen = export.ex_addr->sa_len; memset(&ss, 0, sizeof ss); ss.ss_family = export.ex_addr->sa_family; ss.ss_len = export.ex_addr->sa_len; if (allones(&ss, grp->gr_ptr.gt_net.nt_len) != 0) { syslog(LOG_ERR, "\"%s\", line %ld: Bad network flag", line, (unsigned long)lineno); return (1); } export.ex_mask = (struct sockaddr *)&ss; export.ex_masklen = ss.ss_len; break; default: syslog(LOG_ERR, "\"%s\", line %ld: Bad netgroup type", line, (unsigned long)lineno); return (1); }; /* * XXX: * Maybe I should just use the fsb->f_mntonname path? */ mel.mel_path = dirp; mel.mel_nexports = 1; mel.mel_exports = &export; if (rump_sys_nfssvc(NFSSVC_SETEXPORTSLIST, &mel) != 0) { syslog(LOG_ERR, "\"%s\", line %ld: Can't change attributes for %s to %s: %m %d", line, (unsigned long)lineno, dirp, (grp->gr_type == GT_HOST) ? grp->gr_ptr.gt_addrinfo->ai_canonname : (grp->gr_type == GT_NET) ? grp->gr_ptr.gt_net.nt_name : "Unknown", errno); return (1); } skip: if (addrp) { ai = ai->ai_next; if (ai == NULL) done = TRUE; else { addrp = ai->ai_addr; addrlen = ai->ai_addrlen; } } else done = TRUE; } return (0); } /* * Translate a net address. */ static int get_net(cp, net, maskflg) char *cp; struct netmsk *net; int maskflg; { struct netent *np; char *thename, *p, *prefp; struct sockaddr_in sin, *sinp; struct sockaddr *sa; struct addrinfo hints, *ai = NULL; char netname[NI_MAXHOST]; long preflen; int ecode; (void)memset(&sin, 0, sizeof(sin)); if ((opt_flags & OP_MASKLEN) && !maskflg) { p = strchr(cp, '/'); *p = '\0'; prefp = p + 1; } else { p = NULL; /* XXXGCC -Wuninitialized */ prefp = NULL; /* XXXGCC -Wuninitialized */ } if ((np = getnetbyname(cp)) != NULL) { sin.sin_family = AF_INET; sin.sin_len = sizeof sin; sin.sin_addr = inet_makeaddr(np->n_net, 0); sa = (struct sockaddr *)&sin; } else if (isdigit((unsigned char)*cp)) { memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(cp, NULL, &hints, &ai) != 0) { /* * If getaddrinfo() failed, try the inet4 network * notation with less than 3 dots. */ 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 %x\n", sin.sin_addr.s_addr); sa = (struct sockaddr *)&sin; } else sa = ai->ai_addr; } else if (isxdigit((unsigned char)*cp) || *cp == ':') { memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(cp, NULL, &hints, &ai) == 0) sa = ai->ai_addr; else goto fail; } else goto fail; /* * Only allow /pref notation for v6 addresses. */ if (sa->sa_family == AF_INET6 && (!(opt_flags & OP_MASKLEN) || maskflg)) return 1; ecode = getnameinfo(sa, sa->sa_len, netname, sizeof netname, NULL, 0, ninumeric); if (ecode != 0) goto fail; if (maskflg) net->nt_len = countones(sa); else { if (opt_flags & OP_MASKLEN) { errno = 0; preflen = strtol(prefp, NULL, 10); if (preflen == LONG_MIN && errno == ERANGE) goto fail; net->nt_len = (int)preflen; *p = '/'; } if (np) thename = np->n_name; else { if (getnameinfo(sa, sa->sa_len, netname, sizeof netname, NULL, 0, ninumeric) != 0) strlcpy(netname, "?", sizeof(netname)); thename = netname; } net->nt_name = estrdup(thename); memcpy(&net->nt_net, sa, sa->sa_len); } if (!maskflg && sa->sa_family == AF_INET && !(opt_flags & (OP_MASK|OP_MASKLEN))) { sinp = (struct sockaddr_in *)sa; if (IN_CLASSA(sinp->sin_addr.s_addr)) net->nt_len = 8; else if (IN_CLASSB(sinp->sin_addr.s_addr)) net->nt_len = 16; else if (IN_CLASSC(sinp->sin_addr.s_addr)) net->nt_len = 24; else if (IN_CLASSD(sinp->sin_addr.s_addr)) net->nt_len = 28; else net->nt_len = 32; /* XXX */ } if (ai) freeaddrinfo(ai); return 0; fail: if (ai) freeaddrinfo(ai); return 1; } /* * Parse out the next white space separated field */ static void nextfield(cp, endcp) char **cp; char **endcp; { char *p; p = *cp; while (*p == ' ' || *p == '\t') p++; if (*p == '\n' || *p == '\0') *cp = *endcp = p; else { *cp = p++; while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0') p++; *endcp = p; } } /* * Parse a description of a credential. */ static void parsecred(namelist, cr) char *namelist; struct uucred *cr; { char *thename; int cnt; char *names; struct passwd *pw; struct group *gr; int ngroups; gid_t grps[NGROUPS + 1]; /* * Set up the unprivileged user. */ *cr = def_anon; /* * Get the user's password table entry. */ names = strsep(&namelist, " \t\n"); thename = strsep(&names, ":"); if (isdigit((unsigned char)*thename) || *thename == '-') pw = getpwuid(atoi(thename)); else pw = getpwnam(thename); /* * Credentials specified as those of a user. */ if (names == NULL) { if (pw == NULL) { syslog(LOG_ERR, "Unknown user: %s", thename); return; } cr->cr_uid = pw->pw_uid; ngroups = NGROUPS + 1; if (getgrouplist(pw->pw_name, pw->pw_gid, grps, &ngroups)) syslog(LOG_ERR, "Too many groups for user %s", thename); /* * Convert from int's to gid_t's and compress out duplicate */ cr->cr_ngroups = ngroups - 1; cr->cr_gid = grps[0]; for (cnt = 1; cnt < ngroups; cnt++) cr->cr_groups[cnt - 1] = grps[cnt]; return; } /* * Explicit credential specified as a colon separated list: * uid:gid:gid:... */ if (pw != NULL) cr->cr_uid = pw->pw_uid; else if (isdigit((unsigned char)*thename) || *thename == '-') cr->cr_uid = atoi(thename); else { syslog(LOG_ERR, "Unknown user: %s", thename); return; } cr->cr_ngroups = 0; while (names != NULL && *names != '\0' && cr->cr_ngroups < NGROUPS) { thename = strsep(&names, ":"); if (isdigit((unsigned char)*thename) || *thename == '-') { cr->cr_groups[cr->cr_ngroups++] = atoi(thename); } else { if ((gr = getgrnam(thename)) == NULL) { syslog(LOG_ERR, "Unknown group: %s", thename); continue; } cr->cr_groups[cr->cr_ngroups++] = gr->gr_gid; } } if (names != NULL && *names != '\0' && cr->cr_ngroups == NGROUPS) syslog(LOG_ERR, "Too many groups"); } #define STRSIZ (RPCMNT_NAMELEN+RPCMNT_PATHLEN+50) /* * Routines that maintain the remote mounttab */ static void get_mountlist() { struct mountlist *mlp, **mlpp; char *host, *dirp, *cp; char str[STRSIZ]; FILE *mlfile; if ((mlfile = rumpfopen(_PATH_RMOUNTLIST, "r")) == NULL) { syslog(LOG_ERR, "Can't open %s: %m", _PATH_RMOUNTLIST); return; } mlpp = &mlhead; 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 = emalloc(sizeof(*mlp)); (void)strncpy(mlp->ml_host, host, RPCMNT_NAMELEN); mlp->ml_host[RPCMNT_NAMELEN] = '\0'; (void)strncpy(mlp->ml_dirp, dirp, RPCMNT_PATHLEN); mlp->ml_dirp[RPCMNT_PATHLEN] = '\0'; mlp->ml_next = NULL; *mlpp = mlp; mlpp = &mlp->ml_next; } (void)fclose(mlfile); } static int del_mlist(hostp, dirp, saddr) char *hostp, *dirp; struct sockaddr *saddr; { struct mountlist *mlp, **mlpp; struct mountlist *mlp2; u_short sport; FILE *mlfile; int fnd = 0, ret = 0; char host[NI_MAXHOST]; 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: return -1; } mlpp = &mlhead; mlp = mlhead; while (mlp) { if (!strcmp(mlp->ml_host, hostp) && (!dirp || !strcmp(mlp->ml_dirp, dirp))) { if (!(mlp->ml_flag & DP_NORESMNT) && sport >= IPPORT_RESERVED) { if (getnameinfo(saddr, saddr->sa_len, host, sizeof host, NULL, 0, ninumeric) != 0) strlcpy(host, "?", sizeof(host)); syslog(LOG_NOTICE, "Umount request for %s:%s from %s refused\n", mlp->ml_host, mlp->ml_dirp, host); ret = -1; goto cont; } fnd = 1; mlp2 = mlp; *mlpp = mlp = mlp->ml_next; free(mlp2); } else { cont: mlpp = &mlp->ml_next; mlp = mlp->ml_next; } } if (fnd) { if ((mlfile = rumpfopen(_PATH_RMOUNTLIST, "w")) == NULL) { syslog(LOG_ERR, "Can't update %s: %m", _PATH_RMOUNTLIST); return ret; } mlp = mlhead; while (mlp) { (void)fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dirp); mlp = mlp->ml_next; } (void)fclose(mlfile); } return ret; } static void add_mlist(hostp, dirp, flags) char *hostp, *dirp; int flags; { struct mountlist *mlp, **mlpp; FILE *mlfile; mlpp = &mlhead; mlp = mlhead; while (mlp) { if (!strcmp(mlp->ml_host, hostp) && !strcmp(mlp->ml_dirp, dirp)) return; mlpp = &mlp->ml_next; mlp = mlp->ml_next; } mlp = emalloc(sizeof(*mlp)); strncpy(mlp->ml_host, hostp, RPCMNT_NAMELEN); mlp->ml_host[RPCMNT_NAMELEN] = '\0'; strncpy(mlp->ml_dirp, dirp, RPCMNT_PATHLEN); mlp->ml_dirp[RPCMNT_PATHLEN] = '\0'; mlp->ml_flag = flags; mlp->ml_next = NULL; *mlpp = mlp; if ((mlfile = rumpfopen(_PATH_RMOUNTLIST, "a")) == NULL) { syslog(LOG_ERR, "Can't update %s: %m", _PATH_RMOUNTLIST); return; } (void)fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dirp); (void)fclose(mlfile); } /* * This function is called via. SIGTERM when the system is going down. * It sends a broadcast RPCMNT_UMNTALL. */ /* ARGSUSED */ static void send_umntall(n) int n; { #if 0 (void)clnt_broadcast(RPCPROG_MNT, RPCMNT_VER1, RPCMNT_UMNTALL, xdr_void, NULL, xdr_void, NULL, (resultproc_t)umntall_each); #endif exit(0); } #if 0 static int umntall_each(resultsp, raddr) caddr_t resultsp; struct sockaddr_in *raddr; { return (1); } #endif /* * Free up a group list. */ static void free_grp(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); } free(grp); } #if 0 static void SYSLOG(int pri, const char *fmt,...) { va_list ap; va_start(ap, fmt); if (debug) vfprintf(stderr, fmt, ap); else vsyslog(pri, fmt, ap); va_end(ap); } #endif /* * Check options for consistency. */ static int check_options(line, lineno, dp) const char *line; size_t lineno; struct dirlist *dp; { if (dp == NULL) { syslog(LOG_ERR, "\"%s\", line %ld: missing directory list", line, (unsigned long)lineno); return (1); } if ((opt_flags & (OP_MAPROOT|OP_MAPALL)) == (OP_MAPROOT|OP_MAPALL) || (opt_flags & (OP_MAPROOT|OP_KERB)) == (OP_MAPROOT|OP_KERB) || (opt_flags & (OP_MAPALL|OP_KERB)) == (OP_MAPALL|OP_KERB)) { syslog(LOG_ERR, "\"%s\", line %ld: -mapall, -maproot and -kerb mutually exclusive", line, (unsigned long)lineno); return (1); } if ((opt_flags & OP_MASK) && (opt_flags & OP_NET) == 0) { syslog(LOG_ERR, "\"%s\", line %ld: -mask requires -net", line, (unsigned long)lineno); return (1); } if ((opt_flags & OP_MASK) && (opt_flags & OP_MASKLEN) != 0) { syslog(LOG_ERR, "\"%s\", line %ld: /pref and -mask mutually" " exclusive", line, (unsigned long)lineno); return (1); } if ((opt_flags & OP_ALLDIRS) && dp->dp_left) { syslog(LOG_ERR, "\"%s\", line %ld: -alldirs has multiple directories", line, (unsigned long)lineno); return (1); } return (0); } /* * Check an absolute directory path for any symbolic links. Return true * if no symbolic links are found. */ static int check_dirpath(line, lineno, dirp) const char *line; size_t lineno; char *dirp; { char *cp; struct stat sb; char *file = ""; for (cp = dirp + 1; *cp; cp++) { if (*cp == '/') { *cp = '\0'; if (rump_sys_lstat(dirp, &sb) == -1) goto bad; if (!S_ISDIR(sb.st_mode)) goto bad1; *cp = '/'; } } cp = NULL; if (rump_sys_lstat(dirp, &sb) == -1) goto bad; if (!S_ISDIR(sb.st_mode) && !S_ISREG(sb.st_mode)) { file = " file or a"; goto bad1; } return 1; bad: syslog(LOG_ERR, "\"%s\", line %ld: lstat for `%s' failed: %m", line, (unsigned long)lineno, dirp); if (cp) *cp = '/'; return 0; bad1: syslog(LOG_ERR, "\"%s\", line %ld: `%s' is not a%s directory", line, (unsigned long)lineno, dirp, file); abort(); if (cp) *cp = '/'; return 0; } static void bind_resv_port(int sock, sa_family_t family, in_port_t port) { struct sockaddr *sa; struct sockaddr_in sasin; struct sockaddr_in6 sasin6; switch (family) { case AF_INET: (void)memset(&sasin, 0, sizeof(sasin)); sasin.sin_len = sizeof(sasin); sasin.sin_family = family; sasin.sin_port = htons(port); sa = (struct sockaddr *)(void *)&sasin; break; case AF_INET6: (void)memset(&sasin6, 0, sizeof(sasin6)); sasin6.sin6_len = sizeof(sasin6); sasin6.sin6_family = family; sasin6.sin6_port = htons(port); sa = (struct sockaddr *)(void *)&sasin6; break; default: syslog(LOG_ERR, "Unsupported address family %d", family); return; } if (bindresvport_sa(sock, sa) == -1) syslog(LOG_ERR, "Cannot bind to reserved port %d (%m)", port); } /* ARGSUSED */ static void no_nfs(int sig) { syslog(LOG_ERR, "kernel NFS support not present; exiting"); exit(1); }