/* * Copyright (c) 1999-2011 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int bindresvport_sa(int sd, struct sockaddr *sa); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __LP64__ typedef int xdr_long_t; #else typedef long xdr_long_t; #endif #include "pathnames.h" #include "common.h" /* * Structure for maintaining list of export IDs for exported volumes */ struct expidlist { LIST_ENTRY(expidlist) xid_list; char xid_path[MAXPATHLEN]; /* exported sub-directory */ u_int32_t xid_id; /* export ID */ }; /* * Structure for maintaining list of UUIDs for exported volumes */ struct uuidlist { TAILQ_ENTRY(uuidlist) ul_list; char ul_mntfromname[MAXPATHLEN]; char ul_mntonname[MAXPATHLEN]; u_char ul_uuid[16]; /* UUID used */ u_char ul_dauuid[16]; /* DiskArb UUID */ char ul_davalid; /* DiskArb UUID valid */ char ul_exported; /* currently exported? */ u_int32_t ul_fsid; /* exported FS ID */ LIST_HEAD(expidhead,expidlist) ul_exportids; /* export ID list */ }; TAILQ_HEAD(,uuidlist) ulhead; #define UL_CHECK_MNTFROM 0x1 #define UL_CHECK_MNTON 0x2 #define UL_CHECK_ALL 0x3 #define AOK (void *) // assert alignment is OK /* * Default FSID is just a "hash" of UUID */ #define UUID2FSID(U) \ (*((u_int32_t*) AOK (U)) ^ *(((u_int32_t*) AOK (U))+1) ^ \ *(((u_int32_t*) AOK (U))+2) ^ *(((u_int32_t*) AOK (U))+3)) /* * Structure for keeping the (outstanding) mount list */ struct mountlist { struct mountlist *ml_next; char *ml_host; /* NFS client name or address */ char *ml_dir; /* mounted directory */ }; /* * Structure used to hold a list of names */ struct namelist { TAILQ_ENTRY(namelist) nl_next; char *nl_name; }; TAILQ_HEAD(namelisttqh, namelist); /* * Structure used to hold a list of directories */ struct dirlist { struct dirlist *dl_next; char *dl_dir; }; /* * Structure used to hold an export error message. */ struct errlist { LIST_ENTRY(errlist) el_next; uint32_t el_linenum; char *el_msg; }; /* * Structures for keeping the export lists */ TAILQ_HEAD(expfstqh, expfs); TAILQ_HEAD(expdirtqh, expdir); TAILQ_HEAD(hosttqh, host); /* * Structure to hold the exports for each exported file system. */ struct expfs { TAILQ_ENTRY(expfs) xf_next; struct expdirtqh xf_dirl; /* list of exported directories */ int xf_flag; /* internal flags for this struct */ u_char xf_uuid[16]; /* file system's UUID */ u_int32_t xf_fsid; /* exported FS ID */ char *xf_fsdir; /* mount point of this file system */ }; /* xf_flag bits */ #define XF_LINKED 0x1 /* * Structure to hold info about an exported directory */ struct expdir { TAILQ_ENTRY(expdir) xd_next; struct hosttqh xd_hosts; /* List of hosts this dir exported to */ struct expdirtqh xd_mountdirs; /* list of mountable sub-directories */ int xd_iflags; /* internal flags for this structure */ int xd_flags; /* default export flags */ struct xucred xd_cred; /* default export mapped credential */ struct nfs_sec xd_sec; /* default security flavors */ int xd_oflags; /* old default export flags */ struct xucred xd_ocred; /* old default export mapped credential */ struct nfs_sec xd_osec; /* old default security flavors */ struct nfs_sec xd_ssec; /* security flavors for showmount */ char *xd_dir; /* pathname of exported directory */ struct expidlist *xd_xid; /* corresponding export ID */ }; /* * Structures for holding sets of exported-to hosts/nets/addresses */ /* holds a host address list and name */ struct hostinfo { char *h_name; /* host name */ struct addrinfo *h_ailist; /* address list */ }; /* holds a network/mask and name */ struct netmsk { char *nt_name; /* network name */ sa_family_t nt_family; /* network family */ union { struct { in_addr_t net; /* IPv4 network address */ in_addr_t mask; /* IPv4 network mask */ } ipv4; struct { struct in6_addr net; /* IPv6 network address */ struct in6_addr mask; /* IPv6 network mask */ } ipv6; } nt_u; }; #define nt_net nt_u.ipv4.net #define nt_mask nt_u.ipv4.mask #define nt_net6 nt_u.ipv6.net #define nt_mask6 nt_u.ipv6.mask /* holds either a host or network */ union grouptypes { struct hostinfo gt_hostinfo; struct netmsk gt_net; char * gt_netgroup; }; /* host/network list entry */ struct grouplist { struct grouplist *gr_cache; /* linked list in cache */ struct grouplist *gr_next; /* linked list in get_exportlist() */ int gr_refcnt; /* #references on this group */ int16_t gr_type; /* type of group */ int16_t gr_flags; /* group flags */ union grouptypes gr_u; /* the host/network */ }; /* Group types */ #define GT_NULL 0x0 /* not fully-initialized yet */ #define GT_NETGROUP 0x1 /* this is a netgroup */ #define GT_NET 0x2 /* this is a network */ #define GT_HOST 0x3 /* this is a single host address */ /* Group flags */ #define GF_SHOW 0x1 /* show this entry in export list */ /* * host/network flags list entry */ struct host { TAILQ_ENTRY(host) ht_next; int ht_flags; /* export options for these hosts */ struct xucred ht_cred; /* mapped credential for these hosts */ struct grouplist *ht_grp; /* host/network flags applies to */ struct nfs_sec ht_sec; /* security flavors for these hosts */ }; struct fhreturn { int fhr_flags; int fhr_vers; struct nfs_sec fhr_sec; fhandle_t fhr_fh; }; /* Global defs */ int add_name(struct namelisttqh *, char *); void free_namelist(struct namelisttqh *); int add_dir(struct dirlist **, char *); char * add_expdir(struct expdir **, char *, int); int add_grp(struct grouplist **, struct grouplist *); int add_host(struct hosttqh *, struct host *); void add_mlist(char *, char *); int addrinfo_cmp(struct addrinfo *, struct addrinfo *); int check_dirpath(char *); int check_options(int); void clear_export_error(uint32_t); int cmp_secflavs(struct nfs_sec *, struct nfs_sec *); void merge_secflavs(struct nfs_sec *, struct nfs_sec *); void del_mlist(char *, char *); int expdir_search(struct expfs *, char *, struct sockaddr *, int *, struct nfs_sec *); int do_export(int, struct expfs *, struct expdir *, struct grouplist *, int, struct xucred *, struct nfs_sec *); int do_opt(char **, char **, struct grouplist *, int *, int *, int *, struct xucred *, struct nfs_sec *, char *, u_char *); struct expfs *ex_search(u_char *); void export_error(int, const char *, ...); void export_error_cleanup(struct expfs *); struct host *find_group_address_match_in_host_list(struct hosttqh *, struct grouplist *); struct host *find_host(struct hosttqh *, struct sockaddr *); void free_dirlist(struct dirlist *dl); void free_expdir(struct expdir *); void free_expfs(struct expfs *); void free_grp(struct grouplist *); void free_hosts(struct hosttqh *); void free_host(struct host *); struct expdir *get_expdir(void); struct expfs *get_expfs(void); int get_host_addresses(char *, struct grouplist *); struct host *get_host(void); int get_export_entry(void); void get_mountlist(void); int get_net(char *, struct netmsk *, int); int get_sec_flavors(char *flavorlist, struct nfs_sec *); struct grouplist *get_grp(struct grouplist *); const char *grp_addr(struct grouplist *); char * grp_name(struct grouplist *); int hang_options_setup(struct expdir *, int, struct xucred *, struct grouplist *, struct nfs_sec *, int *); void hang_options_finalize(struct expdir *); void hang_options_cleanup(struct expdir *); int hang_options_mountdir(struct expdir *, char *, int, struct grouplist *, struct nfs_sec *); void mntsrv(struct svc_req *, SVCXPRT *); void nextfield(char **, char **); int parsecred(char *, struct xucred *); int put_exlist(struct expdir *, XDR *); int subdir_check(char *, char *); int xdr_dir(XDR *, char *); int xdr_explist(XDR *, caddr_t); int xdr_fhs(XDR *, caddr_t); int xdr_mlist(XDR *, caddr_t); int get_uuid_from_diskarb(const char *, u_char *); struct uuidlist * get_uuid_from_list(const struct statfs *, u_char *, const int); struct uuidlist * add_uuid_to_list(const struct statfs *, u_char *, u_char *); struct uuidlist * get_uuid(const struct statfs *, u_char *); struct uuidlist * find_uuid(u_char *); struct uuidlist * find_uuid_by_fsid(u_int32_t); void uuidlist_clearexport(void); char * uuidstring(u_char *, char *); void uuidlist_save(void); void uuidlist_restore(void); struct expidlist * find_export_id(struct uuidlist *, u_int32_t); struct expidlist * get_export_id(struct uuidlist *, char *); void dump_exports(void); void snprintf_cred(char *buf, int, struct xucred *cr); pthread_mutex_t export_mutex; /* lock for mountd/export globals */ struct expfstqh xfshead; /* list of exported file systems */ struct dirlist *xpaths; /* list of exported paths */ int xpaths_complete = 1; struct mountlist *mlhead; /* remote mount list */ struct grouplist *grpcache; /* host/net group cache */ struct xucred def_anon = { /* default map credential: "nobody" */ XUCRED_VERSION, (uid_t) -2, 1, { (gid_t) -2 }, }; LIST_HEAD(,errlist) xerrs; /* list of export errors */ int export_errors, hostnamecount, hostnamegoodcount, missingexportcount; SVCXPRT *udptransp, *tcptransp; SVCXPRT *udp6transp, *tcp6transp; int mounttcpsock, mountudpsock; int mounttcp6sock, mountudp6sock; /* export options */ #define OP_MAPROOT 0x00000001 /* map root credentials */ #define OP_MAPALL 0x00000002 /* map all credentials */ #define OP_SECFLAV 0x00000004 /* security flavor(s) specified */ #define OP_MASK 0x00000008 /* network mask specified */ #define OP_NET 0x00000010 /* network address specified */ #define OP_MANGLEDNAMES 0x00000020 /* tell the vfs to mangle names that are > 255 bytes */ #define OP_ALLDIRS 0x00000040 /* allow mounting subdirs */ #define OP_READONLY 0x00000080 /* export read-only */ #define OP_32BITCLIENTS 0x00000100 /* use 32-bit directory cookies */ #define OP_FSPATH 0x00000200 /* file system path specified */ #define OP_FSUUID 0x00000400 /* file system UUID specified */ #define OP_OFFLINE 0x00000800 /* export is offline */ #define OP_ONLINE 0x04000000 /* export is online */ #define OP_SHOW 0x08000000 /* show this entry in export list */ #define OP_MISSING 0x10000000 /* export is missing */ #define OP_DEFEXP 0x20000000 /* default export for everyone (else) */ #define OP_ADD 0x40000000 /* tag export for potential addition */ #define OP_DEL 0x80000000 /* tag export for potential deletion */ #define OP_EXOPTMASK 0x100009E3 /* export options mask */ #define OP_EXOPTS(X) ((X) & OP_EXOPTMASK) #define RECHECKEXPORTS_TIMEOUT 600 #define RECHECKEXPORTS_DELAYED_STARTUP_TIMEOUT 120 #define RECHECKEXPORTS_DELAYED_STARTUP_INTERVAL 5 /* * 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. */ /* * The incredibly complex mountd thread function */ void * mountd_thread(__unused void *arg) { set_thread_sigmask(); svc_run(); log(LOG_ERR, "mountd died"); exit(1); } void mountd_init(void) { int error; TAILQ_INIT(&xfshead); xpaths = NULL; mlhead = NULL; grpcache = NULL; TAILQ_INIT(&ulhead); LIST_INIT(&xerrs); error = pthread_mutex_init(&export_mutex, NULL); if (error) { log(LOG_ERR, "export mutex init failed: %s (%d)", strerror(error), error); exit(1); } uuidlist_restore(); } void mountd(void) { struct sockaddr_storage saddr; struct sockaddr_in *sin = (struct sockaddr_in*)&saddr; struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)&saddr; socklen_t socklen; struct nfs_export_args nxa; int error, on = 1, init_retry, svcregcnt; pthread_t thd; time_t init_start; /* global initialization */ mountd_init(); check_for_mount_changes(); /* * mountd needs to start from a clean slate. */ /* clear the table of mounts at startup. */ unlink(_PATH_RMOUNTLIST); /* Delete all exports that are in the kernel. */ bzero(&nxa, sizeof(nxa)); nxa.nxa_flags = NXA_DELETE_ALL; error = nfssvc(NFSSVC_EXPORT, &nxa); if (error && (errno != ENOENT)) log(LOG_ERR, "Can't delete all exports: %s (%d)", strerror(errno), errno); /* set up the export and mount lists */ /* * Note that the recheckexports_until functionality will allow us to retry exports * for a while if we have problems resolving any host names. However, these problems * may not affect all exports (e.g. default exports) and that could result in some * hosts getting the wrong access until their export options are set up properly. * Since this could result in some hosts receiving errors or erroneous processing, * we check if the first get_exportlist() check resulted in any successful host * name lookups. If there were names and none of them were looked up successfully, * then we'll delay startup for a *short* while in an attempt to avoid any problems * if the problem clears up shortly. */ init_start = time(NULL); init_retry = 0; while (1) { DEBUG(1, "Getting export list."); get_exportlist(); if (!hostnamecount || hostnamegoodcount) { if (init_retry) log(LOG_WARNING, "host name resolution seems to be working now... continuing initialization"); break; } if (!init_retry) { log(LOG_WARNING, "host name resolution seems to be having problems... delaying initialization"); init_retry = 1; } else if (time(NULL) > (init_start + RECHECKEXPORTS_DELAYED_STARTUP_TIMEOUT)) { log(LOG_WARNING, "giving up on host name resolution... continuing initialization"); break; } sleep(RECHECKEXPORTS_DELAYED_STARTUP_INTERVAL); } DEBUG(1, "Getting mount list."); get_mountlist(); DEBUG(1, "Here we go."); /* create mountd service sockets */ if (!config.udp && !config.tcp) { log(LOG_WARNING, "No network transport(s) configured. mountd thread not starting."); return; } mountudpsock = mounttcpsock = -1; mountudp6sock = mounttcp6sock = -1; /* If we are serving UDP, set up the MOUNT/UDP socket. */ if (config.udp) { /* IPv4 */ if ((mountudpsock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) log(LOG_ERR, "can't create MOUNT/UDP IPv4 socket: %s (%d)", strerror(errno), errno); if (mountudpsock >= 0) { sin->sin_family = AF_INET; sin->sin_addr.s_addr = INADDR_ANY; sin->sin_port = htons(config.mount_port); sin->sin_len = sizeof(*sin); if (bindresvport_sa(mountudpsock, (struct sockaddr*)sin) < 0) { /* socket may still be lingering from previous incarnation */ /* wait a few seconds and try again */ sleep(6); if (bindresvport_sa(mountudpsock, (struct sockaddr*)sin) < 0) { log(LOG_ERR, "can't bind MOUNT/UDP IPv4 addr: %s (%d)", strerror(errno), errno); close(mountudpsock); mountudpsock = -1; } } } if (mountudpsock >= 0) { socklen = sizeof(*sin); if (getsockname(mountudpsock, (struct sockaddr*)sin, &socklen)) { log(LOG_ERR, "can't getsockname on MOUNT/UDP IPv4 socket: %s (%d)", strerror(errno), errno); close(mountudpsock); mountudpsock = -1; } else { mountudpport = ntohs(sin->sin_port); } } if ((mountudpsock >= 0) && ((udptransp = svcudp_create(mountudpsock)) == NULL)) { log(LOG_ERR, "Can't create MOUNT/UDP IPv4 service"); close(mountudpsock); mountudpsock = -1; mountudpport = 0; } if (mountudpsock >= 0) { svcregcnt = 0; if (!svc_register(udptransp, RPCPROG_MNT, 1, mntsrv, 0)) log(LOG_ERR, "Can't register IPv4 MOUNT/UDP v1 service"); else svcregcnt++; if (!svc_register(udptransp, RPCPROG_MNT, 3, mntsrv, 0)) log(LOG_ERR, "Can't register IPv4 MOUNT/UDP v3 service"); else svcregcnt++; if (!svcregcnt) { svc_destroy(udptransp); close(mountudpsock); mountudpsock = -1; mountudpport = 0; } } /* IPv6 */ if ((mountudp6sock = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) log(LOG_ERR, "can't create MOUNT/UDP IPv6 socket: %s (%d)", strerror(errno), errno); if (mountudp6sock >= 0) { setsockopt(mountudp6sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = in6addr_any; sin6->sin6_port = htons(config.mount_port); sin6->sin6_len = sizeof(*sin6); if (bindresvport_sa(mountudp6sock, (struct sockaddr*)sin6) < 0) { /* socket may still be lingering from previous incarnation */ /* wait a few seconds and try again */ sleep(6); if (bindresvport_sa(mountudp6sock, (struct sockaddr*)sin6) < 0) { log(LOG_ERR, "can't bind MOUNT/UDP IPv6 addr: %s (%d)", strerror(errno), errno); close(mountudp6sock); mountudp6sock = -1; } } } if (mountudp6sock >= 0) { socklen = sizeof(*sin6); if (getsockname(mountudp6sock, (struct sockaddr*)sin6, &socklen)) { log(LOG_ERR, "can't getsockname on MOUNT/UDP IPv6 socket: %s (%d)", strerror(errno), errno); close(mountudp6sock); mountudp6sock = -1; } else { mountudp6port = ntohs(sin6->sin6_port); } } if ((mountudp6sock >= 0) && ((udp6transp = svcudp_create(mountudp6sock)) == NULL)) { log(LOG_ERR, "Can't create MOUNT/UDP IPv6 service"); close(mountudp6sock); mountudp6sock = -1; mountudp6port = 0; } if (mountudp6sock >= 0) { svcregcnt = 0; if (!svc_register(udp6transp, RPCPROG_MNT, 1, mntsrv, 0)) log(LOG_ERR, "Can't register IPv6 MOUNT/UDP v1 service"); else svcregcnt++; if (!svc_register(udp6transp, RPCPROG_MNT, 3, mntsrv, 0)) log(LOG_ERR, "Can't register IPv6 MOUNT/UDP v3 service"); else svcregcnt++; if (!svcregcnt) { svc_destroy(udp6transp); close(mountudp6sock); mountudp6sock = -1; mountudp6port = 0; } } } /* If we are serving TCP, set up the MOUNT/TCP socket. */ if (config.tcp) { /* IPv4 */ if ((mounttcpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) log(LOG_ERR, "can't create MOUNT/TCP IPv4 socket: %s (%d)", strerror(errno), errno); if (mounttcpsock >= 0) { if (setsockopt(mounttcpsock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) log(LOG_WARNING, "setsockopt MOUNT/TCP IPv4 SO_REUSEADDR: %s (%d)", strerror(errno), errno); sin->sin_family = AF_INET; sin->sin_addr.s_addr = INADDR_ANY; sin->sin_port = htons(config.mount_port); sin->sin_len = sizeof(*sin); if (bindresvport_sa(mounttcpsock, (struct sockaddr*)sin) < 0) { log(LOG_ERR, "can't bind MOUNT/TCP IPv4 addr: %s (%d)", strerror(errno), errno); close(mounttcpsock); mounttcpsock = -1; } } if ((mounttcpsock >= 0) && (listen(mounttcpsock, 128) < 0)) { log(LOG_ERR, "MOUNT IPv4 listen failed: %s (%d)", strerror(errno), errno); close(mounttcpsock); mounttcpsock = -1; } if (mounttcpsock >= 0) { socklen = sizeof(*sin); if (getsockname(mounttcpsock, (struct sockaddr*)sin, &socklen)) { log(LOG_ERR, "can't getsockname on MOUNT/TCP IPv4 socket: %s (%d)", strerror(errno), errno); close(mounttcpsock); mounttcpsock = -1; } else { mounttcpport = ntohs(sin->sin_port); } } if ((mounttcpsock >= 0) && ((tcptransp = svctcp_create(mounttcpsock, 0, 0)) == NULL)) { log(LOG_ERR, "Can't create MOUNT/TCP IPv4 service"); close(mounttcpsock); mounttcpsock = -1; mounttcpport = 0; } if (mounttcpsock >= 0) { svcregcnt = 0; if (!svc_register(tcptransp, RPCPROG_MNT, 1, mntsrv, 0)) log(LOG_ERR, "Can't register IPv4 MOUNT/TCP v1 service"); else svcregcnt++; if (!svc_register(tcptransp, RPCPROG_MNT, 3, mntsrv, 0)) log(LOG_ERR, "Can't register IPv4 MOUNT/TCP v3 service"); else svcregcnt++; if (!svcregcnt) { svc_destroy(tcptransp); close(mounttcpsock); mounttcpsock = -1; mounttcpport = 0; } } /* IPv6 */ if ((mounttcp6sock = socket(AF_INET6, SOCK_STREAM, 0)) < 0) log(LOG_ERR, "can't create MOUNT/TCP IPv6 socket: %s (%d)", strerror(errno), errno); if (mounttcp6sock >= 0) { if (setsockopt(mounttcp6sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) log(LOG_WARNING, "setsockopt MOUNT/TCP IPv6 SO_REUSEADDR: %s (%d)", strerror(errno), errno); setsockopt(mounttcp6sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = in6addr_any; sin6->sin6_port = htons(config.mount_port); sin6->sin6_len = sizeof(*sin6); if (bindresvport_sa(mounttcp6sock, (struct sockaddr*)sin6) < 0) { log(LOG_ERR, "can't bind MOUNT/TCP IPv6 addr: %s (%d)", strerror(errno), errno); close(mounttcp6sock); mounttcp6sock = -1; } } if ((mounttcp6sock >= 0) && (listen(mounttcp6sock, 128) < 0)) { log(LOG_ERR, "MOUNT IPv6 listen failed: %s (%d)", strerror(errno), errno); close(mounttcp6sock); mounttcp6sock = -1; } if (mounttcp6sock >= 0) { socklen = sizeof(*sin6); if (getsockname(mounttcp6sock, (struct sockaddr*)sin6, &socklen)) { log(LOG_ERR, "can't getsockname on MOUNT/TCP IPv6 socket: %s (%d)", strerror(errno), errno); close(mounttcp6sock); mounttcp6sock = -1; } else { mounttcp6port = ntohs(sin6->sin6_port); } } if ((mounttcp6sock >= 0) && ((tcp6transp = svctcp_create(mounttcp6sock, 0, 0)) == NULL)) { log(LOG_ERR, "Can't create MOUNT/TCP IPv6 service"); close(mounttcp6sock); mounttcp6sock = -1; mounttcp6port = 0; } if (mounttcp6sock >= 0) { svcregcnt = 0; if (!svc_register(tcp6transp, RPCPROG_MNT, 1, mntsrv, 0)) log(LOG_ERR, "Can't register IPv6 MOUNT/TCP v1 service"); else svcregcnt++; if (!svc_register(tcp6transp, RPCPROG_MNT, 3, mntsrv, 0)) log(LOG_ERR, "Can't register IPv6 MOUNT/TCP v3 service"); else svcregcnt++; if (!svcregcnt) { svc_destroy(tcp6transp); close(mounttcp6sock); mounttcp6sock = -1; mounttcp6port = 0; } } } if ((mountudp6sock < 0) && (mounttcp6sock < 0)) log(LOG_WARNING, "Can't create MOUNT IPv6 sockets"); if ((mountudpsock < 0) && (mounttcpsock < 0)) log(LOG_WARNING, "Can't create MOUNT IPv4 sockets"); if ((mountudp6sock < 0) && (mounttcp6sock < 0) && (mountudpsock < 0) && (mounttcpsock < 0)) { log(LOG_ERR, "Can't create any MOUNT sockets!"); exit(1); } /* launch mountd pthread */ error = pthread_create(&thd, &pattr, mountd_thread, NULL); if (error) { log(LOG_ERR, "mountd pthread_create: %s (%d)", strerror(error), error); exit(1); } } /* * functions for locking/unlocking the exports list (and other export-related globals) */ void lock_exports(void) { int error; if (checkexports) return; error = pthread_mutex_lock(&export_mutex); if (error) log(LOG_ERR, "export mutex lock failed: %s (%d)", strerror(error), error); } void unlock_exports(void) { int error; if (checkexports) return; error = pthread_mutex_unlock(&export_mutex); if (error) log(LOG_ERR, "export mutex unlock failed: %s (%d)", strerror(error), error); } /* * The mount rpc service */ void mntsrv(struct svc_req *rqstp, SVCXPRT *transp) { struct expfs *xf; struct fhreturn fhr; struct stat stb; struct statfs fsb; struct nfs_sec secflavs; struct sockaddr *sa; u_short sport; char rpcpath[RPCMNT_PATHLEN + 1], dirpath[MAXPATHLEN]; char addrbuf[2*INET6_ADDRSTRLEN], hostbuf[NI_MAXHOST]; int bad = ENOENT, options; u_char uuid[16]; sa = svc_getcaller_sa(transp); if (sa->sa_family == AF_INET) sport = ntohs(((struct sockaddr_in*) AOK sa)->sin_port); else if (sa->sa_family == AF_INET6) sport = ntohs(((struct sockaddr_in6*) AOK sa)->sin6_port); else sport = 0; strlcpy(hostbuf, "unknown_host", sizeof(hostbuf)); strlcpy(addrbuf, "unknown_host", sizeof(addrbuf)); switch (rqstp->rq_proc) { case NULLPROC: if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) log(LOG_ERR, "Can't send NULL MOUNT reply"); return; case RPCMNT_MOUNT: if ((sport >= IPPORT_RESERVED) && config.mount_require_resv_port) { svcerr_weakauth(transp); return; } if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) { svcerr_decode(transp); return; } lock_exports(); /* * 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) == 0 || stat(dirpath, &stb) < 0 || (!S_ISDIR(stb.st_mode) && (!config.mount_regular_files || !S_ISREG(stb.st_mode))) || statfs(dirpath, &fsb) < 0) { unlock_exports(); chdir("/"); /* Just in case realpath doesn't */ DEBUG(1, "stat failed on %s", dirpath); if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) log(LOG_ERR, "Can't send reply for failed mount"); if (config.verbose) { getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); log(LOG_NOTICE, "Mount failed: %s %s", addrbuf, dirpath); } return; } /* get UUID for volume */ if (!get_uuid_from_list(&fsb, uuid, UL_CHECK_ALL)) { unlock_exports(); DEBUG(1, "no exported volume uuid for %s", dirpath); if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) log(LOG_ERR, "Can't send reply for failed mount"); if (config.verbose) { getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); log(LOG_NOTICE, "Mount failed: %s %s", addrbuf, dirpath); } return; } /* Check in the exports list */ xf = ex_search(uuid); if (xf && expdir_search(xf, dirpath, sa, &options, &secflavs)) { fhr.fhr_flags = options; fhr.fhr_vers = rqstp->rq_vers; bcopy(&secflavs, &fhr.fhr_sec, sizeof(struct nfs_sec)); /* Get the file handle (specifying max fh size based on protocol version */ memset(&fhr.fhr_fh, 0, sizeof(fhandle_t)); fhr.fhr_fh.fh_len = (fhr.fhr_vers < 3) ? NFSV2_MAX_FH_SIZE : NFSV3_MAX_FH_SIZE; if (getfh(dirpath, (fhandle_t *)&fhr.fhr_fh) < 0) { DEBUG(1, "Can't get fh for %s: %s (%d)", dirpath, strerror(errno), errno); bad = EACCES; /* path must not be exported */ if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) log(LOG_ERR, "Can't send reply for failed mount"); unlock_exports(); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_fhs, (caddr_t)&fhr)) log(LOG_ERR, "Can't send mount reply"); if (!getnameinfo(sa, sa->sa_len, hostbuf, sizeof(hostbuf), NULL, 0, 0)) add_mlist(hostbuf, dirpath); log(LOG_INFO, "Mount successful: %s %s", hostbuf, dirpath); } else { bad = EACCES; if (config.verbose) { getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); log(LOG_NOTICE, "Mount failed: %s %s", addrbuf, dirpath); } if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) log(LOG_ERR, "Can't send reply for failed mount"); } unlock_exports(); return; case RPCMNT_DUMP: if (!svc_sendreply(transp, (xdrproc_t)xdr_mlist, (caddr_t)NULL)) log(LOG_ERR, "Can't send MOUNT dump reply"); if (config.verbose >= 3) { getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); DEBUG(1, "dump: %s", addrbuf); } return; case RPCMNT_UMOUNT: if ((sport >= IPPORT_RESERVED) && config.mount_require_resv_port) { svcerr_weakauth(transp); return; } if (!svc_getargs(transp, (xdrproc_t)xdr_dir, dirpath)) { svcerr_decode(transp); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) log(LOG_ERR, "Can't send UMOUNT reply"); if (!getnameinfo(sa, sa->sa_len, hostbuf, sizeof(hostbuf), NULL, 0, NI_NAMEREQD)) del_mlist(hostbuf, dirpath); else hostbuf[0] = '\0'; if (!getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST)) del_mlist(addrbuf, dirpath); log(LOG_INFO, "umount: %s %s", hostbuf[0] ? hostbuf : addrbuf, dirpath); return; case RPCMNT_UMNTALL: if ((sport >= IPPORT_RESERVED) && config.mount_require_resv_port) { svcerr_weakauth(transp); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) log(LOG_ERR, "Can't send UMNTALL reply"); if (!getnameinfo(sa, sa->sa_len, hostbuf, sizeof(hostbuf), NULL, 0, NI_NAMEREQD)) del_mlist(hostbuf, NULL); else hostbuf[0] = '\0'; if (!getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST)) del_mlist(addrbuf, (char *)NULL); log(LOG_INFO, "umount all: %s", hostbuf[0] ? hostbuf : addrbuf); return; case RPCMNT_EXPORT: lock_exports(); if (!svc_sendreply(transp, (xdrproc_t)xdr_explist, (caddr_t)NULL)) log(LOG_ERR, "Can't send EXPORT reply"); unlock_exports(); if (config.verbose >= 3) { getnameinfo(sa, sa->sa_len, addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); DEBUG(1, "export: %s", addrbuf); } return; default: svcerr_noproc(transp); return; } } /* * Xdr conversion for a dirpath string */ int xdr_dir(XDR *xdrsp, char *dirp) { return (xdr_string(xdrsp, &dirp, RPCMNT_PATHLEN)); } /* * Xdr routine to generate file handle reply */ int xdr_fhs(XDR *xdrsp, caddr_t cp) { struct fhreturn *fhrp = (struct fhreturn *) AOK cp; xdr_long_t ok = 0, len, auth; int32_t i; if (!xdr_long(xdrsp, &ok)) return (0); switch (fhrp->fhr_vers) { case 1: return (xdr_opaque(xdrsp, fhrp->fhr_fh.fh_data, NFSX_V2FH)); case 3: len = fhrp->fhr_fh.fh_len; if (!xdr_long(xdrsp, &len)) return (0); if (!xdr_opaque(xdrsp, fhrp->fhr_fh.fh_data, fhrp->fhr_fh.fh_len)) return (0); /* security flavors */ if (fhrp->fhr_sec.count == 0) { auth = RPCAUTH_SYS; len = 1; if (!xdr_long(xdrsp, &len)) return (0); return (xdr_long(xdrsp, &auth)); } len = fhrp->fhr_sec.count; if (!xdr_long(xdrsp, &len)) return (0); for (i = 0; i < fhrp->fhr_sec.count; i++) { auth = (xdr_long_t)fhrp->fhr_sec.flavors[i]; if (!xdr_long(xdrsp, &auth)) return (0); } return (TRUE); }; return (0); } int xdr_mlist(XDR *xdrsp, __unused caddr_t cp) { struct mountlist *mlp; int trueval = 1; int falseval = 0; mlp = mlhead; while (mlp) { if (!xdr_bool(xdrsp, &trueval)) return (0); if (!xdr_string(xdrsp, &mlp->ml_host, RPCMNT_NAMELEN)) return (0); if (!xdr_string(xdrsp, &mlp->ml_dir, 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(XDR *xdrsp, __unused caddr_t cp) { struct expfs *xf; struct expdir *xd; int falseval = 0; TAILQ_FOREACH(xf, &xfshead, xf_next) { TAILQ_FOREACH(xd, &xf->xf_dirl, xd_next) { if (put_exlist(xd, xdrsp)) goto errout; } } if (!xdr_bool(xdrsp, &falseval)) return (0); return (1); errout: return (0); } /* * Called from xdr_explist() to output the mountable exported * directory paths. */ int put_exlist(struct expdir *xd, XDR *xdrsp) { struct expdir *mxd; struct grouplist *grp; struct host *hp; int trueval = 1; int falseval = 0; char *strp; char offline_all[] = ""; char offline_some[] = ""; char everyone[] = "(Everyone)"; char abuf[RPCMNT_NAMELEN+1]; int offline = 0, auth = 0, i; if (!xd) return (0); if (!xdr_bool(xdrsp, &trueval)) return (1); strp = xd->xd_dir; if (!xdr_string(xdrsp, &strp, RPCMNT_PATHLEN)) return (1); if (xd->xd_iflags & OP_OFFLINE) { /* report if export is offline for all or some* hosts */ if (!xdr_bool(xdrsp, &trueval)) return (1); strp = (xd->xd_iflags & OP_ONLINE) ? offline_some : offline_all; if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN)) return (1); offline = 1; } if ((xd->xd_ssec.count > 1) || (xd->xd_ssec.flavors[0] != RPCAUTH_SYS)) { /* report non-default auth flavors */ if (!xdr_bool(xdrsp, &trueval)) return (1); abuf[0] = '\0'; strlcpy(abuf, "<", sizeof(abuf)); for (i=0; i < xd->xd_ssec.count; i++) { if (xd->xd_ssec.flavors[i] == RPCAUTH_SYS) strlcat(abuf, "sys", sizeof(abuf)); else if (xd->xd_ssec.flavors[i] == RPCAUTH_KRB5) strlcat(abuf, "krb5", sizeof(abuf)); else if (xd->xd_ssec.flavors[i] == RPCAUTH_KRB5I) strlcat(abuf, "krb5i", sizeof(abuf)); else if (xd->xd_ssec.flavors[i] == RPCAUTH_KRB5P) strlcat(abuf, "krb5p", sizeof(abuf)); else continue; if (i < xd->xd_ssec.count-1) strlcat(abuf, ":", sizeof(abuf)); } strlcat(abuf, ">", sizeof(abuf)); strp = abuf; if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN)) return (1); auth = 1; } if (!(xd->xd_flags & OP_DEFEXP)) { TAILQ_FOREACH(hp, &xd->xd_hosts, ht_next) { if (!(hp->ht_flags & OP_SHOW)) continue; grp = hp->ht_grp; switch (grp->gr_type) { case GT_HOST: case GT_NET: case GT_NETGROUP: if (!xdr_bool(xdrsp, &trueval)) return (1); strp = grp_name(grp); if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN)) return (1); } } } else if (offline || auth) { if (!xdr_bool(xdrsp, &trueval)) return (1); strp = everyone; if (!xdr_string(xdrsp, &strp, RPCMNT_NAMELEN)) return (1); } if (!xdr_bool(xdrsp, &falseval)) return (1); TAILQ_FOREACH(mxd, &xd->xd_mountdirs, xd_next) { if (put_exlist(mxd, xdrsp)) return (1); } return (0); } /* * Clean up a pathname. Removes quotes around quoted strings, * strips escaped characters, removes trailing slashes. */ char * clean_pathname(char *line) { int len, esc; char c, *p, *s; if (line == NULL) return NULL; len = strlen(line); s = malloc(len + 1); if (s == NULL) return NULL; len = 0; esc = 0; c = '\0'; p = line; if (*p == '\'' || *p == '"') { c = *p; p++; } for (;*p != '\0'; p++) { if (esc == 1) { s[len++] = *p; esc = 0; } else if (*p == c) break; else if (*p == '\\') esc = 1; else if (c == '\0' && (*p == ' ' || *p == '\t')) break; else s[len++] = *p; } /* strip trailing slashes */ for (; len > 1 && s[len-1] == '/'; len--) ; s[len] = '\0'; return (s); } /* * Query DiskArb for a volume's UUID */ int get_uuid_from_diskarb(const char *path, u_char *uuid) { DASessionRef session; DADiskRef disk; CFDictionaryRef dd; CFTypeRef val; CFUUIDBytes uuidbytes; int rv = 1; session = NULL; disk = NULL; dd = NULL; session = DASessionCreate(NULL); if (!session) { log(LOG_ERR, "can't create DiskArb session"); rv = 0; goto out; } disk = DADiskCreateFromBSDName(NULL, session, path); if (!disk) { DEBUG(1, "DADiskCreateFromBSDName(%s) failed", path); rv = 0; goto out; } dd = DADiskCopyDescription(disk); if (!dd) { DEBUG(1, "DADiskCopyDescription(%s) failed", path); rv = 0; goto out; } if (!CFDictionaryGetValueIfPresent(dd, (kDADiskDescriptionVolumeUUIDKey), &val)) { DEBUG(1, "unable to get UUID for volume %s", path); rv = 0; goto out; } uuidbytes = CFUUIDGetUUIDBytes(val); bcopy(&uuidbytes, uuid, sizeof(uuidbytes)); out: if (session) CFRelease(session); if (disk) CFRelease(disk); if (dd) CFRelease(dd); return (rv); } /* * find the UUID for this volume in the UUID list */ struct uuidlist * get_uuid_from_list(const struct statfs *fsb, u_char *uuid, const int flags) { struct uuidlist *ulp; if (!(flags & UL_CHECK_ALL)) return (NULL); TAILQ_FOREACH(ulp, &ulhead, ul_list) { if ((flags & UL_CHECK_MNTFROM) && strcmp(fsb->f_mntfromname, ulp->ul_mntfromname)) continue; if ((flags & UL_CHECK_MNTON) && strcmp(fsb->f_mntonname, ulp->ul_mntonname)) continue; if (uuid) bcopy(&ulp->ul_uuid, uuid, sizeof(ulp->ul_uuid)); break; } return (ulp); } /* * find UUID list entry with the given UUID */ struct uuidlist * find_uuid(u_char *uuid) { struct uuidlist *ulp; TAILQ_FOREACH(ulp, &ulhead, ul_list) { if (!bcmp(ulp->ul_uuid, uuid, sizeof(ulp->ul_uuid))) break; } return (ulp); } /* * find UUID list entry with the given FSID */ struct uuidlist * find_uuid_by_fsid(u_int32_t fsid) { struct uuidlist *ulp; TAILQ_FOREACH(ulp, &ulhead, ul_list) { if (ulp->ul_fsid == fsid) break; } return (ulp); } /* * add a UUID to the UUID list */ struct uuidlist * add_uuid_to_list(const struct statfs *fsb, u_char *dauuid, u_char *uuid) { struct uuidlist *ulpnew; u_int32_t xfsid; ulpnew = malloc(sizeof(struct uuidlist)); if (!ulpnew) { log(LOG_ERR, "add_uuid_to_list: out of memory"); return (NULL); } bzero(ulpnew, sizeof(*ulpnew)); LIST_INIT(&ulpnew->ul_exportids); if (dauuid) { bcopy(dauuid, ulpnew->ul_dauuid, sizeof(ulpnew->ul_dauuid)); ulpnew->ul_davalid = 1; } bcopy(uuid, ulpnew->ul_uuid, sizeof(ulpnew->ul_uuid)); strlcpy(ulpnew->ul_mntfromname, fsb->f_mntfromname, sizeof(ulpnew->ul_mntfromname)); strlcpy(ulpnew->ul_mntonname, fsb->f_mntonname, sizeof(ulpnew->ul_mntonname)); /* make sure exported FS ID is unique */ xfsid = UUID2FSID(uuid); ulpnew->ul_fsid = xfsid; while (find_uuid_by_fsid(ulpnew->ul_fsid)) if (++ulpnew->ul_fsid == xfsid) { /* exhausted exported FS ID values! */ log(LOG_ERR, "exported FS ID values exhausted, can't add %s", ulpnew->ul_mntonname); free(ulpnew); return (NULL); } TAILQ_INSERT_TAIL(&ulhead, ulpnew, ul_list); return (ulpnew); } /* * get the UUID to use for this volume's file handles * and add it to the UUID list if it isn't there yet. */ struct uuidlist * get_uuid(const struct statfs *fsb, u_char *uuid) { CFUUIDRef cfuuid; CFUUIDBytes uuidbytes; struct uuidlist *ulp; u_char dauuid[16]; int davalid, uuidchanged, reportuuid = 0; char buf[64], buf2[64]; /* get DiskArb's idea of the UUID (if any) */ davalid = get_uuid_from_diskarb(fsb->f_mntfromname, dauuid); if (davalid) { DEBUG(2, "get_uuid: %s %s DiskArb says: %s", fsb->f_mntfromname, fsb->f_mntonname, uuidstring(dauuid, buf)); } /* try to get UUID out of UUID list */ if ((ulp = get_uuid_from_list(fsb, uuid, UL_CHECK_MNTON))) { DEBUG(2, "get_uuid: %s %s found: %s", fsb->f_mntfromname, fsb->f_mntonname, uuidstring(uuid, buf)); /* * Check against any DiskArb UUID. * If diskarb UUID is different then drop the uuid entry. */ if (davalid) { if (!ulp->ul_davalid) uuidchanged = 1; else if (bcmp(ulp->ul_dauuid, dauuid, sizeof(dauuid))) uuidchanged = 1; else uuidchanged = 0; } else { if (ulp->ul_davalid) { /* * We had a UUID before, but now we don't? * Assume this is just a transient error, * issue a warning, and stick with the old UUID. */ uuidstring(ulp->ul_dauuid, buf); log(LOG_WARNING, "lost UUID for %s, was %s, keeping old UUID", fsb->f_mntonname, buf); uuidchanged = 0; } else uuidchanged = 0; } if (uuidchanged) { uuidstring(ulp->ul_dauuid, buf); if (davalid) uuidstring(dauuid, buf2); else strlcpy(buf2, "------------------------------------", sizeof(buf2)); if (ulp->ul_exported) { /* * Woah! We already have this file system exported with * a different UUID (UUID changed while processing the * exports list). Ignore the UUID change for now so that * all the exports for this file system will be registered * using the same UUID/FSID. * * XXX Should we do something like set gothup=1 so that * we will reregister all the exports (with the new UUID)? * If so, what's to prevent an infinite loop if we always * seem to be hitting this problem? */ log(LOG_WARNING, "ignoring UUID change for already exported file system %s, was %s now %s", fsb->f_mntonname, buf, buf2); uuidchanged = 0; } } if (uuidchanged) { log(LOG_WARNING, "UUID changed for %s, was %s now %s", fsb->f_mntonname, buf, buf2); bcopy(dauuid, uuid, sizeof(dauuid)); /* remove old UUID from list */ TAILQ_REMOVE(&ulhead, ulp, ul_list); free(ulp); ulp = NULL; } else { ulp->ul_exported = 1; } } else if (davalid) { /* * The UUID wasn't in the list, but DiskArb has a UUID for it. * (If the DiskArb UUID conflicts with something already in the * list, we'll need to create a new UUID for it below.) */ bcopy(dauuid, uuid, sizeof(dauuid)); } else { /* * We need to create a UUID to use for this volume. * This is because it wasn't already in the list, and * either DiskArb didn't have a UUID for the volume or * the UUID DiskArb has for the volume conflicts with * a UUID for a volume already in the list. */ reportuuid = 1; cfuuid = CFUUIDCreate(NULL); uuidbytes = CFUUIDGetUUIDBytes(cfuuid); bcopy(&uuidbytes, uuid, sizeof(uuidbytes)); CFRelease(cfuuid); } if (!ulp) { /* * Add the UUID to the list, but make sure it is unique first. */ while ((ulp = find_uuid(uuid))) { reportuuid = 1; uuidstring(uuid, buf); log(LOG_WARNING, "%s UUID conflict with %s, %s", fsb->f_mntonname, ulp->ul_mntonname, buf); cfuuid = CFUUIDCreate(NULL); uuidbytes = CFUUIDGetUUIDBytes(cfuuid); bcopy(&uuidbytes, uuid, sizeof(uuidbytes)); CFRelease(cfuuid); /* double check that the UUID is unique */ } ulp = add_uuid_to_list(fsb, (davalid ? dauuid : NULL), uuid); if (!ulp) { log(LOG_ERR, "error adding %s", fsb->f_mntonname); } else { ulp->ul_exported = 1; } } else if (!ulp->ul_mntfromname[0]) { /* * If the volume didn't exist when mountd read the * mountdexptab, it's possible this ulp doesn't * have a copy of it's mntfromname. So, we make * sure to grab a copy here before the volume gets * exported. */ strlcpy(ulp->ul_mntfromname, fsb->f_mntfromname, sizeof(ulp->ul_mntfromname)); } if (reportuuid) log(LOG_WARNING, "%s using UUID %s", fsb->f_mntonname, uuidstring(uuid, buf)); else DEBUG(1, "%s using UUID %s", fsb->f_mntonname, uuidstring(uuid, buf)); return (ulp); } /* * clear export flags on all UUID entries */ void uuidlist_clearexport(void) { struct uuidlist *ulp; TAILQ_FOREACH(ulp, &ulhead, ul_list) { ulp->ul_exported = 0; } } /* convert UUID bytes to UUID string */ #define HEXTOC(c) \ ((c) >= 'a' ? ((c) - ('a' - 10)) : \ ((c) >= 'A' ? ((c) - ('A' - 10)) : ((c) - '0'))) #define HEXSTRTOI(p) \ ((HEXTOC(p[0]) << 4) + HEXTOC(p[1])) char * uuidstring(u_char *uuid, char *string) { snprintf(string, (16*2)+4+1, /* XXX silly, yes. But at least we're not using sprintf. [sigh] */ "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", uuid[0] & 0xff, uuid[1] & 0xff, uuid[2] & 0xff, uuid[3] & 0xff, uuid[4] & 0xff, uuid[5] & 0xff, uuid[6] & 0xff, uuid[7] & 0xff, uuid[8] & 0xff, uuid[9] & 0xff, uuid[10] & 0xff, uuid[11] & 0xff, uuid[12] & 0xff, uuid[13] & 0xff, uuid[14] & 0xff, uuid[15] & 0xff); return (string); } /* * save the exported volume UUID list to the mountdexptab file * * We have the option of saving all UUIDs in the list, or just * saving the ones that are currently exported. However, if we * have a volume exported, then removed from the export list, and * then added back to the export list, it may be expected that the * file handles/UUIDs will be the same. But if we don't save what * the UUIDs were before, we risk the chance of using a different * UUID for the second export. This can happen if the volume's * DiskArb UUID is not used for export (because DiskArb doesn't have * a UUID for it, or because there was a UUID conflict and we needed * to use a different UUID). */ void uuidlist_save(void) { FILE *ulfile; struct uuidlist *ulp; struct expidlist *xid; char buf[64], buf2[64]; if ((ulfile = fopen(_PATH_MOUNTEXPLIST, "w")) == NULL) { log(LOG_ERR, "Can't write %s: %s (%d)", _PATH_MOUNTEXPLIST, strerror(errno), errno); return; } TAILQ_FOREACH(ulp, &ulhead, ul_list) { #ifdef SAVE_ONLY_EXPORTED_UUIDS if (!ulp->ul_exported) continue; #endif if (ulp->ul_davalid) uuidstring(ulp->ul_dauuid, buf); else strlcpy(buf, "------------------------------------", sizeof(buf)); uuidstring(ulp->ul_uuid, buf2); fprintf(ulfile, "%s %s 0x%08X %s\n", buf, buf2, ulp->ul_fsid, ulp->ul_mntonname); LIST_FOREACH(xid, &ulp->ul_exportids, xid_list) { fprintf(ulfile, "XID 0x%08X %s\n", xid->xid_id, ((xid->xid_path[0] == '\0') ? "." : xid->xid_path)); } } fclose(ulfile); } /* * read in the exported volume UUID list from the mountdexptab file */ void uuidlist_restore(void) { struct uuidlist *ulp; struct expidlist *xid; char *cp, str[2*MAXPATHLEN]; FILE *ulfile; int i, slen, davalid, uuidchanged; uint32_t linenum; struct statfs fsb; u_char dauuid[16]; char buf[64], buf2[64]; if ((ulfile = fopen(_PATH_MOUNTEXPLIST, "r")) == NULL) { if (errno != ENOENT) log(LOG_WARNING, "Can't open %s: %s (%d)", _PATH_MOUNTEXPLIST, strerror(errno), errno); else DEBUG(1, "Can't open %s, %s (%d)", _PATH_MOUNTEXPLIST, strerror(errno), errno); return; } ulp = NULL; linenum = 0; while (fgets(str, 2*MAXPATHLEN, ulfile) != NULL) { linenum++; slen = strlen(str); if (str[slen-1] == '\n') str[slen-1] = '\0'; if (!strncmp(str, "XID ", 4)) { /* we have an export ID line for the current UUID */ if (!ulp) { log(LOG_ERR, "ignoring orphaned export ID at line %d of %s", linenum, _PATH_MOUNTEXPLIST); continue; } /* parse XID and add to current UUID */ xid = malloc(sizeof(*xid)); if (xid == NULL) { log(LOG_ERR, "uuidlist_restore: Out of memory"); exit(2); } cp = str + 4; slen -= 4; if (sscanf(cp, "%i", &xid->xid_id) != 1) { log(LOG_ERR, "invalid export ID at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(xid); continue; } while (*cp && (*cp != ' ')) { cp++; slen--; } cp++; slen--; if (slen >= (int)sizeof(xid->xid_path)) { log(LOG_ERR, "export ID path too long at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(xid); continue; } if ((cp[0] == '.') && (cp[1] == '\0')) xid->xid_path[0] = '\0'; else strlcpy(xid->xid_path, cp, sizeof(xid->xid_path)); LIST_INSERT_HEAD(&ulp->ul_exportids, xid, xid_list); continue; } ulp = malloc(sizeof(*ulp)); if (ulp == NULL) { log(LOG_ERR, "uuidlist_restore: Out of memory"); exit(2); } bzero(ulp, sizeof(*ulp)); LIST_INIT(&ulp->ul_exportids); cp = str; if (*cp == '-') { /* DiskArb UUID not present */ ulp->ul_davalid = 0; bzero(ulp->ul_dauuid, sizeof(ulp->ul_dauuid)); while (*cp && (*cp != ' ')) cp++; } else { ulp->ul_davalid = 1; for (i=0; i < (int)sizeof(ulp->ul_dauuid); i++, cp+=2) { if (*cp == '-') cp++; if (!isxdigit(*cp) || !isxdigit(*(cp+1))) { log(LOG_ERR, "invalid UUID at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); ulp = NULL; break; } ulp->ul_dauuid[i] = HEXSTRTOI(cp); } } if (ulp == NULL) continue; cp++; for (i=0; i < (int)sizeof(ulp->ul_uuid); i++, cp+=2) { if (*cp == '-') cp++; if (!isxdigit(*cp) || !isxdigit(*(cp+1))) { log(LOG_ERR, "invalid UUID at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); ulp = NULL; break; } ulp->ul_uuid[i] = HEXSTRTOI(cp); } if (ulp == NULL) continue; if (*cp != ' ') { log(LOG_ERR, "invalid entry at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); continue; } cp++; if (sscanf(cp, "%i", &ulp->ul_fsid) != 1) { log(LOG_ERR, "invalid entry at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); continue; } while (*cp && (*cp != ' ')) cp++; if (*cp != ' ') { log(LOG_ERR, "invalid entry at line %d of %s", linenum, _PATH_MOUNTEXPLIST); free(ulp); continue; } cp++; strncpy(ulp->ul_mntonname, cp, MAXPATHLEN); ulp->ul_mntonname[MAXPATHLEN-1] = '\0'; /* verify the path exists and that it is a mount point */ if (!check_dirpath(ulp->ul_mntonname) || (statfs(ulp->ul_mntonname, &fsb) < 0) || strcmp(ulp->ul_mntonname, fsb.f_mntonname)) { /* don't drop the UUID record if the volume isn't currently mounted! */ /* If it's mounted/exported later, we want to use the same record. */ DEBUG(1, "export entry for non-existent file system %s at line %d of %s", ulp->ul_mntonname, linenum, _PATH_MOUNTEXPLIST); ulp->ul_mntfromname[0] = '\0'; TAILQ_INSERT_TAIL(&ulhead, ulp, ul_list); continue; } /* grab the path's mntfromname */ strncpy(ulp->ul_mntfromname, fsb.f_mntfromname, MAXPATHLEN); ulp->ul_mntfromname[MAXPATHLEN-1] = '\0'; /* * Grab DiskArb's UUID for this volume (if any) and * see if it has changed. */ davalid = get_uuid_from_diskarb(ulp->ul_mntfromname, dauuid); if (davalid) { if (!ulp->ul_davalid) uuidchanged = 1; else if (bcmp(ulp->ul_dauuid, dauuid, sizeof(dauuid))) uuidchanged = 1; else uuidchanged = 0; } else { if (ulp->ul_davalid) { /* * We had a UUID before, but now we don't? * Assume this is just a transient error, * issue a warning, and stick with the old UUID. */ uuidstring(ulp->ul_dauuid, buf); log(LOG_WARNING, "lost UUID for %s, was %s, keeping old UUID", fsb.f_mntonname, buf); uuidchanged = 0; } else uuidchanged = 0; } if (uuidchanged) { /* The UUID changed, so we'll drop any entry */ uuidstring(ulp->ul_dauuid, buf); if (davalid) uuidstring(dauuid, buf2); else strlcpy(buf2, "------------------------------------", sizeof(buf2)); log(LOG_WARNING, "UUID changed for %s, was %s now %s", ulp->ul_mntonname, buf, buf2); free(ulp); continue; } TAILQ_INSERT_TAIL(&ulhead, ulp, ul_list); } fclose(ulfile); } struct expidlist * find_export_id(struct uuidlist *ulp, u_int32_t id) { struct expidlist *xid; LIST_FOREACH(xid, &ulp->ul_exportids, xid_list) { if (xid->xid_id == id) break; } return (xid); } struct expidlist * get_export_id(struct uuidlist *ulp, char *path) { struct expidlist *xid; u_int32_t maxid = 0; LIST_FOREACH(xid, &ulp->ul_exportids, xid_list) { if (!strcmp(xid->xid_path, path)) break; if (maxid < xid->xid_id) maxid = xid->xid_id; } if (xid) return (xid); /* add it */ xid = malloc(sizeof(*xid)); if (!xid) { log(LOG_ERR, "get_export_id: out of memory"); return (NULL); } bzero(xid, sizeof(*xid)); strlcpy(xid->xid_path, path, sizeof(xid->xid_path)); xid->xid_id = maxid + 1; while (find_export_id(ulp, xid->xid_id)) { xid->xid_id++; if (xid->xid_id == maxid) { /* exhausted export id values! */ log(LOG_ERR, "export ID values exhausted for %s", ulp->ul_mntonname); free(xid); return (NULL); } } LIST_INSERT_HEAD(&ulp->ul_exportids, xid, xid_list); return (xid); } /* * find_exported_fs_by_path_and_uuid() * * Given a path and a uuid, find the exported file system that * best matches both. */ struct uuidlist * find_exported_fs_by_path_and_uuid(char *fspath, u_char *fsuuid) { struct uuidlist *ulp; ulp = TAILQ_FIRST(&ulhead); while (ulp) { /* find next matching uuid */ while (ulp && fsuuid) { if (ulp->ul_davalid && !bcmp(ulp->ul_dauuid, fsuuid, sizeof(ulp->ul_dauuid))) break; if (!bcmp(ulp->ul_uuid, fsuuid, sizeof(ulp->ul_uuid))) break; ulp = TAILQ_NEXT(ulp, ul_list); } if (!ulp) break; /* we're done if fspath ommitted or matches */ if (!fspath || !strcmp(ulp->ul_mntonname, fspath)) break; ulp = TAILQ_NEXT(ulp, ul_list); } return (ulp); } /* * find_exported_fs_by_dirlist() * * Given a list of directories, find the common parent directory and * the exported file system that directory should be located on. */ struct uuidlist * find_exported_fs_by_dirlist(struct dirlist *dirhead) { struct dirlist *dirl; char *path, *p; int cmp, bestlen, len; struct uuidlist *ulp, *bestulp; if (!dirhead) return (NULL); path = strdup(dirhead->dl_dir); dirl = dirhead->dl_next; while (dirl) { cmp = subdir_check(path, dirl->dl_dir); if (cmp >= 0) { /* same or subdir, so skip */ dirl = dirl->dl_next; continue; } p = strrchr(path, '/'); if (p == path) { /* hit root */ p[1] = '\0'; break; } p[0] = '\0'; } DEBUG(4, "find_exported_fs: %s", path); /* * Now search uuid list for best match. * We're looking for the longest mntonname that this path matches. */ bestulp = NULL; bestlen = -1; TAILQ_FOREACH(ulp, &ulhead, ul_list) { if (subdir_check(ulp->ul_mntonname, path) < 0) continue; len = strlen(ulp->ul_mntonname); if (len > bestlen) { bestulp = ulp; bestlen = strlen(ulp->ul_mntonname); } } free(path); DEBUG(4, "find_exported_fs: best exported fs: %s", bestulp ? bestulp->ul_mntonname : ""); return (bestulp); } /* * subdir_check() * * Compares two pathname strings to see if one is a subdir of the other. * Returns: * 1 if second path is a subdir of the first path * 0 if the paths are the same * -1 if the paths have just a substring match * -2 if the paths do not match at all * -3 if second path could not be a subdir of the first path (due to length) */ int subdir_check(char *s1, char *s2) { int len1, len2, rv; len1 = strlen(s1); len2 = strlen(s2); if (len1 > len2) rv = -3; else if (strncmp(s1, s2, len1)) rv = -2; else if (len1 == len2) rv = 0; else if ((s2[len1] == '/') || (len1 == 1)) rv = 1; else rv = -1; DEBUG(4, "subdir_check: %s %s %d", s1, s2, rv); return rv; } char *line = NULL; uint32_t linesize = 0; FILE *exp_file; uint32_t linenum, entrylines; /* * Get the export list */ int get_exportlist(void) { struct expfs *xf, *xf2, *xf3; struct grouplist ngrp, *grp, *tgrp, tmpgrp; struct expdir *xd, *xd2, *xd3; struct expidlist *xid; struct dirlist *dirhead, *dirl, *dirl2; struct namelisttqh names, netgroups; struct namelist *nl; struct host *ht, *ht2, *ht3; struct hosttqh htfree; struct statfs fsb; struct xucred anon; struct nfs_sec secflavs; char *cp, *endcp, *name, *word, *hst, *usr, *dom, savedc, *savedcp, *subdir, *mntonname, *word2; int len, dlen, hostcount, badhostcount, exflags, got_nondir, netgrp, cmp, show; char fspath[MAXPATHLEN]; u_char uuid[16], fsuuid[16]; struct uuidlist *ulp, *bestulp; char buf[64], buf2[64]; int error, opt_flags, need_export, saved_errors; lock_exports(); /* * First, tag all existing export/group structures for deletion */ TAILQ_FOREACH(xf, &xfshead, xf_next) { TAILQ_FOREACH(xd, &xf->xf_dirl, xd_next) { xd->xd_iflags &= ~(OP_ADD|OP_OFFLINE|OP_ONLINE); xd->xd_ssec.count = 0; xd->xd_oflags = xd->xd_flags; xd->xd_ocred = xd->xd_cred; xd->xd_osec = xd->xd_sec; xd->xd_flags |= OP_DEL; TAILQ_FOREACH(ht, &xd->xd_hosts, ht_next) ht->ht_flags |= OP_DEL; TAILQ_FOREACH(xd2, &xd->xd_mountdirs, xd_next) { xd2->xd_flags |= OP_DEL; TAILQ_FOREACH(ht, &xd2->xd_hosts, ht_next) ht->ht_flags |= OP_DEL; } } } uuidlist_clearexport(); if (xpaths) { free_dirlist(xpaths); xpaths = NULL; } xpaths_complete = 1; TAILQ_INIT(&names); TAILQ_INIT(&netgroups); hostnamecount = hostnamegoodcount = 0; missingexportcount = 0; export_errors = 0; linenum = 0; if ((exp_file = fopen(exportsfilepath, "r")) == NULL) { log(LOG_WARNING, "Can't open %s", exportsfilepath); export_errors = 1; goto exports_read; } /* * Read in the exports and build the list, calling nfssvc(NFSSVC_EXPORT) * as we go along to push the NEW export rules into the kernel. */ dirhead = NULL; while (get_export_entry()) { /* * Create new exports list entry */ DEBUG(1, "---> Got line: %s", line); /* * Set defaults. */ saved_errors = export_errors; hostcount = badhostcount = 0; anon = def_anon; exflags = 0; opt_flags = 0; got_nondir = 0; xf = NULL; ulp = NULL; fspath[0] = '\0'; bzero(fsuuid, sizeof(fsuuid)); /* init group list and net group */ tgrp = NULL; bzero(&ngrp, sizeof(ngrp)); /* init default security flavor */ bzero(&secflavs, sizeof(secflavs)); secflavs.flavors[0] = RPCAUTH_SYS; secflavs.count = 1; /* * process all the fields in this line * (we loop until nextfield finds nothing) */ cp = line; nextfield(&cp, &endcp); if (*cp == '#') goto nextline; while (endcp > cp) { DEBUG(2, "got field: %.*s", endcp-cp, cp); if (*cp == '-') { /* * looks like we have some options */ if (dirhead == NULL) { export_error(LOG_ERR, "got options with no exported directory: %s", cp); export_error_cleanup(xf); goto nextline; } DEBUG(3, "processing option: %.*s", endcp-cp, cp); got_nondir = 1; savedcp = cp; if (do_opt(&cp, &endcp, &ngrp, &hostcount, &opt_flags, &exflags, &anon, &secflavs, fspath, fsuuid)) { export_error(LOG_ERR, "error processing options: %s", savedcp); export_error_cleanup(xf); goto nextline; } } else if ((*cp == '/') || ((*cp == '\'' || *cp == '"') && (*(cp+1) == '/'))) { /* * looks like we have a pathname */ DEBUG(2, "processing pathname: %.*s", endcp-cp, cp); word = clean_pathname(cp); DEBUG(3, " cleaned pathname: %s", word); if (word == NULL) { export_error(LOG_ERR, "error processing pathname (out of memory)"); export_error_cleanup(xf); goto nextline; } if (got_nondir) { export_error(LOG_ERR, "Directories must be first: %s", word); export_error_cleanup(xf); free(word); goto nextline; } if (strlen(word) > RPCMNT_NAMELEN) { export_error(LOG_ERR, "pathname too long (%d > %d): %s", strlen(word), RPCMNT_NAMELEN, word); export_error_cleanup(xf); free(word); goto nextline; } /* Add path to this global exported path list */ word2 = strdup(word); if ((error = add_dir(&xpaths, word2))) free(word2); if (error == ENOMEM) { log(LOG_WARNING, "Can't allocate memory to add export path: %s", word); xpaths_complete = 0; } /* Add path to this line's directory list */ error = add_dir(&dirhead, word); if (error == EEXIST) { export_error(LOG_WARNING, "duplicate directory: %s", word); free(word); } else if (error == ENOMEM) { export_error(LOG_ERR, "Can't allocate memory to add directory: %s", word); export_error_cleanup(xf); free(word); goto nextline; } } else { /* * looks like we have a host/netgroup */ savedc = *endcp; *endcp = '\0'; DEBUG(2, "got host/netgroup: %s", cp); got_nondir = 1; if (dirhead == NULL) { export_error(LOG_ERR, "got host/group with no directory?: %s", cp); export_error_cleanup(xf); goto nextline; } /* add it to the name list */ error = add_name(&names, cp); if (error == ENOMEM) { export_error(LOG_ERR, "Can't allocate memory to add host/group: %s", cp); export_error_cleanup(xf); goto nextline; } /* iterate name list until it's empty (first name gets GF_SHOW) */ show = 1; while ((nl = TAILQ_FIRST(&names))) { TAILQ_REMOVE(&names, nl, nl_next); name = nl->nl_name; free(nl); /* check if name is a netgroup */ setnetgrent(name); netgrp = getnetgrent(&hst, &usr, &dom); if (netgrp) { DEBUG(3, "got netgroup: %s", name); error = add_name(&netgroups, name); if (error == ENOMEM) { export_error(LOG_ERR, "Can't allocate memory to add host/group: %s", cp); export_error_cleanup(xf); endnetgrent(); free(name); goto nextline; } if (show) { /* add an entry for the netgroup (w/GF_SHOW) */ DEBUG(3, "add netgroup w/show: %s", name); show = 0; bzero(&tmpgrp, sizeof(tmpgrp)); tmpgrp.gr_type = GT_NETGROUP; tmpgrp.gr_flags = GF_SHOW; tmpgrp.gr_u.gt_netgroup = strdup(name); if (!tmpgrp.gr_u.gt_netgroup || !(grp = get_grp(&tmpgrp))) { export_error(LOG_ERR, "Can't allocate memory to add netgroup - %s", name); export_error_cleanup(xf); endnetgrent(); free(name); goto nextline; } else if (!add_grp(&tgrp, grp)) { DEBUG(3, "duplicate netgroup: %s", name); free_grp(grp); } } if (error == -1) { /* already in the netgroup list, skip it */ DEBUG(3, "netgroup already in netgroup list: %s", name); endnetgrent(); free(name); continue; } /* enumerate the netgroup, adding each name to the name list */ do { DEBUG(3, "add netgroup member: %s", hst); error = add_name(&names, hst); if (error == ENOMEM) { export_error(LOG_ERR, "Can't allocate memory to add netgroup:host - %s", name, hst); export_error_cleanup(xf); endnetgrent(); free(name); goto nextline; } } while (getnetgrent(&hst, &usr, &dom)); endnetgrent(); free(name); continue; } endnetgrent(); /* not a netgroup, add a host/net entry */ bzero(&tmpgrp, sizeof(tmpgrp)); if (get_host_addresses(name, &tmpgrp)) { export_error(LOG_WARNING, "couldn't get address for host: %s", name); badhostcount++; } else { DEBUG(3, "got host: %s", name); if (show) { show = 0; tmpgrp.gr_flags |= GF_SHOW; } grp = get_grp(&tmpgrp); if (!grp) { export_error(LOG_ERR, "Can't allocate memory to add host - %s", name); } else if (!add_grp(&tgrp, grp)) { DEBUG(3, "duplicate host: %s", name); free_grp(grp); } else { hostcount++; } } free(name); } *endcp = savedc; } cp = endcp; nextfield(&cp, &endcp); } /* * Done parsing through export entry fields. */ /* check options and hosts */ if (!(opt_flags & (OP_MAPROOT|OP_MAPALL))) { /* If no mapping option specified, map root by default */ exflags |= NX_MAPROOT; opt_flags |= OP_MAPROOT; } if (check_options(opt_flags)) { export_error(LOG_ERR, "bad export options"); export_error_cleanup(xf); goto nextline; } if (!hostcount) { if (badhostcount) { export_error(LOG_ERR, "no valid hosts found for export"); export_error_cleanup(xf); goto nextline; } DEBUG(1, "default export"); } else if (opt_flags & OP_NET) { if (tgrp) { /* * Don't allow a network export coincide with a list of * host(s) on the same line. */ export_error(LOG_ERR, "can't specify both network and hosts on same line"); export_error_cleanup(xf); goto nextline; } ngrp.gr_flags |= GF_SHOW; tgrp = get_grp(&ngrp); if (!tgrp) { export_error(LOG_ERR, "Can't allocate memory to add network - %s", grp_name(&ngrp)); export_error_cleanup(xf); goto nextline; } ngrp.gr_type = GT_NULL; } /* check directory list */ if (dirhead == NULL) { /* sanity check */ export_error(LOG_ERR, "no directories for export entry?"); export_error_cleanup(xf); goto nextline; } mntonname = NULL; if (opt_flags & (OP_FSPATH|OP_FSUUID)) bestulp = find_exported_fs_by_path_and_uuid( (opt_flags & OP_FSPATH) ? fspath : NULL, (opt_flags & OP_FSUUID) ? fsuuid : NULL); else bestulp = find_exported_fs_by_dirlist(dirhead); dirl = dirhead; /* Look for an exported directory that passes check_dirpath() */ while (dirl && !check_dirpath(dirl->dl_dir)) { export_error(LOG_WARNING, "path contains non-directory or non-existent components: %s", dirl->dl_dir); /* skip subdirectories */ dirl2 = dirl->dl_next; while (dirl2 && (subdir_check(dirl->dl_dir, dirl2->dl_dir) == 1)) dirl2 = dirl2->dl_next; dirl = dirl2; } if (!dirl) { if (!bestulp) { export_error(LOG_ERR, "no usable directories in export entry and no fallback"); export_error_cleanup(xf); goto nextline; } export_error(LOG_WARNING, "no usable directories in export entry"); goto prepare_offline_export; } if (statfs(dirl->dl_dir, &fsb) < 0) { export_error(LOG_ERR, "statfs failed (%s (%d)) for path: %s", strerror(errno), errno, dirl->dl_dir); export_error_cleanup(xf); goto nextline; } if ((opt_flags & OP_FSPATH) && strcmp(fsb.f_mntonname, fspath)) { /* fspath doesn't match export fs path? */ if (!bestulp) { export_error(LOG_ERR, "file system path (%s) does not match fspath (%s) and no fallback", fsb.f_mntonname, fspath); export_error_cleanup(xf); goto nextline; } export_error(LOG_WARNING, "file system path (%s) does not match fspath (%s)", fsb.f_mntonname, fspath); goto prepare_offline_export; } if (bestulp && (subdir_check(fsb.f_mntonname, bestulp->ul_mntonname) > 0)) { export_error(LOG_WARNING, "Exported file system (%s) doesn't match best guess (%s).", fsb.f_mntonname, bestulp->ul_mntonname); if (!(opt_flags & (OP_FSUUID|OP_FSPATH))) export_error(LOG_WARNING, "Suggest using fspath=/path and/or fsuuid=uuid to disambiguate."); goto prepare_offline_export; } if (!(ulp = get_uuid(&fsb, uuid))) { export_error(LOG_ERR, "couldn't get UUID for volume: %s", fsb.f_mntonname); export_error_cleanup(xf); goto nextline; } if ((opt_flags & OP_FSUUID) && bcmp(uuid, fsuuid, sizeof(fsuuid))) { /* fsuuid doesn't match export fs uuid? */ if (!bestulp) { export_error(LOG_ERR, "file system UUID (%s) does not match fsuuid (%s) and no fallback", uuidstring(uuid, buf), uuidstring(fsuuid, buf2)); export_error_cleanup(xf); goto nextline; } export_error(LOG_WARNING, "file system UUID (%s) does not match fsuuid (%s)", uuidstring(uuid, buf), uuidstring(fsuuid, buf2)); goto prepare_offline_export; } if (bestulp && (opt_flags & OP_MISSING)) { prepare_offline_export: missingexportcount++; export_error(LOG_WARNING, "using fallback (marked offline): %s", bestulp->ul_mntonname); exflags |= NX_OFFLINE; opt_flags |= OP_MISSING; ulp = bestulp; mntonname = ulp->ul_mntonname; bcopy(ulp->ul_uuid, uuid, sizeof(uuid)); } else { mntonname = fsb.f_mntonname; } /* See if this directory is already in the export list. */ xf = ex_search(uuid); if (xf == NULL) { xf = get_expfs(); if (xf) xf->xf_fsdir = strdup(mntonname); if (!xf || !xf->xf_fsdir) { export_error(LOG_ERR, "Can't allocate memory to export volume: %s", mntonname); export_error_cleanup(xf); goto nextline; } bcopy(uuid, xf->xf_uuid, sizeof(uuid)); xf->xf_fsid = ulp->ul_fsid; DEBUG(2, "New expfs uuid=%s", uuidstring(uuid, buf)); } else { DEBUG(2, "Found expfs uuid=%s", uuidstring(uuid, buf)); } /* verify the rest of the directories in the list are kosher */ if (dirl) dirl = dirl->dl_next; for (; dirl; dirl = dirl->dl_next) { DEBUG(2, "dir: %s", dirl->dl_dir); if (!check_dirpath(dirl->dl_dir)) { export_error(LOG_WARNING, "path contains non-directory or non-existent components: %s", dirl->dl_dir); continue; } if (statfs(dirl->dl_dir, &fsb) < 0) { export_error(LOG_WARNING, "statfs failed (%s (%d)) for path: %s", strerror(errno), errno, dirl->dl_dir); continue; } if (strcmp(xf->xf_fsdir, fsb.f_mntonname)) { export_error(LOG_WARNING, "Volume mismatch for: %s\ndirectories must be on same volume", dirl->dl_dir); continue; } } /* * Done processing exports line fields. */ /* * Walk the dirlist. * Verify the next dir is (or can be) an exported directory. * Check subsequent dirs to see if they are mount subdirs of that dir. */ dirl = dirhead; while (dirl) { DEBUG(2, "dir: %s", dirl->dl_dir); /* * Check for nesting conflicts with any existing entries. * Note we skip any entries that are NOT marked OP_ADD because * we don't care about directories that are tagged for deletion. */ TAILQ_FOREACH(xd2, &xf->xf_dirl, xd_next) { if ((xd2->xd_iflags & OP_ADD) && ((subdir_check(xd2->xd_dir, dirl->dl_dir) == 1) || (subdir_check(dirl->dl_dir, xd2->xd_dir) == 1))) { export_error(LOG_ERR, "%s conflicts with existing export %s", dirl->dl_dir, xd2->xd_dir); export_error_cleanup(xf); goto nextline; } } /* * Scan exported file system for a matching exported directory * or at least the insertion point of a new one. */ len = strlen(dirl->dl_dir); xd3 = NULL; cmp = 1; TAILQ_FOREACH(xd2, &xf->xf_dirl, xd_next) { dlen = strlen(xd2->xd_dir); cmp = strncmp(dirl->dl_dir, xd2->xd_dir, dlen); DEBUG(3, " %s compare %s %d", dirl->dl_dir, xd2->xd_dir, cmp); if (!cmp) { if (len == dlen) /* found an exact match */ break; /* dirl was actually longer than xd2 */ cmp = 1; } if (cmp > 0) break; xd3 = xd2; } if (!cmp) { xd = xd2; DEBUG(2, " %s xd is %s", dirl->dl_dir, xd->xd_dir); } else { /* go ahead and create a new expdir structure */ if (strncmp(dirl->dl_dir, mntonname, strlen(mntonname))) { export_error(LOG_ERR, "exported dir/fs mismatch: %s %s", dirl->dl_dir, mntonname); export_error_cleanup(xf); goto nextline; } /* first, get export path and ID */ /* point subdir beyond mount path string */ subdir = dirl->dl_dir + strlen(mntonname); /* skip "/" between mount and subdir */ while (*subdir && (*subdir == '/')) subdir++; xid = get_export_id(ulp, subdir); if (!xid) { export_error(LOG_ERR, "unable to get export ID for %s", dirl->dl_dir); export_error_cleanup(xf); goto nextline; } xd = get_expdir(); if (xd) xd->xd_dir = strdup(dirl->dl_dir); if (!xd || !xd->xd_dir) { if (xd) free_expdir(xd); export_error(LOG_ERR, "can't allocate memory for export %s", dirl->dl_dir); export_error_cleanup(xf); goto nextline; } xd->xd_xid = xid; DEBUG(2, " %s new xd", xd->xd_dir); } /* preflight the addition of these new export options */ if (hang_options_setup(xd, opt_flags, &anon, tgrp, &secflavs, &need_export)) { export_error(LOG_ERR, "export option conflict for %s", xd->xd_dir); /* XXX what to do about already successful exports? */ hang_options_cleanup(xd); if (cmp) free_expdir(xd); export_error_cleanup(xf); goto nextline; } /* * Send list of hosts to do_export for pushing the exports into * the kernel (unless checkexports and the export is missing). */ if (need_export && !(checkexports && (opt_flags & OP_MISSING))) { int expcmd = checkexports ? NXA_CHECK : NXA_REPLACE; if (do_export(expcmd, xf, xd, tgrp, exflags, &anon, &secflavs)) { if ((errno == ENOTSUP) || (errno == EISDIR)) { /* if ENOTSUP report lack of NFS export support */ /* if EISDIR report lack of extended readdir support */ export_error(LOG_ERR, "kernel export registration failed: " "NFS exporting not supported by fstype \"%s\" (%s)", statfs(xf->xf_fsdir, &fsb) ? "?" : fsb.f_fstypename, (errno == EISDIR) ? "readdir" : "fh"); } else { export_error(LOG_ERR, "kernel export registration failed"); } hang_options_cleanup(xd); if (cmp) free_expdir(xd); export_error_cleanup(xf); goto nextline; } /* Success. Update the data structures. */ DEBUG(1, "kernel export registered for %s/%s", xf->xf_fsdir, xd->xd_xid->xid_path); } else { DEBUG(2, "kernel export already registered for %s/%s", xf->xf_fsdir, xd->xd_xid->xid_path); } /* add mount subdirectories of this directory */ dirl2 = dirl->dl_next; while (dirl2) { if (subdir_check(dirl->dl_dir, dirl2->dl_dir) != 1) break; error = hang_options_mountdir(xd, dirl2->dl_dir, opt_flags, tgrp, &secflavs); if (error == EEXIST) { export_error(LOG_WARNING, "mount subdirectory export option conflict for %s", dirl2->dl_dir); } else if (error == ENOMEM) { export_error(LOG_WARNING, "unable to add mount subdirectory for %s, %s", xd->xd_dir, dirl2->dl_dir); } dirl2 = dirl2->dl_next; } dirl = dirl2; /* finalize export option additions */ hang_options_finalize(xd); /* mark that we've added exports to this xd */ xd->xd_iflags |= OP_ADD; if (cmp) { /* add new expdir to xf */ if (xd3) { TAILQ_INSERT_AFTER(&xf->xf_dirl, xd3, xd, xd_next); } else { TAILQ_INSERT_HEAD(&xf->xf_dirl, xd, xd_next); } } } if ((xf->xf_flag & XF_LINKED) == 0) { /* Insert in the list in alphabetical order. */ xf3 = NULL; TAILQ_FOREACH(xf2, &xfshead, xf_next) { if (strcmp(xf->xf_fsdir, xf2->xf_fsdir) < 0) break; xf3 = xf2; } if (xf3) { TAILQ_INSERT_AFTER(&xfshead, xf3, xf, xf_next); } else { TAILQ_INSERT_HEAD(&xfshead, xf, xf_next); } xf->xf_flag |= XF_LINKED; } if (export_errors == saved_errors) { /* no errors, clear any previous errors for this entry */ if (clear_export_errors(linenum)) log(LOG_WARNING, "exports:%d: export entry OK (previous errors cleared)", linenum); } nextline: if (!TAILQ_EMPTY(&netgroups)) free_namelist(&netgroups); if (!TAILQ_EMPTY(&names)) free_namelist(&names); if (dirhead) { free_dirlist(dirhead); dirhead = NULL; } /* release groups */ switch (ngrp.gr_type) { case GT_NET: if (ngrp.gr_u.gt_net.nt_name) free(ngrp.gr_u.gt_net.nt_name); break; } while (tgrp) { grp = tgrp; tgrp = tgrp->gr_next; grp->gr_flags &= ~GF_SHOW; free_grp(grp); } } fclose(exp_file); if (config.verbose >= 5) { DEBUG(3, "========> get_exportlist() CURRENT EXPORTS UPDATED:"); dump_exports(); } exports_read: /* * Now, find all existing structures still tagged for deletion. * For each tagged group found, call nfssvc(NXA_DELETE) to delete * the exports for the addresses that haven't had new/replacement * exports registered. We simply scan the current exports for * an untagged match for each address. If an exported directory * loses all of its exports, we delete that expdir. */ xf = TAILQ_FIRST(&xfshead); while (xf && !checkexports) { xd = TAILQ_FIRST(&xf->xf_dirl); while (xd) { /* * First check the list of mountdirs. Since the kernel * is not aware of these and they are not registered, * we merely need to delete the data structures. */ TAILQ_FOREACH_SAFE(xd2, &xd->xd_mountdirs, xd_next, xd3) { TAILQ_FOREACH_SAFE(ht, &xd2->xd_hosts, ht_next, ht2) if (ht->ht_flags & OP_DEL) { TAILQ_REMOVE(&xd2->xd_hosts, ht, ht_next); free_host(ht); } if (xd2->xd_flags & OP_DEL) xd2->xd_flags = xd2->xd_oflags = 0; if (!(xd2->xd_flags & OP_DEFEXP) && TAILQ_EMPTY(&xd2->xd_hosts)) { /* No more exports here, delete */ TAILQ_REMOVE(&xd->xd_mountdirs, xd2, xd_next); free_expdir(xd2); } } /* * Now scan the xd_hosts list for hosts that are still * tagged for deletion and move those to the htfree list. */ TAILQ_INIT(&htfree); TAILQ_FOREACH_SAFE(ht, &xd->xd_hosts, ht_next, ht2) if (ht->ht_flags & OP_DEL) { TAILQ_REMOVE(&xd->xd_hosts, ht, ht_next); TAILQ_INSERT_TAIL(&htfree, ht, ht_next); } /* * Go through htfree and find the groups/addresses * that are no longer being exported to and place * those groups/addresses in the list tgrp. * The hosts and groups/addresses that have been * replaced with newer exports (and thus don't require * deletion from the kernel) will be freed as we go. */ tgrp = NULL; TAILQ_FOREACH_SAFE(ht, &htfree, ht_next, ht2) { grp = ht->ht_grp; if (grp->gr_type == GT_NETGROUP) { /* * netgroup entries should just be freed up now with the host. * Don't bother trying to match up because they won't. */ } else if (!find_group_address_match_in_host_list(&xd->xd_hosts, grp)) { /* no conflicts, so we can safely delete these */ /* steal the grp from the host */ ht->ht_grp = NULL; if (!add_grp(&tgrp, grp)) { /* shouldn't happen... */ log(LOG_ERR, "failure to queue group for export deletion"); /* ... but try to recover anyway */ grp->gr_next = tgrp; tgrp = grp; } } else if (grp->gr_type == GT_HOST) { struct addrinfo *a1, *a2, *prea3, *a3, atmp, *anexttmp; /* * Some or all of the addresses are still exported. * Find any addresses whose exports haven't been replaced. * a3 points to the location of the next address we will * want to delete the export for. a1 walks the array * and a3 follows along/behind essentially compacting the * array of addresses into only those that we want to * delete exports for. The effect is that addresses which * are still exported will be "squeezed" out of the array. */ a1 = grp->gr_u.gt_hostinfo.h_ailist; prea3 = NULL; a3 = a1; while (a1) { /* scan exports host list for GT_HOSTs w/matching address */ TAILQ_FOREACH(ht3, &xd->xd_hosts, ht_next) { if (ht3->ht_grp->gr_type != GT_HOST) continue; a2 = ht3->ht_grp->gr_u.gt_hostinfo.h_ailist; while (a2 && addrinfo_cmp(a1, a2)) a2 = a2->ai_next; if (a2) /* we found a match */ break; } if (!ht3) { /* didn't find address, so "add" it to the array */ if (a3 != a1) { /* switch all elements except ai_next */ atmp = *a3; anexttmp = a3->ai_next; *a3 = *a1; a3->ai_next = anexttmp; atmp.ai_next = a1->ai_next; *a1 = atmp; } prea3 = a3; a3 = a3->ai_next; } a1 = a1->ai_next; } if (a3 == grp->gr_u.gt_hostinfo.h_ailist) { /* a3 hasn't moved, so we know that all of */ /* the addresses are being exported again */ /* so we shouldn't delete any of the exports */ } else { /* some of the addresses are being exported again */ /* we've compacted the list of addresses that aren't */ /* and here we will free up the rest of them */ if (prea3) prea3->ai_next = NULL; freeaddrinfo(a3); /* steal the grp from the host */ ht->ht_grp = NULL; if (!add_grp(&tgrp, grp)) { /* shouldn't happen... */ log(LOG_ERR, "failure to queue group for export deletion"); /* ... but try to recover anyway */ grp->gr_next = tgrp; tgrp = grp; } } } TAILQ_REMOVE(&htfree, ht, ht_next); free_host(ht); } if ((config.verbose >= 3) && tgrp) { struct grouplist *g; DEBUG(1, "deleting export for %s/%s", xf->xf_fsdir, xd->xd_xid->xid_path); g = tgrp; while (g) { DEBUG(3, " %p %d %s %s", g, g->gr_refcnt, grp_name(g), grp_addr(g)); g = g->gr_next; } } if (tgrp && do_export(NXA_DELETE, xf, xd, tgrp, 0, NULL, NULL)) { log(LOG_ERR, "kernel export unregistration failed for %s, %s%s", xd->xd_dir, grp_name(tgrp), tgrp->gr_next ? ", ..." : ""); } while (tgrp) { grp = tgrp; tgrp = tgrp->gr_next; free_grp(grp); } if ((xd->xd_flags & (OP_DEL|OP_DEFEXP)) == (OP_DEL|OP_DEFEXP)) { DEBUG(1, "deleting default export for %s/%s", xf->xf_fsdir, xd->xd_xid->xid_path); /* we need to delete this default export */ xd->xd_flags = xd->xd_oflags = 0; if (do_export(NXA_DELETE, xf, xd, NULL, 0, NULL, NULL)) { log(LOG_ERR, "kernel export unregistration failed for %s," " default export", xd->xd_dir); } } xd3 = TAILQ_NEXT(xd, xd_next); if (!(xd->xd_flags & OP_DEFEXP) && TAILQ_EMPTY(&xd->xd_hosts)) { TAILQ_REMOVE(&xf->xf_dirl, xd, xd_next); free_expdir(xd); } else { xd->xd_iflags &= ~OP_ADD; xd->xd_flags &= ~OP_DEL; } xd = xd3; } xf2 = TAILQ_NEXT(xf, xf_next); if (TAILQ_EMPTY(&xf->xf_dirl)) { /* No more exports here, delete */ TAILQ_REMOVE(&xfshead, xf, xf_next); free_expfs(xf); } xf = xf2; } if (config.verbose >= 4) { DEBUG(2, "========> get_exportlist() NEW EXPORTS LIST:"); dump_exports(); } if (!checkexports) uuidlist_save(); /* * If we appear to be having problems resolving host names on startup, * then we'll want to automatically recheck exports for a while. * * First time through, we make sure to set recheckexports_until. * If we have problems then set the recheck timer - otherwise disable it. * On subsequent export checks, turn off the recheck timer once we no * longer need it or the timer expires. */ if (!checkexports && (recheckexports_until == 0)) { /* first time through... */ /* did we have any host names and were any of them problematic? */ if (hostnamegoodcount != hostnamecount) { log(LOG_WARNING, "There seem to be problems resolving host names..."); log(LOG_WARNING, "...will periodically recheck exports for a while."); recheckexports_until = time(NULL) + RECHECKEXPORTS_TIMEOUT; /* set the recheck timer */ } else { recheckexports_until = -1; /* turn it off */ } } else if (recheckexports_until > 0) { /* if we don't need to recheck any more, turn it off */ if (hostnamegoodcount == hostnamecount) { recheckexports_until = -1; } else if (recheckexports_until < time(NULL)) { log(LOG_WARNING, "Giving up on automatic rechecking of exports."); recheckexports_until = -1; } } /* should we be rechecking exports? */ if (!checkexports && ((recheckexports_until > 0) || missingexportcount)) recheckexports = 1; else recheckexports = 0; unlock_exports(); return (export_errors); } /* * Allocate an exported file system structure */ struct expfs * get_expfs(void) { struct expfs *xf; xf = malloc(sizeof(*xf)); if (xf == NULL) return (NULL); memset(xf, 0, sizeof(*xf)); TAILQ_INIT(&xf->xf_dirl); return (xf); } /* * Allocate an exported directory structure */ struct expdir * get_expdir(void) { struct expdir *xd; xd = malloc(sizeof(*xd)); if (xd == NULL) return (NULL); memset(xd, 0, sizeof(*xd)); TAILQ_INIT(&xd->xd_hosts); TAILQ_INIT(&xd->xd_mountdirs); return (xd); } /* * Return the "name" of the given group */ static char unknown_group[] = "unknown group"; char * grp_name(struct grouplist *grp) { if (grp->gr_type == GT_NETGROUP) return (grp->gr_u.gt_netgroup); if (grp->gr_type == GT_NET) return (grp->gr_u.gt_net.nt_name); if (grp->gr_type == GT_HOST) return (grp->gr_u.gt_hostinfo.h_name); return (unknown_group); } /* * Return a string (in a static buffer) for the (first) address of the given group. */ static char grpaddrbuf[MAXPATHLEN]; const char * grp_addr(struct grouplist *grp) { struct addrinfo *ai; void *sinaddr; const char *s = NULL; if (grp->gr_type == GT_HOST) { if ((ai = grp->gr_u.gt_hostinfo.h_ailist)) { sinaddr = (ai->ai_family == AF_INET) ? (void*)&((struct sockaddr_in*) AOK ai->ai_addr)->sin_addr : (void*)&((struct sockaddr_in6*) AOK ai->ai_addr)->sin6_addr; if (inet_ntop(ai->ai_family, sinaddr, grpaddrbuf, sizeof(grpaddrbuf))) s = grpaddrbuf; } } else if (grp->gr_type == GT_NET) { sinaddr = (grp->gr_u.gt_net.nt_family == AF_INET) ? (void*)&grp->gr_u.gt_net.nt_net : (void*)&grp->gr_u.gt_net.nt_net6; if (inet_ntop(grp->gr_u.gt_net.nt_family, sinaddr, grpaddrbuf, sizeof(grpaddrbuf))) s = grpaddrbuf; } else if (grp->gr_type == GT_NETGROUP) { s = ""; } if (!s) s = "???"; return (s); } int addrinfo_cmp(struct addrinfo *a1, struct addrinfo *a2) { if (a1->ai_family != a2->ai_family) return (a1->ai_family - a2->ai_family); if (a1->ai_addrlen != a2->ai_addrlen) return (a1->ai_addrlen - a2->ai_addrlen); return bcmp(a1->ai_addr, a2->ai_addr, a1->ai_addrlen); } /* * compare two group list elements */ int grpcmp(struct grouplist *g1, struct grouplist *g2) { struct netmsk *n1, *n2; struct addrinfo *a1, *a2; int rv; rv = g1->gr_type - g2->gr_type; if (rv) return (rv); switch (g1->gr_type) { case GT_NETGROUP: rv = strcmp(g1->gr_u.gt_netgroup, g2->gr_u.gt_netgroup); break; case GT_NET: n1 = &g1->gr_u.gt_net; n2 = &g2->gr_u.gt_net; rv = strcmp(n1->nt_name, n2->nt_name); if (rv) break; rv = n1->nt_family - n2->nt_family; if (rv) break; if (n1->nt_family == AF_INET) { rv = bcmp(&n1->nt_net, &n2->nt_net, sizeof(n1->nt_net)); if (rv) break; rv = bcmp(&n1->nt_mask, &n2->nt_mask, sizeof(n1->nt_mask)); } else if (n1->nt_family == AF_INET6) { rv = bcmp(&n1->nt_net6, &n2->nt_net6, sizeof(n1->nt_net6)); if (rv) break; rv = bcmp(&n1->nt_mask6, &n2->nt_mask6, sizeof(n1->nt_mask6)); } break; case GT_HOST: rv = strcmp(g1->gr_u.gt_hostinfo.h_name, g2->gr_u.gt_hostinfo.h_name); if (rv) break; a1 = g1->gr_u.gt_hostinfo.h_ailist; a2 = g2->gr_u.gt_hostinfo.h_ailist; while (a1 && a2) { rv = addrinfo_cmp(a1, a2); if (rv) break; a1 = a1->ai_next; a2 = a2->ai_next; } if (!rv && !(!a1 && !a2)) { if (a1) rv = 1; else rv = -1; } break; } return (rv); } /* * Return a group in the group cache that matches the given group. * If the group isn't yet in the cache, a new group will be allocated, * populated with the info from the given group, and added to the cache. * In any event, any memory referred to within grptmp->gr_u has been * either used in a new group cache entry or freed. */ struct grouplist * get_grp(struct grouplist *grptmp) { struct grouplist *g, *g2; int cmp = 1, clean_up_gr_u = 1; if (config.verbose >= 7) { DEBUG(5, "get_grp: %s %s", grp_name(grptmp), grp_addr(grptmp)); g = grpcache; while (g) { DEBUG(6, "grpcache: %p %d %s %s", g, g->gr_refcnt, grp_name(g), grp_addr(g)); g = g->gr_cache; } } g2 = NULL; g = grpcache; while (g && ((cmp = grpcmp(grptmp, g)) > 0)) { g2 = g; g = g->gr_cache; } if (!cmp) { g->gr_refcnt++; if (config.verbose >= 7) DEBUG(5, "get_grp: found %p %d %s %s", g, g->gr_refcnt, grp_name(g), grp_addr(g)); g->gr_flags |= grptmp->gr_flags; goto out; } g = malloc(sizeof(*g)); if (g == NULL) goto out; memset(g, 0, sizeof(*g)); g->gr_refcnt = 1; g->gr_type = grptmp->gr_type; g->gr_flags = grptmp->gr_flags; g->gr_u = grptmp->gr_u; /* memory allocations in *grptmp->gr_u are now owned by g->gr_u */ clean_up_gr_u = 0; if (g2) { g->gr_cache = g2->gr_cache; g2->gr_cache = g; } else { g->gr_cache = grpcache; grpcache = g; } if (config.verbose >= 7) { DEBUG(5, "get_grp: ----- NEW %p %d %s %s", g, g->gr_refcnt, grp_name(g), grp_addr(g)); g2 = grpcache; while (g2) { DEBUG(6, "grpcache: %p %d %s %s", g2, g2->gr_refcnt, grp_name(g2), grp_addr(g2)); g2 = g2->gr_cache; } } out: if (clean_up_gr_u) { /* free up the contents of grptmp->gr_u */ switch (grptmp->gr_type) { case GT_HOST: if (grptmp->gr_u.gt_hostinfo.h_ailist) freeaddrinfo(grptmp->gr_u.gt_hostinfo.h_ailist); if (grptmp->gr_u.gt_hostinfo.h_name) free(grptmp->gr_u.gt_hostinfo.h_name); break; case GT_NET: if (grptmp->gr_u.gt_net.nt_name) free(grptmp->gr_u.gt_net.nt_name); break; case GT_NETGROUP: if (grptmp->gr_u.gt_netgroup) free(grptmp->gr_u.gt_netgroup); break; } } return (g); } /* * Free up a group list. */ void free_grp(struct grouplist *grp) { struct grouplist **g; /* decrement reference count */ grp->gr_refcnt--; if (config.verbose >= 7) DEBUG(5, "free_grp: %p %d %s %s", grp, grp->gr_refcnt, grp_name(grp), grp_addr(grp)); if (grp->gr_refcnt > 0) return; /* remove group from grpcache list */ g = &grpcache; while (*g && (*g != grp)) g = &(*g)->gr_cache; if (*g) *g = (*g)->gr_cache; /* free up the memory */ if (grp->gr_type == GT_HOST) { if (grp->gr_u.gt_hostinfo.h_ailist) freeaddrinfo(grp->gr_u.gt_hostinfo.h_ailist); if (grp->gr_u.gt_hostinfo.h_name) free(grp->gr_u.gt_hostinfo.h_name); } else if (grp->gr_type == GT_NET) { if (grp->gr_u.gt_net.nt_name) free(grp->gr_u.gt_net.nt_name); } else if (grp->gr_type == GT_NETGROUP) { if (grp->gr_u.gt_netgroup) free(grp->gr_u.gt_netgroup); } free((caddr_t)grp); } /* * insert a group list element into a group list */ int add_grp(struct grouplist **glp, struct grouplist *newg) { struct grouplist *g1, *g2; int cmp = 1; g2 = NULL; g1 = *glp; while (g1 && ((cmp = grpcmp(newg, g1)) > 0)) { g2 = g1; g1 = g1->gr_next; } if (!cmp) { /* already in list, make sure SHOW bit is set */ if (newg->gr_flags & GF_SHOW) g1->gr_flags |= GF_SHOW; return (0); } if (g2) { newg->gr_next = g2->gr_next; g2->gr_next = newg; } else { newg->gr_next = *glp; *glp = newg; } return (1); } /* * insert a host list element into a host list * (identical to add_grp(), but takes a host list and * allows "duplicates" if one is tagged for deletion) */ int add_host(struct hosttqh *head, struct host *newht) { struct host *ht; int cmp = 1; TAILQ_FOREACH(ht, head, ht_next) if ((cmp = grpcmp(newht->ht_grp, ht->ht_grp)) <= 0) break; if (!cmp && !(ht->ht_flags & OP_DEL)) { /* already in list, make sure SHOW bit is set */ if (newht->ht_flags & OP_SHOW) ht->ht_flags |= OP_SHOW; return (0); } if (ht) TAILQ_INSERT_BEFORE(ht, newht, ht_next); else TAILQ_INSERT_TAIL(head, newht, ht_next); return (1); } /* * report/record an export error message */ void export_error(int level, const char *fmt, ...) { struct errlist *elnew, *el, *elp; char *s = NULL; va_list ap; export_errors++; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" va_start(ap, fmt); vasprintf(&s, fmt, ap); va_end(ap); #pragma clang diagnostic pop /* check if we've already logged this error */ LIST_FOREACH(el, &xerrs, el_next) { if (linenum < el->el_linenum) continue; if (linenum > el->el_linenum) { el = NULL; break; } if (!strcmp(el->el_msg, s)) break; } /* log the message if we haven't already */ if (!el) log(level, "exports:%d: %s", linenum, s); /* add this error to the list */ elnew = malloc(sizeof(*elnew)); if (!elnew) { free(s); return; } elnew->el_linenum = linenum; elnew->el_msg = s; elp = NULL; LIST_FOREACH(el, &xerrs, el_next) { if (linenum < el->el_linenum) break; elp = el; } if (elp) LIST_INSERT_AFTER(elp, elnew, el_next); else LIST_INSERT_HEAD(&xerrs, elnew, el_next); } /* * clear export errors on the give line# (or all if line#=0) */ int clear_export_errors(uint32_t linenum) { struct errlist *el, *elnext; int cleared = 0; LIST_FOREACH_SAFE(el, &xerrs, el_next, elnext) { if (linenum) { if (linenum < el->el_linenum) break; if (linenum > el->el_linenum) continue; } cleared = 1; LIST_REMOVE(el, el_next); if (el->el_msg) free(el->el_msg); free(el); } return (cleared); } /* * Clean up upon an error in get_exportlist(). */ void export_error_cleanup(struct expfs *xf) { if (xf && (xf->xf_flag & XF_LINKED) == 0) free_expfs(xf); } /* * Search the export list for a matching fs. */ struct expfs * ex_search(u_char *uuid) { struct expfs *xf; TAILQ_FOREACH(xf, &xfshead, xf_next) { if (!bcmp(xf->xf_uuid, uuid, 16)) return (xf); } return (xf); } /* * add a directory to a dirlist (sorted) * * Note that the list is sorted to place subdirectories * after the entry for their matching parent directory. * This isn't strictly sorted because other directories may * have similar names with characters that sort lower than '/'. * For example: /export /export.test /export/subdir */ int add_dir(struct dirlist **dlpp, char *cp) { struct dirlist *newdl, *dl, *dl2, *dl3, *dlstop; int cplen, dlen, cmp; dlstop = NULL; dl2 = NULL; dl = *dlpp; cplen = strlen(cp); while (dl && (dl != dlstop)) { dlen = strlen(dl->dl_dir); cmp = strncmp(cp, dl->dl_dir, dlen); DEBUG(3, "add_dir: %s compare %s %d", cp, dl->dl_dir, cmp); if (cmp < 0) break; if (cmp == 0) { if (cplen == dlen) /* duplicate */ return (EEXIST); if (cp[dlen] == '/') { /* * Find the next entry that isn't a * subdirectory of this directory so * we know when to stop looking for * the insertion point. */ DEBUG(3, "add_dir: %s compare %s %d subdir match", cp, dl->dl_dir, cmp); dlstop = dl->dl_next; while (dlstop && (subdir_check(dl->dl_dir, dlstop->dl_dir) == 1)) dlstop = dlstop->dl_next; } else { /* * The new dir should go after this directory and * its subdirectories. So, skip subdirs of this dir. */ DEBUG(3, "add_dir: %s compare %s %d partial match", cp, dl->dl_dir, cmp); dl3 = dl; dl2 = dl; dl = dl->dl_next; while (dl && (subdir_check(dl3->dl_dir, dl->dl_dir) == 1)) { dl2 = dl; dl = dl->dl_next; } continue; } } dl2 = dl; dl = dl->dl_next; } if (dl && (dl == dlstop)) DEBUG(3, "add_dir: %s stopped before %s", cp, dlstop->dl_dir); newdl = malloc(sizeof(*dl)); if (newdl == NULL) { log(LOG_ERR, "can't allocate memory to add dir %s", cp); return (ENOMEM); } newdl->dl_dir = cp; if (dl2 == NULL) { newdl->dl_next = *dlpp; *dlpp = newdl; } else { newdl->dl_next = dl; dl2->dl_next = newdl; } if (config.verbose >= 6) { dl = *dlpp; while (dl) { DEBUG(4, "DIRLIST: %s", dl->dl_dir); dl = dl->dl_next; } } return (0); } /* * free up all the elements in a dirlist */ void free_dirlist(struct dirlist *dl) { struct dirlist *dl2; while (dl) { dl2 = dl->dl_next; if (dl->dl_dir) free(dl->dl_dir); free(dl); dl = dl2; } } /* * add a name to a namelist * * returns 0 on success, -1 on duplicate, or error */ int add_name(struct namelisttqh *names, char *name) { struct namelist *nl; TAILQ_FOREACH(nl, names, nl_next) if (!strcmp(nl->nl_name, name)) return (-1); nl = malloc(sizeof(*nl)); if (!nl) return (ENOMEM); nl->nl_name = strdup(name); if (!nl->nl_name) { free(nl); return (ENOMEM); } TAILQ_INSERT_TAIL(names, nl, nl_next); return (0); } /* * free up all the elements in a namelist */ void free_namelist(struct namelisttqh *names) { struct namelist *nl, *nlnext; TAILQ_FOREACH_SAFE(nl, names, nl_next, nlnext) { TAILQ_REMOVE(names, nl, nl_next); if (nl->nl_name) free(nl->nl_name); free(nl); } } /* * find a host list entry that has the same group */ struct host * find_group_match_in_host_list(struct hosttqh *head, struct grouplist *grp) { struct host *ht; TAILQ_FOREACH(ht, head, ht_next) if (!grpcmp(grp, ht->ht_grp)) break; return (ht); } /* * find a host list entry that has the same address */ struct host * find_group_address_match_in_host_list(struct hosttqh *head, struct grouplist *grp) { struct host *ht; struct netmsk *n1, *n2; struct addrinfo *a1, *a2; int i; switch (grp->gr_type) { case GT_HOST: a1 = grp->gr_u.gt_hostinfo.h_ailist; while (a1) { TAILQ_FOREACH(ht, head, ht_next) { if (ht->ht_grp->gr_type != GT_HOST) continue; if (ht->ht_flags & OP_DEL) continue; a2 = ht->ht_grp->gr_u.gt_hostinfo.h_ailist; while (a2) { if (!addrinfo_cmp(a1, a2)) return (ht); a2 = a2->ai_next; } } a1 = a1->ai_next; } break; case GT_NET: n1 = &grp->gr_u.gt_net; TAILQ_FOREACH(ht, head, ht_next) { if (ht->ht_grp->gr_type != GT_NET) continue; if (ht->ht_flags & OP_DEL) continue; n2 = &ht->ht_grp->gr_u.gt_net; if (n1->nt_family != n2->nt_family) continue; if (n1->nt_family == AF_INET) { in_addr_t ina1, ina2; ina1 = n1->nt_net & n1->nt_mask; ina2 = n2->nt_net & n2->nt_mask; if (ina1 == ina2) return (ht); } else if (n1->nt_family == AF_INET6) { struct in6_addr ina1, ina2; for (i=0; i < (int)sizeof(ina1.s6_addr); i++) { ina1.s6_addr[i] = n1->nt_net6.s6_addr[i] & n1->nt_mask6.s6_addr[i]; ina2.s6_addr[i] = n2->nt_net6.s6_addr[i] & n2->nt_mask6.s6_addr[i]; } if (!bcmp(&ina1, &ina2, sizeof(ina1))) return (ht); } } break; } return (NULL); } /* * compare two credentials */ int crcmp(struct xucred *cr1, struct xucred *cr2) { int i; if (cr1 == cr2) return 0; if (cr1 == NULL || cr2 == NULL) return 1; if (cr1->cr_uid != cr2->cr_uid) return 1; if (cr1->cr_ngroups != cr2->cr_ngroups) return 1; /* XXX assumes groups will always be listed in some order */ for (i=0; i < cr1->cr_ngroups; i++) if (cr1->cr_groups[i] != cr2->cr_groups[i]) return 1; return (0); } /* * tentatively hang export options for a list of groups off of an exported directory */ int hang_options_setup(struct expdir *xd, int opt_flags, struct xucred *cr, struct grouplist *grp, struct nfs_sec *secflavs, int *need_export) { struct host *ht; *need_export = 0; if (!grp) { /* default export */ if (xd->xd_flags & OP_DEFEXP) { /* exported directory already has default export! */ if ((OP_EXOPTS(xd->xd_flags) == OP_EXOPTS(opt_flags)) && (!(opt_flags & (OP_MAPALL|OP_MAPROOT)) || !crcmp(&xd->xd_cred, cr)) && (!cmp_secflavs(&xd->xd_sec, secflavs))) { if (!(xd->xd_flags & OP_DEL)) log(LOG_WARNING, "duplicate default export for %s", xd->xd_dir); xd->xd_flags &= ~OP_EXOPTMASK; xd->xd_flags |= opt_flags | OP_DEFEXP | OP_ADD; return (0); } else if (!(xd->xd_flags & OP_DEL)) { log(LOG_ERR, "multiple/conflicting default exports for %s", xd->xd_dir); return (EEXIST); } } xd->xd_flags &= ~OP_EXOPTMASK; xd->xd_flags |= opt_flags | OP_DEFEXP | OP_ADD; if (cr) xd->xd_cred = *cr; bcopy(secflavs, &xd->xd_sec, sizeof(struct nfs_sec)); DEBUG(3, "hang_options_setup: %s default 0x%x", xd->xd_dir, xd->xd_flags); *need_export = 1; return (0); } while (grp) { /* first check for an existing entry for this group */ ht = find_group_match_in_host_list(&xd->xd_hosts, grp); if (ht) { /* found a match... */ if ((OP_EXOPTS(ht->ht_flags) == OP_EXOPTS(opt_flags)) && (!(opt_flags & (OP_MAPALL|OP_MAPROOT)) || !crcmp(&ht->ht_cred, cr)) && (!cmp_secflavs(&ht->ht_sec, secflavs))) { /* options match, OK, it's the same export */ if (!(ht->ht_flags & OP_ADD) && !(grp->gr_flags & GF_SHOW)) ht->ht_flags &= ~OP_SHOW; ht->ht_flags |= OP_ADD; if (grp->gr_flags & GF_SHOW) ht->ht_flags |= OP_SHOW; grp = grp->gr_next; continue; } /* options don't match... */ if (!(ht->ht_flags & OP_DEL)) { /* this is a new entry, so this is a conflict */ log(LOG_ERR, "conflicting exports for %s, %s", xd->xd_dir, grp_name(grp)); return (EEXIST); } /* this entry is marked for deletion, so there is no conflict */ /* go ahead and add a new entry with the new options */ } /* also check for an existing entry for any addresses in this group */ ht = find_group_address_match_in_host_list(&xd->xd_hosts, grp); if (ht) { /* found a match... */ if ((OP_EXOPTS(ht->ht_flags) != OP_EXOPTS(opt_flags)) || ((opt_flags & (OP_MAPALL|OP_MAPROOT)) && crcmp(&ht->ht_cred, cr)) || (cmp_secflavs(&ht->ht_sec, secflavs))) { /* ...with different options */ log(LOG_ERR, "conflicting exports for %s, %s", xd->xd_dir, grp_name(grp)); return (EEXIST); } /* ... with same options */ log(LOG_WARNING, "duplicate export for %s, %s vs. %s", xd->xd_dir, grp_name(grp), grp_name(ht->ht_grp)); grp = grp->gr_next; continue; } /* OK to add a new host */ ht = get_host(); if (!ht) { log(LOG_ERR, "Can't allocate memory for host: %s", grp_name(grp)); return(ENOMEM); } ht->ht_flags = opt_flags | OP_ADD; ht->ht_cred = *cr; ht->ht_grp = grp; grp->gr_refcnt++; bcopy(secflavs, &ht->ht_sec, sizeof(struct nfs_sec)); if (grp->gr_flags & GF_SHOW) ht->ht_flags |= OP_SHOW; if (config.verbose >= 6) DEBUG(4, "grp2host: %p %d %s %s", grp, grp->gr_refcnt, grp_name(grp), grp_addr(grp)); if (!add_host(&xd->xd_hosts, ht)) { /* This shouldn't happen given the above checks */ log(LOG_ERR, "duplicate host in export list: %s", grp_name(grp)); free_host(ht); return (EEXIST); } *need_export = 1; DEBUG(3, "hang_options_setup: %s %s 0x%x", xd->xd_dir, grp_name(grp), opt_flags); grp = grp->gr_next; } return (0); } /* * make permanent the export options added via hang_options_setup() */ void hang_options_finalize(struct expdir *xd) { struct host *ht; struct expdir *mxd; if (xd->xd_flags & OP_ADD) { xd->xd_iflags |= (xd->xd_flags & (OP_OFFLINE|OP_MISSING)) ? OP_OFFLINE : OP_ONLINE; merge_secflavs(&xd->xd_ssec, &xd->xd_sec); xd->xd_flags &= ~(OP_ADD|OP_DEL); /* update old options in case subsequent export fails */ xd->xd_oflags = xd->xd_flags; xd->xd_ocred = xd->xd_cred; bcopy(&xd->xd_sec, &xd->xd_osec, sizeof(struct nfs_sec)); } TAILQ_FOREACH(ht, &xd->xd_hosts, ht_next) { if (!(ht->ht_flags & OP_ADD)) continue; ht->ht_flags &= ~(OP_ADD|OP_DEL); xd->xd_iflags |= (ht->ht_flags & (OP_OFFLINE|OP_MISSING)) ? OP_OFFLINE : OP_ONLINE; merge_secflavs(&xd->xd_ssec, &ht->ht_sec); } TAILQ_FOREACH(mxd, &xd->xd_mountdirs, xd_next) { hang_options_finalize(mxd); } } /* * cleanup/undo the export options added via hang_options_setup() */ void hang_options_cleanup(struct expdir *xd) { struct host *ht, *htnext; if (xd->xd_flags & OP_ADD) { xd->xd_flags = xd->xd_oflags; xd->xd_cred = xd->xd_ocred; bcopy(&xd->xd_osec, &xd->xd_sec, sizeof(struct nfs_sec)); } TAILQ_FOREACH_SAFE(ht, &xd->xd_hosts, ht_next, htnext) { if (!(ht->ht_flags & OP_ADD)) continue; if (ht->ht_flags & OP_DEL) { ht->ht_flags &= ~OP_ADD; continue; } TAILQ_REMOVE(&xd->xd_hosts, ht, ht_next); free_host(ht); } /* * Note: currently cleanup isn't called after handling mountdirs, * so we don't have to bother cleaning up any of the mountdirs. */ } /* * hang export options for mountable subdirectories of an exported directory */ int hang_options_mountdir(struct expdir *xd, char *dir, int opt_flags, struct grouplist *grp, struct nfs_sec *secflavs) { struct host *ht; struct expdir *mxd, *mxd2, *mxd3; int cmp; /* check for existing mountdir */ mxd = mxd3 = NULL; TAILQ_FOREACH(mxd2, &xd->xd_mountdirs, xd_next) { cmp = strcmp(dir, mxd2->xd_dir); if (!cmp) { /* found it */ mxd = mxd2; break; } else if (cmp < 0) { /* found where it needs to be inserted */ break; } mxd3 = mxd2; } if (!mxd) { mxd = get_expdir(); if (mxd) mxd->xd_dir = strdup(dir); if (!mxd || !mxd->xd_dir) { if (mxd) free_expdir(mxd); log(LOG_ERR, "can't allocate memory for mountable sub-directory; %s", dir); return (ENOMEM); } if (mxd3) { TAILQ_INSERT_AFTER(&xd->xd_mountdirs, mxd3, mxd, xd_next); } else { TAILQ_INSERT_HEAD(&xd->xd_mountdirs, mxd, xd_next); } } if (!grp) { /* default export */ if (mxd->xd_flags & OP_DEFEXP) { /* exported directory already has default export! */ if ((OP_EXOPTS(mxd->xd_flags) == OP_EXOPTS(opt_flags)) && (!cmp_secflavs(&mxd->xd_sec, secflavs))) { if (!(mxd->xd_flags & OP_DEL)) log(LOG_WARNING, "duplicate default export for %s", mxd->xd_dir); mxd->xd_flags &= ~OP_EXOPTMASK; mxd->xd_flags |= opt_flags | OP_DEFEXP | OP_ADD; return (0); } else if (!(mxd->xd_flags & OP_DEL)) { log(LOG_ERR, "multiple/conflicting default exports for %s", mxd->xd_dir); return (EEXIST); } } mxd->xd_flags &= ~OP_EXOPTMASK; mxd->xd_flags |= opt_flags | OP_DEFEXP | OP_ADD; bcopy(secflavs, &mxd->xd_sec, sizeof(struct nfs_sec)); DEBUG(3, "hang_options_mountdir: %s default 0x%x", mxd->xd_dir, mxd->xd_flags); return (0); } while (grp) { /* first check for an existing entry for this group */ ht = find_group_match_in_host_list(&mxd->xd_hosts, grp); if (ht) { /* found a match... */ if ((OP_EXOPTS(ht->ht_flags) == OP_EXOPTS(opt_flags)) && (!cmp_secflavs(&ht->ht_sec, secflavs))) { /* options match, OK, it's the same export */ ht->ht_flags |= OP_ADD; grp = grp->gr_next; continue; } /* options don't match... */ if (!(ht->ht_flags & OP_DEL)) { /* this is a new entry, so this is a conflict */ log(LOG_ERR, "conflicting mountdir exports for %s, %s", mxd->xd_dir, grp_name(grp)); return (EEXIST); } /* this entry is marked for deletion, so there is no conflict */ /* go ahead and add a new entry with the new options */ } /* also check for an existing entry for any addresses in this group */ ht = find_group_address_match_in_host_list(&mxd->xd_hosts, grp); if (ht) { /* found a match... */ if ((OP_EXOPTS(ht->ht_flags) != OP_EXOPTS(opt_flags)) || (cmp_secflavs(&ht->ht_sec, secflavs))) { /* ...with different options */ log(LOG_ERR, "conflicting mountdir exports for %s, %s", mxd->xd_dir, grp_name(grp)); return (EEXIST); } /* ... with same options */ log(LOG_WARNING, "duplicate mountdir export for %s, %s vs. %s", mxd->xd_dir, grp_name(grp), grp_name(ht->ht_grp)); grp = grp->gr_next; continue; } /* OK to add a new host */ ht = get_host(); if (!ht) { log(LOG_ERR, "Can't allocate memory for host: %s", grp_name(grp)); return(ENOMEM); } ht->ht_flags = opt_flags | OP_ADD; ht->ht_grp = grp; grp->gr_refcnt++; bcopy(secflavs, &ht->ht_sec, sizeof(struct nfs_sec)); if (grp->gr_flags & GF_SHOW) ht->ht_flags |= OP_SHOW; if (config.verbose >= 6) DEBUG(4, "grp2host: %p %d %s %s", grp, grp->gr_refcnt, grp_name(grp), grp_addr(grp)); if (!add_host(&mxd->xd_hosts, ht)) { /* This shouldn't happen given the above checks */ log(LOG_ERR, "Can't add host to mountdir export list: %s", grp_name(grp)); free_host(ht); return (EEXIST); } DEBUG(3, "hang_options_mountdir: %s %s 0x%x", mxd->xd_dir, grp_name(grp), opt_flags); grp = grp->gr_next; } return (0); } /* * Search for an exported directory on an exported file system that * a given host can mount and return the export options. * * Search order: * an exact match on exported directory path * an exact match on exported directory mountdir path * a subdir match on exported directory mountdir path with ALLDIRS * a subdir match on exported directory path with ALLDIRS */ int expdir_search(struct expfs *xf, char *dirpath, struct sockaddr *sa, int *options, struct nfs_sec *secflavs) { struct expdir *xd, *mxd; struct host *hp; int cmp = 1, chkalldirs = 0; TAILQ_FOREACH(xd, &xf->xf_dirl, xd_next) { if ((cmp = subdir_check(xd->xd_dir, dirpath)) >= 0) break; } if (!xd) { DEBUG(1, "expdir_search: no matching export: %s", dirpath); return (0); } DEBUG(1, "expdir_search: %s -> %s", dirpath, xd->xd_dir); if (cmp == 0) { /* exact match on exported directory path */ check_xd_hosts: /* find options for this host */ hp = find_host(&xd->xd_hosts, sa); if (hp && (!chkalldirs || (hp->ht_flags & OP_ALLDIRS))) { DEBUG(2, "expdir_search: %s host %s", dirpath, (chkalldirs ? "alldirs" : "match")); *options = hp->ht_flags; bcopy(&hp->ht_sec, secflavs, sizeof(struct nfs_sec)); } else if ((xd->xd_flags & OP_DEFEXP) && (!chkalldirs || (xd->xd_flags & OP_ALLDIRS))) { DEBUG(2, "expdir_search: %s defexp %s", dirpath, (chkalldirs ? "alldirs" : "match")); *options = xd->xd_flags; bcopy(&xd->xd_sec, secflavs, sizeof(struct nfs_sec)); } else { /* not exported to this host */ *options = 0; DEBUG(2, "expdir_search: %s NO match", dirpath); return (0); } return (1); } /* search for a matching mountdir */ TAILQ_FOREACH(mxd, &xd->xd_mountdirs, xd_next) { cmp = subdir_check(mxd->xd_dir, dirpath); if (cmp < 0) continue; DEBUG(1, "expdir_search: %s subdir path match %s", dirpath, mxd->xd_dir); chkalldirs = (cmp != 0); /* found a match on a mountdir */ hp = find_host(&mxd->xd_hosts, sa); if (hp && (!chkalldirs || (hp->ht_flags & OP_ALLDIRS))) { DEBUG(2, "expdir_search: %s -> %s subdir host %s", dirpath, mxd->xd_dir, (chkalldirs ? "alldirs" : "match")); *options = hp->ht_flags; bcopy(&hp->ht_sec, secflavs, sizeof(struct nfs_sec)); return (1); } else if ((mxd->xd_flags & OP_DEFEXP) && (!chkalldirs || (mxd->xd_flags & OP_ALLDIRS))) { DEBUG(2, "expdir_search: %s -> %s subdir defexp %s", dirpath, mxd->xd_dir, (chkalldirs ? "alldirs" : "match")); *options = mxd->xd_flags; bcopy(&mxd->xd_sec, secflavs, sizeof(struct nfs_sec)); return (1); } /* not exported to this host */ } DEBUG(1, "expdir_search: %s NO match, check alldirs", dirpath); chkalldirs = 1; goto check_xd_hosts; } /* * search a host list for a match for the given address */ struct host * find_host(struct hosttqh *head, struct sockaddr *sa) { struct host *hp; struct grouplist *grp; struct addrinfo *ai; int i; TAILQ_FOREACH(hp, head, ht_next) { grp = hp->ht_grp; switch (grp->gr_type) { case GT_HOST: for (ai = grp->gr_u.gt_hostinfo.h_ailist; ai; ai = ai->ai_next) { if (ai->ai_family != sa->sa_family) continue; if (ai->ai_addrlen != sa->sa_len) continue; if (ai->ai_family == AF_INET) { struct sockaddr_in *sin1, *sin2; sin1 = (struct sockaddr_in*) AOK ai->ai_addr; sin2 = (struct sockaddr_in*) AOK sa; if (!bcmp(&sin1->sin_addr, &sin2->sin_addr, sizeof(sin1->sin_addr))) return (hp); } else if (ai->ai_family == AF_INET6) { struct sockaddr_in6 *sin1, *sin2; sin1 = (struct sockaddr_in6*) AOK ai->ai_addr; sin2 = (struct sockaddr_in6*) AOK sa; if (!bcmp(&sin1->sin6_addr, &sin2->sin6_addr, sizeof(sin1->sin6_addr))) return (hp); } } break; case GT_NET: if (grp->gr_u.gt_net.nt_family != sa->sa_family) break; if (grp->gr_u.gt_net.nt_family == AF_INET) { in_addr_t ina = ((struct sockaddr_in*) AOK sa)->sin_addr.s_addr; if ((ina & grp->gr_u.gt_net.nt_mask) == grp->gr_u.gt_net.nt_net) return (hp); } else if (grp->gr_u.gt_net.nt_family == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6*) AOK sa; struct in6_addr ina; for (i=0; i < (int)sizeof(ina.s6_addr); i++) ina.s6_addr[i] = sa6->sin6_addr.s6_addr[i] & grp->gr_u.gt_net.nt_mask6.s6_addr[i]; if (!bcmp(&ina, &grp->gr_u.gt_net.nt_net6, sizeof(ina))) return (hp); } break; } } return (NULL); } /* * Parse the option string and update fields. * Option arguments may either be -