Current Path : /usr/src/usr.sbin/mountd/ |
FreeBSD hs32.drive.ne.jp 9.1-RELEASE FreeBSD 9.1-RELEASE #1: Wed Jan 14 12:18:08 JST 2015 root@hs32.drive.ne.jp:/sys/amd64/compile/hs32 amd64 |
Current File : //usr/src/usr.sbin/mountd/mountd.c |
/* * 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. * 4. 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: release/9.1.0/usr.sbin/mountd/mountd.c 224003 2011-07-14 07:35:28Z delphij $"); #include <sys/param.h> #include <sys/fcntl.h> #include <sys/linker.h> #include <sys/module.h> #include <sys/mount.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 { struct mountlist *ml_next; char ml_host[MNTNAMLEN+1]; char ml_dirp[MNTPATHLEN+1]; }; 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 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; int ex_numsecflavors; int ex_secflavors[MAXSECFLAVORS]; }; /* ex_flag bits */ #define EX_LINKED 0x1 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; }; /* Group types */ #define GT_NULL 0x0 #define GT_HOST 0x1 #define GT_NET 0x2 #define GT_DEFAULT 0x3 #define GT_IGNORE 0x5 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 # */ /* Global defs */ char *add_expdir(struct dirlist **, char *, int); void add_dlist(struct dirlist **, struct dirlist *, struct grouplist *, int); void add_mlist(char *, char *); int check_dirpath(char *); int check_options(struct dirlist *); int checkmask(struct sockaddr *sa); int chk_host(struct dirlist *, struct sockaddr *, int *, int *); static int create_service(struct netconfig *nconf); static void complete_service(struct netconfig *nconf, char *port_str); static void clearout_service(void); void del_mlist(char *hostp, char *dirp); struct dirlist *dirp_search(struct dirlist *, char *); int do_mount(struct exportlist *, struct grouplist *, int, struct xucred *, char *, int, struct statfs *); int do_opt(char **, char **, struct exportlist *, struct grouplist *, int *, int *, struct xucred *); struct exportlist *ex_search(fsid_t *); struct exportlist *get_exp(void); void free_dir(struct dirlist *); void free_exp(struct exportlist *); void free_grp(struct grouplist *); void free_host(struct hostlist *); void get_exportlist(void); int get_host(char *, struct grouplist *, struct grouplist *); struct hostlist *get_ht(void); int get_line(void); void get_mountlist(void); int get_net(char *, struct netmsk *, int); void getexp_err(struct exportlist *, struct grouplist *); struct grouplist *get_grp(void); void hang_dirp(struct dirlist *, struct grouplist *, struct exportlist *, int); void huphandler(int sig); int makemask(struct sockaddr_storage *ssp, int bitlen); void mntsrv(struct svc_req *, SVCXPRT *); void nextfield(char **, char **); void out_of_mem(void); void parsecred(char *, struct xucred *); int parsesec(char *, struct exportlist *); int put_exlist(struct dirlist *, XDR *, struct dirlist *, int *, int); void *sa_rawaddr(struct sockaddr *sa, int *nbytes); int sacmp(struct sockaddr *sa1, struct sockaddr *sa2, struct sockaddr *samask); int scan_tree(struct dirlist *, struct sockaddr *); static void usage(void); int xdr_dir(XDR *, char *); int xdr_explist(XDR *, caddr_t); int xdr_explist_brief(XDR *, caddr_t); int xdr_explist_common(XDR *, caddr_t, int); int xdr_fhs(XDR *, caddr_t); int xdr_mlist(XDR *, caddr_t); void terminate(int); struct exportlist *exphead; struct mountlist *mlhead; struct grouplist *grphead; char *exnames_default[2] = { _PATH_EXPORTS, NULL }; char **exnames; char **hosts = NULL; struct xucred def_anon = { XUCRED_VERSION, (uid_t)-2, 1, { (gid_t)-2 }, NULL }; int force_v2 = 0; int resvport_only = 1; int nhosts = 0; int dir_only = 1; int dolog = 0; int got_sighup = 0; int xcreated = 0; char *svcport_str = NULL; static int mallocd_svcport = 0; static int *sock_fd; static int sock_fdcnt; static int sock_fdpos; int opt_flags; static int have_v6 = 1; int v4root_phase = 0; char v4root_dirpath[PATH_MAX + 1]; int run_v4server = 1; int has_publicfh = 0; 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 int debug = 1; void SYSLOG(int, const char *, ...) __printflike(2, 3); #define syslog SYSLOG #else int debug = 0; #endif /* * 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; /* 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:lnop:r")) != -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': dir_only = 0; break; case 'd': debug = debug ? 0 : 1; break; case 'l': dolog = 1; break; case 'o': run_v4server = 0; 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; default: usage(); }; /* * Unless the "-o" option was specified, try and run "nfsd". * If "-o" was specified, try and run "nfsserver". */ if (run_v4server > 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"); } } else if (modfind("nfsserver") < 0) { /* Not present in kernel, try loading it */ if (kldload("nfsserver") < 0 || modfind("nfsserver") < 0) errx(1, "NFS server is not available"); } argc -= optind; argv += optind; grphead = (struct grouplist *)NULL; exphead = (struct exportlist *)NULL; mlhead = (struct mountlist *)NULL; if (argc > 0) exnames = argv; else exnames = exnames_default; openlog("mountd", LOG_PID, LOG_DAEMON); if (debug) warnx("getting export list"); get_exportlist(); 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); rpcb_unset(MOUNTPROG, MOUNTVERS, NULL); rpcb_unset(MOUNTPROG, MOUNTVERS3, NULL); rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec); if (!resvport_only) { if (sysctlbyname("vfs.nfsrv.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; 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 and * 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(). */ for (;;) { if (got_sighup) { get_exportlist(); got_sighup = 0; } readfds = svc_fdset; switch (select(svc_maxfd + 1, &readfds, NULL, NULL, NULL)) { case -1: if (errno == EINTR) continue; syslog(LOG_ERR, "mountd died: select: %m"); exit(1); case 0: continue; default: svc_getreqset(&readfds); } } } /* * 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_flags = AI_PASSIVE; 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; /* * XXX - using RPC library internal functions. */ if ((fd = __rpc_nconf2fd(nconf)) < 0) { int non_fatal = 0; if (errno == EPROTONOSUPPORT && 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; if (nconf->nc_semantics != NC_TPI_CLTS) listen(fd, SOMAXCONN); 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] " "[-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 bad = 0, defset, hostset; sigset_t sighup_mask; 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; } 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 || (!S_ISDIR(stb.st_mode) && (dir_only || !S_ISREG(stb.st_mode))) || 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 */ } /* Check in the exports list */ sigprocmask(SIG_BLOCK, &sighup_mask, NULL); ep = ex_search(&fsb.f_fsid); hostset = defset = 0; if (ep && (chk_host(ep->ex_defdir, saddr, &defset, &hostset) || ((dp = dirp_search(ep->ex_dirl, dirpath)) && chk_host(dp, saddr, &defset, &hostset)) || (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; else fhr.fhr_flag = defset; 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; } fhr.fhr_numsecflavors = ep->ex_numsecflavors; fhr.fhr_secflavors = ep->ex_secflavors; 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 { 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 */ int xdr_dir(XDR *xdrsp, char *dirp) { return (xdr_string(xdrsp, &dirp, MNTPATHLEN)); } /* * Xdr routine to generate file handle reply */ 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); } int xdr_mlist(XDR *xdrsp, caddr_t cp __unused) { struct mountlist *mlp; int true = 1; int false = 0; char *strp; mlp = mlhead; while (mlp) { 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); mlp = mlp->ml_next; } if (!xdr_bool(xdrsp, &false)) return (0); return (1); } /* * Xdr conversion for export list */ int xdr_explist_common(XDR *xdrsp, caddr_t cp __unused, int brief) { struct exportlist *ep; int false = 0; int putdef; sigset_t sighup_mask; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); sigprocmask(SIG_BLOCK, &sighup_mask, NULL); ep = exphead; while (ep) { 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, (struct dirlist *)NULL, &putdef, brief)) goto errout; ep = ep->ex_next; } 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. */ 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); } int xdr_explist(XDR *xdrsp, caddr_t cp) { return xdr_explist_common(xdrsp, cp, 0); } int xdr_explist_brief(XDR *xdrsp, caddr_t cp) { return xdr_explist_common(xdrsp, cp, 1); } char *line; int linesize; FILE *exp_file; /* * Get the export list from one, currently open file */ static void get_exportlist_one(void) { struct exportlist *ep, *ep2; struct grouplist *grp, *tgrp; struct exportlist **epp; struct dirlist *dirhead; struct statfs fsb; struct xucred anon; char *cp, *endcp, *dirp, *hst, *usr, *dom, savedc; int len, has_host, exflags, got_nondir, dirplen, netgrp; v4root_phase = 0; 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 = def_anon; 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); goto nextline; } if (*cp == '-') { if (ep == (struct exportlist *)NULL) { getexp_err(ep, tgrp); 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); goto nextline; } } else if (*cp == '/') { savedc = *endcp; *endcp = '\0'; if (v4root_phase > 1) { if (dirp != NULL) { syslog(LOG_ERR, "Multiple V4 dirs"); getexp_err(ep, tgrp); goto nextline; } } if (check_dirpath(cp) && statfs(cp, &fsb) >= 0) { if (got_nondir) { syslog(LOG_ERR, "dirs must be first"); getexp_err(ep, tgrp); goto nextline; } if (v4root_phase == 1) { if (dirp != NULL) { syslog(LOG_ERR, "Multiple V4 dirs"); getexp_err(ep, tgrp); 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); goto nextline; } dirp = cp; v4root_phase = 2; got_nondir = 1; ep = get_exp(); } else { if (ep) { if (ep->ex_fs.val[0] != fsb.f_fsid.val[0] || ep->ex_fs.val[1] != fsb.f_fsid.val[1]) { getexp_err(ep, tgrp); goto nextline; } } else { /* * See if this directory is already * in the list. */ ep = ex_search(&fsb.f_fsid); if (ep == (struct exportlist *)NULL) { ep = get_exp(); ep->ex_fs = fsb.f_fsid; ep->ex_fsdir = (char *)malloc (strlen(fsb.f_mntonname) + 1); if (ep->ex_fsdir) strcpy(ep->ex_fsdir, fsb.f_mntonname); else 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); goto nextline; } *endcp = savedc; } else { savedc = *endcp; *endcp = '\0'; got_nondir = 1; if (ep == (struct exportlist *)NULL) { getexp_err(ep, tgrp); 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); 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) { syslog(LOG_ERR, "network/host conflict"); getexp_err(ep, tgrp); 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); goto nextline; } } if (v4root_phase == 1) { syslog(LOG_ERR, "V4:root, no dirp, ignored"); getexp_err(ep, tgrp); 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. */ grp = tgrp; do { if (do_mount(ep, grp, exflags, &anon, dirp, dirplen, &fsb)) { getexp_err(ep, tgrp); 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) { /* * Since these structures aren't used by mountd, * free them up now. */ 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); grp->gr_next = grphead; grphead = tgrp; } else { hang_dirp(dirhead, (struct grouplist *)NULL, ep, opt_flags); free_grp(grp); } dirhead = (struct dirlist *)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; } nextline: v4root_phase = 0; if (dirhead) { free_dir(dirhead); dirhead = (struct dirlist *)NULL; } } } /* * Get the export list from all specified files */ void get_exportlist(void) { struct exportlist *ep, *ep2; struct grouplist *grp, *tgrp; struct export_args export; struct iovec *iov; struct statfs *fsp, *mntbufp; struct xvfsconf vfc; char *dirp; char errmsg[255]; int dirplen, num, i; int iovlen; int done; struct nfsex_args eargs; v4root_dirpath[0] = '\0'; bzero(&export, sizeof(export)); export.ex_flags = MNT_DELEXPORT; dirp = NULL; dirplen = 0; iov = NULL; iovlen = 0; bzero(errmsg, sizeof(errmsg)); /* * First, get rid of the old list */ ep = exphead; while (ep) { ep2 = ep; ep = ep->ex_next; free_exp(ep2); } exphead = (struct exportlist *)NULL; grp = grphead; while (grp) { tgrp = grp; grp = grp->gr_next; free_grp(tgrp); } grphead = (struct grouplist *)NULL; /* * and the old V4 root dir. */ bzero(&eargs, sizeof (eargs)); eargs.export.ex_flags = MNT_DELEXPORT; if (run_v4server > 0 && nfssvc(NFSSVC_V4ROOTEXPORT, (caddr_t)&eargs) < 0 && errno != ENOENT) syslog(LOG_ERR, "Can't delete exports for V4:"); /* * and clear flag that notes if a public fh has been exported. */ has_publicfh = 0; /* * 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); if (num > 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", &export, sizeof(export)); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); } for (i = 0; i < num; i++) { fsp = &mntbufp[i]; if (getvfsbyname(fsp->f_fstypename, &vfc) != 0) { syslog(LOG_ERR, "getvfsbyname() failed for %s", fsp->f_fstypename); continue; } /* * 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) continue; 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; if (nmount(iov, iovlen, fsp->f_flags) < 0 && errno != ENOENT && errno != ENOTSUP) { syslog(LOG_ERR, "can't delete exports for %s: %m %s", fsp->f_mntonname, errmsg); } } 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; } /* * 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(); fclose(exp_file); done++; } if (done == 0) { syslog(LOG_ERR, "can't open any exports file"); exit(2); } /* * If there was no public fh, clear any previous one set. */ if (run_v4server > 0 && has_publicfh == 0) (void) nfssvc(NFSSVC_NOPUBLICFH, NULL); } /* * Allocate an export list element */ 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 */ 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(). */ void getexp_err(struct exportlist *ep, struct grouplist *grp) { struct grouplist *tgrp; if (!(opt_flags & OP_QUIET)) 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. */ struct exportlist * ex_search(fsid_t *fsid) { struct exportlist *ep; ep = exphead; while (ep) { if (ep->ex_fs.val[0] == fsid->val[0] && ep->ex_fs.val[1] == fsid->val[1]) return (ep); ep = ep->ex_next; } return (ep); } /* * Add a directory path to the list. */ char * add_expdir(struct dirlist **dpp, char *cp, int len) { struct dirlist *dp; dp = (struct dirlist *)malloc(sizeof (struct dirlist) + len); 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; 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(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((caddr_t)dp); else ep->ex_defdir = dp; if (grp == (struct grouplist *)NULL) { ep->ex_defdir->dp_flag |= DP_DEFSET; } else while (grp) { hp = get_ht(); 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. */ void add_dlist(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((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; grp = grp->gr_next; } while (grp); } else { dp->dp_flag |= DP_DEFSET; } } /* * Search for a dirpath on the export point. */ 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. */ int chk_host(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, NULL)) { *hostsetp = (hp->ht_flag | DP_HOSTSET); 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); return (1); } break; } hp = hp->ht_next; } } return (0); } /* * Scan tree for a host that matches the address. */ 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)) return (1); if (scan_tree(dp->dp_right, saddr)) return (1); } return (0); } /* * Traverse the dirlist tree and free it up. */ void free_dir(struct dirlist *dp) { if (dp) { free_dir(dp->dp_left); free_dir(dp->dp_right); free_host(dp->dp_hosts); free((caddr_t)dp); } } /* * Parse a colon separated list of security flavors */ 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> */ int do_opt(char **cpp, char **endcpp, struct exportlist *ep, struct grouplist *grp, int *has_hostp, int *exflagsp, struct xucred *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 (!strcmp(cpopt, "sec")) { if (parsesec(cpoptarg, ep)) return (1); opt_flags |= OP_SEC; usedarg++; } 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. */ 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 */ void free_exp(struct exportlist *ep) { 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); free((caddr_t)ep); } /* * Free hosts. */ void free_host(struct hostlist *hp) { struct hostlist *hp2; while (hp) { hp2 = hp; hp = hp->ht_next; free((caddr_t)hp2); } } 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 */ void out_of_mem(void) { syslog(LOG_ERR, "out of memory"); exit(2); } /* * Do the nmount() syscall with the update flag to push the export info into * the kernel. */ int do_mount(struct exportlist *ep, struct grouplist *grp, int exflags, struct xucred *anoncrp, char *dirp, int dirplen, struct statfs *fsb) { struct statfs fsb1; struct addrinfo *ai; struct export_args ea, *eap; char errmsg[255]; char *cp; int done; char savedc; struct iovec *iov; int i, iovlen; int ret; struct nfsex_args nfsea; if (run_v4server > 0) eap = &nfsea.export; else eap = &ea; 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_anon = *anoncrp; eap->ex_indexfile = ep->ex_indexfile; if (grp->gr_type == GT_HOST) ai = grp->gr_ptr.gt_addrinfo; else ai = NULL; eap->ex_numsecflavors = ep->ex_numsecflavors; for (i = 0; i < eap->ex_numsecflavors; i++) eap->ex_secflavors[i] = ep->ex_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 (run_v4server > 0 && nfssvc(NFSSVC_V4ROOTEXPORT, (caddr_t)&nfsea) < 0) { syslog(LOG_ERR, "Exporting V4: failed"); return (2); } } 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; 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", dirp); syslog(LOG_ERR, "can't change attributes for %s", dirp); 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 == '/' && cp > dirp) cp--; while (*(cp - 1) != '/' && cp > dirp) 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 || bcmp(&fsb1.f_fsid, &fsb->f_fsid, sizeof (fsb1.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 (run_v4server > 0 && (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; } skip: if (ai != NULL) ai = ai->ai_next; if (ai == NULL) done = TRUE; } if (cp) *cp = savedc; error_exit: /* 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. */ 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 */ void nextfield(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; } } /* * Get an exports file line. Skip over blank lines and handle line * continuations. */ 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. */ void parsecred(char *namelist, struct xucred *cr) { char *name; int cnt; char *names; struct passwd *pw; struct group *gr; gid_t groups[XU_NGROUPS + 1]; int ngroups; cr->cr_version = XUCRED_VERSION; /* * Set up the unprivileged user. */ cr->cr_uid = -2; cr->cr_groups[0] = -2; cr->cr_ngroups = 1; /* * Get the user's password table entry. */ names = strsep(&namelist, " \t\n"); name = strsep(&names, ":"); 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 = XU_NGROUPS + 1; if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups)) syslog(LOG_ERR, "too many groups"); /* * Compress out duplicate. */ cr->cr_ngroups = ngroups - 1; cr->cr_groups[0] = groups[0]; for (cnt = 2; cnt < ngroups; cnt++) cr->cr_groups[cnt - 1] = groups[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(*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 < XU_NGROUPS) { name = strsep(&names, ":"); if (isdigit(*name) || *name == '-') { cr->cr_groups[cr->cr_ngroups++] = atoi(name); } else { if ((gr = getgrnam(name)) == NULL) { syslog(LOG_ERR, "unknown group: %s", name); continue; } cr->cr_groups[cr->cr_ngroups++] = gr->gr_gid; } } if (names != NULL && *names != '\0' && cr->cr_ngroups == XU_NGROUPS) syslog(LOG_ERR, "too many groups"); } #define STRSIZ (MNTNAMLEN+MNTPATHLEN+50) /* * Routines that maintain the remote mounttab */ void get_mountlist(void) { struct mountlist *mlp, **mlpp; 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; } } 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 = (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'; mlp->ml_next = (struct mountlist *)NULL; *mlpp = mlp; mlpp = &mlp->ml_next; } fclose(mlfile); } void del_mlist(char *hostp, char *dirp) { struct mountlist *mlp, **mlpp; struct mountlist *mlp2; FILE *mlfile; int fnd = 0; mlpp = &mlhead; mlp = mlhead; while (mlp) { if (!strcmp(mlp->ml_host, hostp) && (!dirp || !strcmp(mlp->ml_dirp, dirp))) { fnd = 1; mlp2 = mlp; *mlpp = mlp = mlp->ml_next; free((caddr_t)mlp2); } else { mlpp = &mlp->ml_next; mlp = mlp->ml_next; } } if (fnd) { if ((mlfile = fopen(_PATH_RMOUNTLIST, "w")) == NULL) { syslog(LOG_ERR,"can't update %s", _PATH_RMOUNTLIST); return; } mlp = mlhead; while (mlp) { fprintf(mlfile, "%s %s\n", mlp->ml_host, mlp->ml_dirp); mlp = mlp->ml_next; } fclose(mlfile); } } void add_mlist(char *hostp, char *dirp) { 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 = (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'; mlp->ml_next = (struct mountlist *)NULL; *mlpp = mlp; 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. */ 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); } free((caddr_t)grp); } #ifdef DEBUG 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. */ 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 */ 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. */ 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 = (bitlen > CHAR_BIT) ? 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). */ 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 comparision. */ 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. */ 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); } void huphandler(int sig __unused) { got_sighup = 1; } void terminate(int sig __unused) { pidfile_remove(pfh); rpcb_unset(MOUNTPROG, MOUNTVERS, NULL); rpcb_unset(MOUNTPROG, MOUNTVERS3, NULL); exit (0); }