diff --git a/usr.sbin/mountd/exports.5 b/usr.sbin/mountd/exports.5 index bba10ddc7be1..d5aa49a1f428 100644 --- a/usr.sbin/mountd/exports.5 +++ b/usr.sbin/mountd/exports.5 @@ -1,607 +1,608 @@ .\" Copyright (c) 1989, 1991, 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. 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. .\" .\" @(#)exports.5 8.3 (Berkeley) 3/29/95 .\" $FreeBSD$ .\" -.Dd November 20, 2020 +.Dd November 9, 2021 .Dt EXPORTS 5 .Os .Sh NAME .Nm exports .Nd define remote mount points for .Tn NFS mount requests .Sh SYNOPSIS .Nm .Sh DESCRIPTION The .Nm file specifies remote mount points for the .Tn NFS mount protocol per the .Tn NFS server specification; see .%T "Network File System Protocol Specification" , RFC1094, Appendix A and .%T "NFS: Network File System Version 3 Specification" , Appendix I. .Pp Each line in the file (other than comment lines that begin with a #) specifies the mount point(s) and export flags within one local server file system or the NFSv4 tree root for one or more hosts. A long line may be split over several lines by ending all but the last line with a backslash .Pq Ql \e . A host may be specified only once for each local file or the NFSv4 tree root on the server and there may be only one default entry for each server file system that applies to all other hosts. The latter exports the file system to the .Dq world and should be used only when the file system contains public information. .Pp In a mount entry, the first field(s) specify the directory path(s) within a server file system that can be mounted on by the corresponding client(s). There are three forms of this specification. The first is to list all mount points as absolute directory paths separated by whitespace. This list of directory paths should be considered an .Dq administrative control , since it is only enforced by the .Xr mountd 8 daemon and not the kernel. As such, it only applies to NFSv2 and NFSv3 mounts and only with respect to the client's use of the mount protocol. The second is to specify the pathname of the root of the file system followed by the .Fl alldirs flag; this form allows the host(s) to mount at any point within the file system, including regular files if the .Fl r option is used on .Xr mountd 8 . Because NFSv4 does not use the mount protocol, the .Dq administrative controls are not applied and all directories within this server file system are mountable via NFSv4 even if the .Fl alldirs flag has not been specified. The third form has the string ``V4:'' followed by a single absolute path name, to specify the NFSv4 tree root. This line does not export any file system, but simply marks where the root of the server's directory tree is for NFSv4 clients. The exported file systems for NFSv4 are specified via the other lines in the .Nm file in the same way as for NFSv2 and NFSv3. The pathnames must not have any symbolic links in them and should not have any .Dq Pa \&. or .Dq Pa .. components. Mount points for a file system may appear on multiple lines each with different sets of hosts and export options. .Pp The second component of a line specifies how the file system is to be exported to the host set. The option flags specify whether the file system is exported read-only or read-write and how the client UID is mapped to user credentials on the server. For the NFSv4 tree root, the only options that can be specified in this section are ones related to security: .Fl sec , .Fl tls , .Fl tlscert and .Fl tlscertuser . .Pp Export options are specified as follows: .Pp .Sm off .Fl maproot Li = Sy user .Sm on The credential of the specified user is used for remote access by root. The credential includes all the groups to which the user is a member on the local machine (see .Xr id 1 ) . The user may be specified by name or number. The user string may be quoted, or use backslash escaping. .Pp .Sm off .Fl maproot Li = Sy user:group1:group2:... .Sm on The colon separated list is used to specify the precise credential to be used for remote access by root. The elements of the list may be either names or numbers. Note that user: should be used to distinguish a credential containing no groups from a complete credential for that user. The group names may be quoted, or use backslash escaping. .Pp .Sm off .Fl mapall Li = Sy user .Sm on or .Sm off .Fl mapall Li = Sy user:group1:group2:... .Sm on specifies a mapping for all client UIDs (including root) using the same semantics as .Fl maproot . .Pp The option .Fl r is a synonym for .Fl maproot in an effort to be backward compatible with older export file formats. .Pp In the absence of .Fl maproot and .Fl mapall options, remote accesses by root will result in using a credential of 65534:65533. All other users will be mapped to their remote credential. If a .Fl maproot option is given, remote access by root will be mapped to that credential instead of 65534:65533. If a .Fl mapall option is given, all users (including root) will be mapped to that credential in place of their own. .Pp .Sm off .Fl sec Li = Sy flavor1:flavor2... .Sm on specifies a colon separated list of acceptable security flavors to be used for remote access. Supported security flavors are sys, krb5, krb5i and krb5p. If multiple flavors are listed, they should be ordered with the most preferred flavor first. If this option is not present, the default security flavor list of just sys is used. .Pp The .Fl ro option specifies that the file system should be exported read-only (default read/write). The option .Fl o is a synonym for .Fl ro in an effort to be backward compatible with older export file formats. .Pp .Tn WebNFS exports strictly according to the spec (RFC 2054 and RFC 2055) can be done with the .Fl public flag. However, this flag in itself allows r/w access to all files in the file system, not requiring reserved ports and not remapping UIDs. It is only provided to conform to the spec, and should normally not be used. For a .Tn WebNFS export, use the .Fl webnfs flag, which implies .Fl public , .Sm off .Fl mapall No = Sy nobody .Sm on and .Fl ro . Note that only one file system can be .Tn WebNFS exported on a server. .Pp A .Sm off .Fl index No = Pa file .Sm on option can be used to specify a file whose handle will be returned if a directory is looked up using the public filehandle .Pq Tn WebNFS . This is to mimic the behavior of URLs. If no .Fl index option is specified, a directory filehandle will be returned as usual. The .Fl index option only makes sense in combination with the .Fl public or .Fl webnfs flags. .Pp The .Fl tls , .Fl tlscert and .Fl tlscertuser export options are used to require the client to use TLS for the mount(s) per RFC NNNN. For NFS mounts using TLS to work, .Xr rpc.tlsservd 8 must be running on the server. .Bd -filled -offset indent .Fl tls requires that the client use TLS. .br .Fl tlscert requires that the client use TLS and provide a verifiable X.509 certificate during TLS handshake. .br .Fl tlscertuser requires that the client use TLS and provide a verifiable X.509 certificate. The otherName component of the certificate's subjAltName must have a an OID of 1.3.6.1.4.1.2238.1.1.1 and a UTF8 string of the form .Dq user@domain . .Dq user@domain will be translated to the credentials of the specified user in the same manner as .Xr nfsuserd 8 , where .Dq user is normally a username is the server's password database and .Dq domain is the DNS domain name for the server. All RPCs will be performed using these credentials instead of the ones in the RPC header in a manner similar to .Sm off .Fl mapall Li = Sy user . .Sm on .Ed .Pp If none of these three flags are specified, TLS mounts are permitted but not required. .Pp Specifying the .Fl quiet option will inhibit some of the syslog diagnostics for bad lines in .Pa /etc/exports . This can be useful to avoid annoying error messages for known possible problems (see .Sx EXAMPLES below). .Pp The third component of a line specifies the host set to which the line applies. The set may be specified in three ways. The first way is to list the host name(s) separated by white space. (Standard Internet .Dq dot addresses may be used in place of names.) The second way is to specify a .Dq netgroup as defined in the .Pa netgroup file (see .Xr netgroup 5 ) . The third way is to specify an Internet subnetwork using a network and network mask that is defined as the set of all hosts with addresses within the subnetwork. This latter approach requires less overhead within the kernel and is recommended for cases where the export line refers to a large number of clients within an administrative subnet. .Pp The first two cases are specified by simply listing the name(s) separated by whitespace. All names are checked to see if they are .Dq netgroup names first and are assumed to be hostnames otherwise. Using the full domain specification for a hostname can normally circumvent the problem of a host that has the same name as a netgroup. The third case is specified by the flag .Sm off .Fl network Li = Sy netname Op Li / Ar prefixlength .Sm on and optionally .Sm off .Fl mask No = Sy netmask . .Sm on The netmask may be specified either by attaching a .Ar prefixlength to the .Fl network option, or by using a separate .Fl mask option. -If the mask is not specified, it will default to the mask for that network -class (A, B or C; see +If the mask is not specified, it will default to the historical mask +for that network class (A, B, or C; see .Xr inet 4 ) . +This usage is deprecated, and will elicit a warning log message. See the .Sx EXAMPLES section below. .Pp Scoped IPv6 address must carry scope identifier as documented in .Xr inet6 4 . For example, .Dq Li fe80::%re2/10 is used to specify .Li fe80::/10 on .Li re2 interface. .Pp For the third form which specifies the NFSv4 tree root, the directory path specifies the location within the server's file system tree which is the root of the NFSv4 tree. There can only be one NFSv4 root directory per server. As such, all entries of this form must specify the same directory path. For file systems other than ZFS, this location can be any directory and does not need to be within an exported file system. If it is not in an exported file system, a very limited set of operations are permitted, so that an NFSv4 client can traverse the tree to an exported file system. Although parts of the NFSv4 tree can be non-exported, the entire NFSv4 tree must consist of local file systems capable of being exported via NFS. All ZFS file systems in the subtree below the NFSv4 tree root must be exported. NFSv4 does not use the mount protocol and does permit clients to cross server mount point boundaries, although not all clients are capable of crossing the mount points. .Pp The .Fl sec option on these line(s) specifies what security flavors may be used for NFSv4 operations that do not use file handles. Since these operations (SetClientID, SetClientIDConfirm, Renew, DelegPurge and ReleaseLockOnwer) allocate/modify state in the server, it is possible to restrict some clients to the use of the krb5[ip] security flavors, via this option. See the .Sx EXAMPLES section below. This third form is meaningless for NFSv2 and NFSv3 and is ignored for them. .Pp The .Xr mountd 8 utility can be made to re-read the .Nm file by sending it a hangup signal as follows: .Bd -literal -offset indent /etc/rc.d/mountd reload .Ed .Pp After sending the .Dv SIGHUP , check the .Xr syslogd 8 output to see whether .Xr mountd 8 logged any parsing errors in the .Nm file. .Sh FILES .Bl -tag -width /etc/exports -compact .It Pa /etc/exports the default remote mount-point file .El .Sh EXAMPLES .Bd -literal -offset indent /usr /usr/local -maproot=0:10 friends /usr -maproot=daemon grumpy.cis.uoguelph.ca 131.104.48.16 /usr -ro -mapall=nobody /u -maproot=bin: -network 131.104.48 -mask 255.255.255.0 /a -network 192.168.0/24 /a -network 3ffe:1ce1:1:fe80::/64 /u2 -maproot=root friends /u2 -alldirs -network cis-net -mask cis-mask /cdrom -alldirs,quiet,ro -network 192.168.33.0 -mask 255.255.255.0 /private -sec=krb5i /secret -sec=krb5p V4: / -sec=krb5:krb5i:krb5p -network 131.104.48 -mask 255.255.255.0 V4: / -sec=sys:krb5:krb5i:krb5p grumpy.cis.uoguelph.ca .Ed .Pp Given that .Pa /usr , /u , /a and .Pa /u2 are local file system mount points, the above example specifies the following: .Pp The file system rooted at .Pa /usr is exported to hosts .Em friends where friends is specified in the netgroup file with users mapped to their remote credentials and root mapped to UID 0 and group 10. It is exported read-write and the hosts in .Dq friends can mount either .Pa /usr or .Pa /usr/local . It is exported to .Em 131.104.48.16 and .Em grumpy.cis.uoguelph.ca with users mapped to their remote credentials and root mapped to the user and groups associated with .Dq daemon ; it is exported to the rest of the world as read-only with all users mapped to the user and groups associated with .Dq nobody . .Pp The file system rooted at .Pa /u is exported to all hosts on the subnetwork .Em 131.104.48 with root mapped to the UID for .Dq bin and with no group access. .Pp The file system rooted at .Pa /u2 is exported to the hosts in .Dq friends with root mapped to UID and groups associated with .Dq root ; it is exported to all hosts on network .Dq cis-net allowing mounts at any directory within /u2. .Pp The file system rooted at .Pa /a is exported to the network 192.168.0.0, with a netmask of 255.255.255.0. However, the netmask length in the entry for .Pa /a is not specified through a .Fl mask option, but through the .Li / Ns Ar prefix notation. .Pp The file system rooted at .Pa /a is also exported to the IPv6 network .Li 3ffe:1ce1:1:fe80:: address, using the upper 64 bits as the prefix. Note that, unlike with IPv4 network addresses, the specified network address must be complete, and not just contain the upper bits. With IPv6 addresses, the .Fl mask option must not be used. .Pp The file system rooted at .Pa /cdrom will be exported read-only to the entire network 192.168.33.0/24, including all its subdirectories. Since .Pa /cdrom is the conventional mountpoint for a CD-ROM device, this export will fail if no CD-ROM medium is currently mounted there since that line would then attempt to export a subdirectory of the root file system with the .Fl alldirs option which is not allowed. The .Fl quiet option will then suppress the error message for this condition that would normally be syslogged. As soon as an actual CD-ROM is going to be mounted, .Xr mount 8 will notify .Xr mountd 8 about this situation, and the .Pa /cdrom file system will be exported as intended. Note that without using the .Fl alldirs option, the export would always succeed. While there is no CD-ROM medium mounted under .Pa /cdrom , it would export the (normally empty) directory .Pa /cdrom of the root file system instead. .Pp The file system rooted at .Pa /private will be exported using Kerberos 5 authentication and will require integrity protected messages for all accesses. The file system rooted at .Pa /secret will also be exported using Kerberos 5 authentication and all messages used to access it will be encrypted. .Pp For the experimental server, the NFSv4 tree is rooted at ``/'', and any client within the 131.104.48 subnet is permitted to perform NFSv4 state operations on the server, so long as valid Kerberos credentials are provided. The machine grumpy.cis.uoguelph.ca is permitted to perform NFSv4 state operations on the server using AUTH_SYS credentials, as well as Kerberos ones. .Pp In the following example some directories are exported as NFSv3 and NFSv4: .Bd -literal -offset indent V4: /wingsdl/nfsv4 /wingsdl/nfsv4/usr-ports -maproot=root -network 172.16.0.0 -mask 255.255.0.0 /wingsdl/nfsv4/clasper -maproot=root clasper .Ed .Pp Only one V4: line is needed or allowed to declare where NFSv4 is rooted. The other lines declare specific exported directories with their absolute paths given in /etc/exports. .Pp The exported directories' paths are used for both v3 and v4. However, they are interpreted differently for v3 and v4. A client mount command for usr-ports would use the server-absolute name when using nfsv3: .Bd -literal -offset indent mount server:/wingsdl/nfsv4/usr-ports /mnt/tmp .Ed .Pp A mount command using NFSv4 would use the path relative to the NFSv4 root: .Bd -literal -offset indent mount server:/usr-ports /mnt/tmp .Ed .Pp This also differentiates which version you want if the client can do both v3 and v4. The former will only ever do a v3 mount and the latter will only ever do a v4 mount. .Pp Note that due to different mount behavior between NFSv3 and NFSv4 a NFSv4 mount request for a directory that the client does not have permission for will succeed and read/write access will fail afterwards, whereas NFSv3 rejects the mount request. .Sh SEE ALSO .Xr nfsv4 4 , .Xr netgroup 5 , .Xr mountd 8 , .Xr nfsd 8 , .Xr rpc.tlsservd 8 , .Xr showmount 8 .Sh STANDARDS The implementation is based on the specification in .Rs .%T "Network File System Protocol Specification, Appendix A, RFC 1094" .%T "NFS: Network File System Version 3, Appendix I, RFC 1813" .%T "Towards Remote Procedure Call Encryption By Default, RFC nnnn" .Re .Sh BUGS The export options are tied to the local mount points in the kernel and must be non-contradictory for any exported subdirectory of the local server mount point. It is recommended that all exported directories within the same server file system be specified on adjacent lines going down the tree. You cannot specify a hostname that is also the name of a netgroup. Specifying the full domain specification for a hostname can normally circumvent the problem. diff --git a/usr.sbin/mountd/mountd.c b/usr.sbin/mountd/mountd.c index 0c077b857d7e..569ea0fbf707 100644 --- a/usr.sbin/mountd/mountd.c +++ b/usr.sbin/mountd/mountd.c @@ -1,3957 +1,3960 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Herb Hasler and Rick Macklem at The University of Guelph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1989, 1993\n\ The Regents of the University of California. All rights reserved.\n"; #endif /*not lint*/ #if 0 #ifndef lint static char sccsid[] = "@(#)mountd.c 8.15 (Berkeley) 5/1/95"; #endif /*not lint*/ #endif #include __FBSDID("$FreeBSD$"); #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 #include #include #include #include #include #include #include #include #include "pathnames.h" #include "mntopts.h" #ifdef DEBUG #include #endif /* * Structures for keeping the mount list and export list */ struct mountlist { char ml_host[MNTNAMLEN+1]; char ml_dirp[MNTPATHLEN+1]; SLIST_ENTRY(mountlist) next; }; struct dirlist { struct dirlist *dp_left; struct dirlist *dp_right; int dp_flag; struct hostlist *dp_hosts; /* List of hosts this dir exported to */ char *dp_dirp; }; /* dp_flag bits */ #define DP_DEFSET 0x1 #define DP_HOSTSET 0x2 /* * maproot/mapall credentials. * cr_smallgrps can be used for a group list up to SMALLNGROUPS in size. * Larger group lists are malloc'd/free'd. */ #define SMALLNGROUPS 32 struct expcred { uid_t cr_uid; int cr_ngroups; gid_t cr_smallgrps[SMALLNGROUPS]; gid_t *cr_groups; }; struct exportlist { struct dirlist *ex_dirl; struct dirlist *ex_defdir; struct grouplist *ex_grphead; int ex_flag; fsid_t ex_fs; char *ex_fsdir; char *ex_indexfile; struct expcred ex_defanon; uint64_t ex_defexflags; int ex_numsecflavors; int ex_secflavors[MAXSECFLAVORS]; int ex_defnumsecflavors; int ex_defsecflavors[MAXSECFLAVORS]; SLIST_ENTRY(exportlist) entries; }; /* ex_flag bits */ #define EX_LINKED 0x1 #define EX_DONE 0x2 #define EX_DEFSET 0x4 #define EX_PUBLICFH 0x8 SLIST_HEAD(exportlisthead, exportlist); struct netmsk { struct sockaddr_storage nt_net; struct sockaddr_storage nt_mask; char *nt_name; }; union grouptypes { struct addrinfo *gt_addrinfo; struct netmsk gt_net; }; struct grouplist { int gr_type; union grouptypes gr_ptr; struct grouplist *gr_next; struct expcred gr_anon; uint64_t gr_exflags; int gr_flag; int gr_numsecflavors; int gr_secflavors[MAXSECFLAVORS]; }; /* Group types */ #define GT_NULL 0x0 #define GT_HOST 0x1 #define GT_NET 0x2 #define GT_DEFAULT 0x3 #define GT_IGNORE 0x5 /* Group flags */ #define GR_FND 0x1 struct hostlist { int ht_flag; /* Uses DP_xx bits */ struct grouplist *ht_grp; struct hostlist *ht_next; }; struct fhreturn { int fhr_flag; int fhr_vers; nfsfh_t fhr_fh; int fhr_numsecflavors; int *fhr_secflavors; }; #define GETPORT_MAXTRY 20 /* Max tries to get a port # */ /* * How long to delay a reload of exports when there are RPC request(s) * to process, in usec. Must be less than 1second. */ #define RELOADDELAY 250000 /* Global defs */ static char *add_expdir(struct dirlist **, char *, int); static void add_dlist(struct dirlist **, struct dirlist *, struct grouplist *, int, struct exportlist *, struct expcred *, uint64_t); static void add_mlist(char *, char *); static int check_dirpath(char *); static int check_options(struct dirlist *); static int checkmask(struct sockaddr *sa); static int chk_host(struct dirlist *, struct sockaddr *, int *, int *, int *, int **); static char *strsep_quote(char **stringp, const char *delim); static int create_service(struct netconfig *nconf); static void complete_service(struct netconfig *nconf, char *port_str); static void clearout_service(void); static void del_mlist(char *hostp, char *dirp); static struct dirlist *dirp_search(struct dirlist *, char *); static int do_export_mount(struct exportlist *, struct statfs *); static int do_mount(struct exportlist *, struct grouplist *, uint64_t, struct expcred *, char *, int, struct statfs *, int, int *); static int do_opt(char **, char **, struct exportlist *, struct grouplist *, int *, uint64_t *, struct expcred *); static struct exportlist *ex_search(fsid_t *, struct exportlisthead *); static struct exportlist *get_exp(void); static void free_dir(struct dirlist *); static void free_exp(struct exportlist *); static void free_grp(struct grouplist *); static void free_host(struct hostlist *); static void free_v4rootexp(void); static void get_exportlist_one(int); static void get_exportlist(int); static void insert_exports(struct exportlist *, struct exportlisthead *); static void free_exports(struct exportlisthead *); static void read_exportfile(int); static int compare_nmount_exportlist(struct iovec *, int, char *); static int compare_export(struct exportlist *, struct exportlist *); static int compare_cred(struct expcred *, struct expcred *); static int compare_secflavor(int *, int *, int); static void delete_export(struct iovec *, int, struct statfs *, char *); static int get_host(char *, struct grouplist *, struct grouplist *); static struct hostlist *get_ht(void); static int get_line(void); static void get_mountlist(void); static int get_net(char *, struct netmsk *, int); static void getexp_err(struct exportlist *, struct grouplist *, const char *); static struct grouplist *get_grp(void); static void hang_dirp(struct dirlist *, struct grouplist *, struct exportlist *, int, struct expcred *, uint64_t); static void huphandler(int sig); static int makemask(struct sockaddr_storage *ssp, int bitlen); static void mntsrv(struct svc_req *, SVCXPRT *); static void nextfield(char **, char **); static void out_of_mem(void); static void parsecred(char *, struct expcred *); static int parsesec(char *, struct exportlist *); static int put_exlist(struct dirlist *, XDR *, struct dirlist *, int *, int); static void *sa_rawaddr(struct sockaddr *sa, int *nbytes); static int sacmp(struct sockaddr *sa1, struct sockaddr *sa2, struct sockaddr *samask); static int scan_tree(struct dirlist *, struct sockaddr *); static void usage(void); static int xdr_dir(XDR *, char *); static int xdr_explist(XDR *, caddr_t); static int xdr_explist_brief(XDR *, caddr_t); static int xdr_explist_common(XDR *, caddr_t, int); static int xdr_fhs(XDR *, caddr_t); static int xdr_mlist(XDR *, caddr_t); static void terminate(int); static void cp_cred(struct expcred *, struct expcred *); #define EXPHASH(f) (fnv_32_buf((f), sizeof(fsid_t), 0) % exphashsize) static struct exportlisthead *exphead = NULL; static struct exportlisthead *oldexphead = NULL; static int exphashsize = 0; static SLIST_HEAD(, mountlist) mlhead = SLIST_HEAD_INITIALIZER(&mlhead); static char *exnames_default[2] = { _PATH_EXPORTS, NULL }; static char **exnames; static char **hosts = NULL; static int force_v2 = 0; static int resvport_only = 1; static int nhosts = 0; static int dir_only = 1; static int dolog = 0; static int got_sighup = 0; static int xcreated = 0; static char *svcport_str = NULL; static int mallocd_svcport = 0; static int *sock_fd; static int sock_fdcnt; static int sock_fdpos; static int suspend_nfsd = 0; static int opt_flags; static int have_v6 = 1; static int v4root_phase = 0; static char v4root_dirpath[PATH_MAX + 1]; static struct exportlist *v4root_ep = NULL; static int has_publicfh = 0; static int has_set_publicfh = 0; static struct pidfh *pfh = NULL; /* Bits for opt_flags above */ #define OP_MAPROOT 0x01 #define OP_MAPALL 0x02 /* 0x4 free */ #define OP_MASK 0x08 #define OP_NET 0x10 #define OP_ALLDIRS 0x40 #define OP_HAVEMASK 0x80 /* A mask was specified or inferred. */ #define OP_QUIET 0x100 #define OP_MASKLEN 0x200 #define OP_SEC 0x400 #ifdef DEBUG static int debug = 1; static void SYSLOG(int, const char *, ...) __printflike(2, 3); #define syslog SYSLOG #else static int debug = 0; #endif /* * The LOGDEBUG() syslog() calls are always compiled into the daemon. * To enable them, create a file at _PATH_MOUNTDDEBUG. This file can be empty. * To disable the logging, just delete the file at _PATH_MOUNTDDEBUG. */ static int logdebug = 0; #define LOGDEBUG(format, ...) \ (logdebug ? syslog(LOG_DEBUG, format, ## __VA_ARGS__) : 0) /* * Similar to strsep(), but it allows for quoted strings * and escaped characters. * * It returns the string (or NULL, if *stringp is NULL), * which is a de-quoted version of the string if necessary. * * It modifies *stringp in place. */ static char * strsep_quote(char **stringp, const char *delim) { char *srcptr, *dstptr, *retval; char quot = 0; if (stringp == NULL || *stringp == NULL) return (NULL); srcptr = dstptr = retval = *stringp; while (*srcptr) { /* * We're looking for several edge cases here. * First: if we're in quote state (quot != 0), * then we ignore the delim characters, but otherwise * process as normal, unless it is the quote character. * Second: if the current character is a backslash, * we take the next character as-is, without checking * for delim, quote, or backslash. Exception: if the * next character is a NUL, that's the end of the string. * Third: if the character is a quote character, we toggle * quote state. * Otherwise: check the current character for NUL, or * being in delim, and end the string if either is true. */ if (*srcptr == '\\') { srcptr++; /* * The edge case here is if the next character * is NUL, we want to stop processing. But if * it's not NUL, then we simply want to copy it. */ if (*srcptr) { *dstptr++ = *srcptr++; } continue; } if (quot == 0 && (*srcptr == '\'' || *srcptr == '"')) { quot = *srcptr++; continue; } if (quot && *srcptr == quot) { /* End of the quoted part */ quot = 0; srcptr++; continue; } if (!quot && strchr(delim, *srcptr)) break; *dstptr++ = *srcptr++; } *stringp = (*srcptr == '\0') ? NULL : srcptr + 1; *dstptr = 0; /* Terminate the string */ return (retval); } /* * Mountd server for NFS mount protocol as described in: * NFS: Network File System Protocol Specification, RFC1094, Appendix A * The optional arguments are the exports file name * default: _PATH_EXPORTS * and "-n" to allow nonroot mount. */ int main(int argc, char **argv) { fd_set readfds; struct netconfig *nconf; char *endptr, **hosts_bak; void *nc_handle; pid_t otherpid; in_port_t svcport; int c, k, s; int maxrec = RPC_MAXDATASIZE; int attempt_cnt, port_len, port_pos, ret; char **port_list; uint64_t curtime, nexttime; struct timeval tv; struct timespec tp; sigset_t sig_mask, sighup_mask; int enable_rpcbind; enable_rpcbind = 1; /* Check that another mountd isn't already running. */ pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &otherpid); if (pfh == NULL) { if (errno == EEXIST) errx(1, "mountd already running, pid: %d.", otherpid); warn("cannot open or create pidfile"); } s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (s < 0) have_v6 = 0; else close(s); while ((c = getopt(argc, argv, "2deh:lnp:RrS")) != -1) switch (c) { case '2': force_v2 = 1; break; case 'e': /* now a no-op, since this is the default */ break; case 'n': resvport_only = 0; break; case 'R': /* Do not support Mount protocol */ enable_rpcbind = 0; break; case 'r': dir_only = 0; break; case 'd': debug = debug ? 0 : 1; break; case 'l': dolog = 1; break; case 'p': endptr = NULL; svcport = (in_port_t)strtoul(optarg, &endptr, 10); if (endptr == NULL || *endptr != '\0' || svcport == 0 || svcport >= IPPORT_MAX) usage(); svcport_str = strdup(optarg); break; case 'h': ++nhosts; hosts_bak = hosts; hosts_bak = realloc(hosts, nhosts * sizeof(char *)); if (hosts_bak == NULL) { if (hosts != NULL) { for (k = 0; k < nhosts; k++) free(hosts[k]); free(hosts); out_of_mem(); } } hosts = hosts_bak; hosts[nhosts - 1] = strdup(optarg); if (hosts[nhosts - 1] == NULL) { for (k = 0; k < (nhosts - 1); k++) free(hosts[k]); free(hosts); out_of_mem(); } break; case 'S': suspend_nfsd = 1; break; default: usage(); } if (enable_rpcbind == 0) { if (svcport_str != NULL) { warnx("-p option not compatible with -R, ignored"); free(svcport_str); svcport_str = NULL; } if (nhosts > 0) { warnx("-h option not compatible with -R, ignored"); for (k = 0; k < nhosts; k++) free(hosts[k]); free(hosts); hosts = NULL; nhosts = 0; } } if (modfind("nfsd") < 0) { /* Not present in kernel, try loading it */ if (kldload("nfsd") < 0 || modfind("nfsd") < 0) errx(1, "NFS server is not available"); } argc -= optind; argv += optind; if (argc > 0) exnames = argv; else exnames = exnames_default; openlog("mountd", LOG_PID, LOG_DAEMON); if (debug) warnx("getting export list"); get_exportlist(0); if (debug) warnx("getting mount list"); get_mountlist(); if (debug) warnx("here we go"); if (debug == 0) { daemon(0, 0); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); } signal(SIGHUP, huphandler); signal(SIGTERM, terminate); signal(SIGPIPE, SIG_IGN); pidfile_write(pfh); if (enable_rpcbind != 0) { rpcb_unset(MOUNTPROG, MOUNTVERS, NULL); rpcb_unset(MOUNTPROG, MOUNTVERS3, NULL); rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec); if (!resvport_only) { if (sysctlbyname("vfs.nfsd.nfs_privport", NULL, NULL, &resvport_only, sizeof(resvport_only)) != 0 && errno != ENOENT) { syslog(LOG_ERR, "sysctl: %m"); exit(1); } } /* * If no hosts were specified, add a wildcard entry to bind to * INADDR_ANY. Otherwise make sure 127.0.0.1 and ::1 are added * to the list. */ if (nhosts == 0) { hosts = malloc(sizeof(char *)); if (hosts == NULL) out_of_mem(); hosts[0] = "*"; nhosts = 1; } else { hosts_bak = hosts; if (have_v6) { hosts_bak = realloc(hosts, (nhosts + 2) * sizeof(char *)); if (hosts_bak == NULL) { for (k = 0; k < nhosts; k++) free(hosts[k]); free(hosts); out_of_mem(); } else hosts = hosts_bak; nhosts += 2; hosts[nhosts - 2] = "::1"; } else { hosts_bak = realloc(hosts, (nhosts + 1) * sizeof(char *)); if (hosts_bak == NULL) { for (k = 0; k < nhosts; k++) free(hosts[k]); free(hosts); out_of_mem(); } else { nhosts += 1; hosts = hosts_bak; } } hosts[nhosts - 1] = "127.0.0.1"; } } attempt_cnt = 1; sock_fdcnt = 0; sock_fd = NULL; port_list = NULL; port_len = 0; if (enable_rpcbind != 0) { nc_handle = setnetconfig(); while ((nconf = getnetconfig(nc_handle))) { if (nconf->nc_flag & NC_VISIBLE) { if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) { /* DO NOTHING */ } else { ret = create_service(nconf); if (ret == 1) /* Ignore this call */ continue; if (ret < 0) { /* * Failed to bind port, so close * off all sockets created and * try again if the port# was * dynamically assigned via * bind(2). */ clearout_service(); if (mallocd_svcport != 0 && attempt_cnt < GETPORT_MAXTRY) { free(svcport_str); svcport_str = NULL; mallocd_svcport = 0; } else { errno = EADDRINUSE; syslog(LOG_ERR, "bindresvport_sa:" " %m"); exit(1); } /* * Start over at the first * service. */ free(sock_fd); sock_fdcnt = 0; sock_fd = NULL; nc_handle = setnetconfig(); attempt_cnt++; } else if (mallocd_svcport != 0 && attempt_cnt == GETPORT_MAXTRY) { /* * For the last attempt, allow * different port #s for each * nconf by saving the * svcport_str setting it back * to NULL. */ port_list = realloc(port_list, (port_len + 1) * sizeof(char *)); if (port_list == NULL) out_of_mem(); port_list[port_len++] = svcport_str; svcport_str = NULL; mallocd_svcport = 0; } } } } /* * Successfully bound the ports, so call complete_service() to * do the rest of the setup on the service(s). */ sock_fdpos = 0; port_pos = 0; nc_handle = setnetconfig(); while ((nconf = getnetconfig(nc_handle))) { if (nconf->nc_flag & NC_VISIBLE) { if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) { /* DO NOTHING */ } else if (port_list != NULL) { if (port_pos >= port_len) { syslog(LOG_ERR, "too many" " port#s"); exit(1); } complete_service(nconf, port_list[port_pos++]); } else complete_service(nconf, svcport_str); } } endnetconfig(nc_handle); free(sock_fd); if (port_list != NULL) { for (port_pos = 0; port_pos < port_len; port_pos++) free(port_list[port_pos]); free(port_list); } if (xcreated == 0) { syslog(LOG_ERR, "could not create any services"); exit(1); } } /* Expand svc_run() here so that we can call get_exportlist(). */ curtime = nexttime = 0; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); for (;;) { clock_gettime(CLOCK_MONOTONIC, &tp); curtime = tp.tv_sec; curtime = curtime * 1000000 + tp.tv_nsec / 1000; sigprocmask(SIG_BLOCK, &sighup_mask, &sig_mask); if (got_sighup && curtime >= nexttime) { got_sighup = 0; sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); get_exportlist(1); clock_gettime(CLOCK_MONOTONIC, &tp); nexttime = tp.tv_sec; nexttime = nexttime * 1000000 + tp.tv_nsec / 1000 + RELOADDELAY; } else sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); /* * If a reload is pending, poll for received request(s), * otherwise set a RELOADDELAY timeout, since a SIGHUP * could be processed between the got_sighup test and * the select() system call. */ tv.tv_sec = 0; if (got_sighup) tv.tv_usec = 0; else tv.tv_usec = RELOADDELAY; if (enable_rpcbind != 0) { readfds = svc_fdset; switch (select(svc_maxfd + 1, &readfds, NULL, NULL, &tv)) { case -1: if (errno == EINTR) { /* Allow a reload now. */ nexttime = 0; continue; } syslog(LOG_ERR, "mountd died: select: %m"); exit(1); case 0: /* Allow a reload now. */ nexttime = 0; continue; default: svc_getreqset(&readfds); } } else { /* Simply wait for a signal. */ sigsuspend(&sig_mask); } } } /* * This routine creates and binds sockets on the appropriate * addresses. It gets called one time for each transport. * It returns 0 upon success, 1 for ingore the call and -1 to indicate * bind failed with EADDRINUSE. * Any file descriptors that have been created are stored in sock_fd and * the total count of them is maintained in sock_fdcnt. */ static int create_service(struct netconfig *nconf) { struct addrinfo hints, *res = NULL; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct __rpc_sockinfo si; int aicode; int fd; int nhostsbak; int one = 1; int r; u_int32_t host_addr[4]; /* IPv4 or IPv6 */ int mallocd_res; if ((nconf->nc_semantics != NC_TPI_CLTS) && (nconf->nc_semantics != NC_TPI_COTS) && (nconf->nc_semantics != NC_TPI_COTS_ORD)) return (1); /* not my type */ /* * XXX - using RPC library internal functions. */ if (!__rpc_nconf2sockinfo(nconf, &si)) { syslog(LOG_ERR, "cannot get information for %s", nconf->nc_netid); return (1); } /* Get mountd's address on this transport */ memset(&hints, 0, sizeof hints); hints.ai_family = si.si_af; hints.ai_socktype = si.si_socktype; hints.ai_protocol = si.si_proto; /* * Bind to specific IPs if asked to */ nhostsbak = nhosts; while (nhostsbak > 0) { --nhostsbak; sock_fd = realloc(sock_fd, (sock_fdcnt + 1) * sizeof(int)); if (sock_fd == NULL) out_of_mem(); sock_fd[sock_fdcnt++] = -1; /* Set invalid for now. */ mallocd_res = 0; hints.ai_flags = AI_PASSIVE; /* * XXX - using RPC library internal functions. */ if ((fd = __rpc_nconf2fd(nconf)) < 0) { int non_fatal = 0; if (errno == EAFNOSUPPORT && nconf->nc_semantics != NC_TPI_CLTS) non_fatal = 1; syslog(non_fatal ? LOG_DEBUG : LOG_ERR, "cannot create socket for %s", nconf->nc_netid); if (non_fatal != 0) continue; exit(1); } switch (hints.ai_family) { case AF_INET: if (inet_pton(AF_INET, hosts[nhostsbak], host_addr) == 1) { hints.ai_flags |= AI_NUMERICHOST; } else { /* * Skip if we have an AF_INET6 address. */ if (inet_pton(AF_INET6, hosts[nhostsbak], host_addr) == 1) { close(fd); continue; } } break; case AF_INET6: if (inet_pton(AF_INET6, hosts[nhostsbak], host_addr) == 1) { hints.ai_flags |= AI_NUMERICHOST; } else { /* * Skip if we have an AF_INET address. */ if (inet_pton(AF_INET, hosts[nhostsbak], host_addr) == 1) { close(fd); continue; } } /* * We're doing host-based access checks here, so don't * allow v4-in-v6 to confuse things. The kernel will * disable it by default on NFS sockets too. */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof one) < 0) { syslog(LOG_ERR, "can't disable v4-in-v6 on IPv6 socket"); exit(1); } break; default: break; } /* * If no hosts were specified, just bind to INADDR_ANY */ if (strcmp("*", hosts[nhostsbak]) == 0) { if (svcport_str == NULL) { res = malloc(sizeof(struct addrinfo)); if (res == NULL) out_of_mem(); mallocd_res = 1; res->ai_flags = hints.ai_flags; res->ai_family = hints.ai_family; res->ai_protocol = hints.ai_protocol; switch (res->ai_family) { case AF_INET: sin = malloc(sizeof(struct sockaddr_in)); if (sin == NULL) out_of_mem(); sin->sin_family = AF_INET; sin->sin_port = htons(0); sin->sin_addr.s_addr = htonl(INADDR_ANY); res->ai_addr = (struct sockaddr*) sin; res->ai_addrlen = (socklen_t) sizeof(struct sockaddr_in); break; case AF_INET6: sin6 = malloc(sizeof(struct sockaddr_in6)); if (sin6 == NULL) out_of_mem(); sin6->sin6_family = AF_INET6; sin6->sin6_port = htons(0); sin6->sin6_addr = in6addr_any; res->ai_addr = (struct sockaddr*) sin6; res->ai_addrlen = (socklen_t) sizeof(struct sockaddr_in6); break; default: syslog(LOG_ERR, "bad addr fam %d", res->ai_family); exit(1); } } else { if ((aicode = getaddrinfo(NULL, svcport_str, &hints, &res)) != 0) { syslog(LOG_ERR, "cannot get local address for %s: %s", nconf->nc_netid, gai_strerror(aicode)); close(fd); continue; } } } else { if ((aicode = getaddrinfo(hosts[nhostsbak], svcport_str, &hints, &res)) != 0) { syslog(LOG_ERR, "cannot get local address for %s: %s", nconf->nc_netid, gai_strerror(aicode)); close(fd); continue; } } /* Store the fd. */ sock_fd[sock_fdcnt - 1] = fd; /* Now, attempt the bind. */ r = bindresvport_sa(fd, res->ai_addr); if (r != 0) { if (errno == EADDRINUSE && mallocd_svcport != 0) { if (mallocd_res != 0) { free(res->ai_addr); free(res); } else freeaddrinfo(res); return (-1); } syslog(LOG_ERR, "bindresvport_sa: %m"); exit(1); } if (svcport_str == NULL) { svcport_str = malloc(NI_MAXSERV * sizeof(char)); if (svcport_str == NULL) out_of_mem(); mallocd_svcport = 1; if (getnameinfo(res->ai_addr, res->ai_addr->sa_len, NULL, NI_MAXHOST, svcport_str, NI_MAXSERV * sizeof(char), NI_NUMERICHOST | NI_NUMERICSERV)) errx(1, "Cannot get port number"); } if (mallocd_res != 0) { free(res->ai_addr); free(res); } else freeaddrinfo(res); res = NULL; } return (0); } /* * Called after all the create_service() calls have succeeded, to complete * the setup and registration. */ static void complete_service(struct netconfig *nconf, char *port_str) { struct addrinfo hints, *res = NULL; struct __rpc_sockinfo si; struct netbuf servaddr; SVCXPRT *transp = NULL; int aicode, fd, nhostsbak; int registered = 0; if ((nconf->nc_semantics != NC_TPI_CLTS) && (nconf->nc_semantics != NC_TPI_COTS) && (nconf->nc_semantics != NC_TPI_COTS_ORD)) return; /* not my type */ /* * XXX - using RPC library internal functions. */ if (!__rpc_nconf2sockinfo(nconf, &si)) { syslog(LOG_ERR, "cannot get information for %s", nconf->nc_netid); return; } nhostsbak = nhosts; while (nhostsbak > 0) { --nhostsbak; if (sock_fdpos >= sock_fdcnt) { /* Should never happen. */ syslog(LOG_ERR, "Ran out of socket fd's"); return; } fd = sock_fd[sock_fdpos++]; if (fd < 0) continue; /* * Using -1 tells listen(2) to use * kern.ipc.soacceptqueue for the backlog. */ if (nconf->nc_semantics != NC_TPI_CLTS) listen(fd, -1); if (nconf->nc_semantics == NC_TPI_CLTS ) transp = svc_dg_create(fd, 0, 0); else transp = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE); if (transp != (SVCXPRT *) NULL) { if (!svc_reg(transp, MOUNTPROG, MOUNTVERS, mntsrv, NULL)) syslog(LOG_ERR, "can't register %s MOUNTVERS service", nconf->nc_netid); if (!force_v2) { if (!svc_reg(transp, MOUNTPROG, MOUNTVERS3, mntsrv, NULL)) syslog(LOG_ERR, "can't register %s MOUNTVERS3 service", nconf->nc_netid); } } else syslog(LOG_WARNING, "can't create %s services", nconf->nc_netid); if (registered == 0) { registered = 1; memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = si.si_af; hints.ai_socktype = si.si_socktype; hints.ai_protocol = si.si_proto; if ((aicode = getaddrinfo(NULL, port_str, &hints, &res)) != 0) { syslog(LOG_ERR, "cannot get local address: %s", gai_strerror(aicode)); exit(1); } servaddr.buf = malloc(res->ai_addrlen); memcpy(servaddr.buf, res->ai_addr, res->ai_addrlen); servaddr.len = res->ai_addrlen; rpcb_set(MOUNTPROG, MOUNTVERS, nconf, &servaddr); rpcb_set(MOUNTPROG, MOUNTVERS3, nconf, &servaddr); xcreated++; freeaddrinfo(res); } } /* end while */ } /* * Clear out sockets after a failure to bind one of them, so that the * cycle of socket creation/binding can start anew. */ static void clearout_service(void) { int i; for (i = 0; i < sock_fdcnt; i++) { if (sock_fd[i] >= 0) { shutdown(sock_fd[i], SHUT_RDWR); close(sock_fd[i]); } } } static void usage(void) { fprintf(stderr, "usage: mountd [-2] [-d] [-e] [-l] [-n] [-p ] [-r] " "[-S] [-h ] [export_file ...]\n"); exit(1); } /* * The mount rpc service */ void mntsrv(struct svc_req *rqstp, SVCXPRT *transp) { struct exportlist *ep; struct dirlist *dp; struct fhreturn fhr; struct stat stb; struct statfs fsb; char host[NI_MAXHOST], numerichost[NI_MAXHOST]; int lookup_failed = 1; struct sockaddr *saddr; u_short sport; char rpcpath[MNTPATHLEN + 1], dirpath[MAXPATHLEN]; int defset, hostset; long bad = 0; sigset_t sighup_mask; int numsecflavors, *secflavorsp; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); saddr = svc_getrpccaller(transp)->buf; switch (saddr->sa_family) { case AF_INET6: sport = ntohs(((struct sockaddr_in6 *)saddr)->sin6_port); break; case AF_INET: sport = ntohs(((struct sockaddr_in *)saddr)->sin_port); break; default: syslog(LOG_ERR, "request from unknown address family"); return; } switch (rqstp->rq_proc) { case MOUNTPROC_MNT: case MOUNTPROC_UMNT: case MOUNTPROC_UMNTALL: lookup_failed = getnameinfo(saddr, saddr->sa_len, host, sizeof host, NULL, 0, 0); } getnameinfo(saddr, saddr->sa_len, numerichost, sizeof numerichost, NULL, 0, NI_NUMERICHOST); switch (rqstp->rq_proc) { case NULLPROC: if (!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL)) syslog(LOG_ERR, "can't send reply"); return; case MOUNTPROC_MNT: if (sport >= IPPORT_RESERVED && resvport_only) { syslog(LOG_NOTICE, "mount request from %s from unprivileged port", numerichost); svcerr_weakauth(transp); return; } if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) { syslog(LOG_NOTICE, "undecodable mount request from %s", numerichost); svcerr_decode(transp); return; } /* * Get the real pathname and make sure it is a directory * or a regular file if the -r option was specified * and it exists. */ if (realpath(rpcpath, dirpath) == NULL || stat(dirpath, &stb) < 0 || statfs(dirpath, &fsb) < 0) { chdir("/"); /* Just in case realpath doesn't */ syslog(LOG_NOTICE, "mount request from %s for non existent path %s", numerichost, dirpath); if (debug) warnx("stat failed on %s", dirpath); bad = ENOENT; /* We will send error reply later */ } if (!bad && !S_ISDIR(stb.st_mode) && (dir_only || !S_ISREG(stb.st_mode))) { syslog(LOG_NOTICE, "mount request from %s for non-directory path %s", numerichost, dirpath); if (debug) warnx("mounting non-directory %s", dirpath); bad = ENOTDIR; /* We will send error reply later */ } /* Check in the exports list */ sigprocmask(SIG_BLOCK, &sighup_mask, NULL); if (bad) ep = NULL; else ep = ex_search(&fsb.f_fsid, exphead); hostset = defset = 0; if (ep && (chk_host(ep->ex_defdir, saddr, &defset, &hostset, &numsecflavors, &secflavorsp) || ((dp = dirp_search(ep->ex_dirl, dirpath)) && chk_host(dp, saddr, &defset, &hostset, &numsecflavors, &secflavorsp)) || (defset && scan_tree(ep->ex_defdir, saddr) == 0 && scan_tree(ep->ex_dirl, saddr) == 0))) { if (bad) { if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "can't send reply"); sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; } if (hostset & DP_HOSTSET) { fhr.fhr_flag = hostset; fhr.fhr_numsecflavors = numsecflavors; fhr.fhr_secflavors = secflavorsp; } else { fhr.fhr_flag = defset; fhr.fhr_numsecflavors = ep->ex_defnumsecflavors; fhr.fhr_secflavors = ep->ex_defsecflavors; } fhr.fhr_vers = rqstp->rq_vers; /* Get the file handle */ memset(&fhr.fhr_fh, 0, sizeof(nfsfh_t)); if (getfh(dirpath, (fhandle_t *)&fhr.fhr_fh) < 0) { bad = errno; syslog(LOG_ERR, "can't get fh for %s", dirpath); if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "can't send reply"); sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_fhs, (caddr_t)&fhr)) syslog(LOG_ERR, "can't send reply"); if (!lookup_failed) add_mlist(host, dirpath); else add_mlist(numerichost, dirpath); if (debug) warnx("mount successful"); if (dolog) syslog(LOG_NOTICE, "mount request succeeded from %s for %s", numerichost, dirpath); } else { if (!bad) bad = EACCES; syslog(LOG_NOTICE, "mount request denied from %s for %s", numerichost, dirpath); } if (bad && !svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "can't send reply"); sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; case MOUNTPROC_DUMP: if (!svc_sendreply(transp, (xdrproc_t)xdr_mlist, (caddr_t)NULL)) syslog(LOG_ERR, "can't send reply"); else if (dolog) syslog(LOG_NOTICE, "dump request succeeded from %s", numerichost); return; case MOUNTPROC_UMNT: if (sport >= IPPORT_RESERVED && resvport_only) { syslog(LOG_NOTICE, "umount request from %s from unprivileged port", numerichost); svcerr_weakauth(transp); return; } if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) { syslog(LOG_NOTICE, "undecodable umount request from %s", numerichost); svcerr_decode(transp); return; } if (realpath(rpcpath, dirpath) == NULL) { syslog(LOG_NOTICE, "umount request from %s " "for non existent path %s", numerichost, dirpath); } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) syslog(LOG_ERR, "can't send reply"); if (!lookup_failed) del_mlist(host, dirpath); del_mlist(numerichost, dirpath); if (dolog) syslog(LOG_NOTICE, "umount request succeeded from %s for %s", numerichost, dirpath); return; case MOUNTPROC_UMNTALL: if (sport >= IPPORT_RESERVED && resvport_only) { syslog(LOG_NOTICE, "umountall request from %s from unprivileged port", numerichost); svcerr_weakauth(transp); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) syslog(LOG_ERR, "can't send reply"); if (!lookup_failed) del_mlist(host, NULL); del_mlist(numerichost, NULL); if (dolog) syslog(LOG_NOTICE, "umountall request succeeded from %s", numerichost); return; case MOUNTPROC_EXPORT: if (!svc_sendreply(transp, (xdrproc_t)xdr_explist, (caddr_t)NULL)) if (!svc_sendreply(transp, (xdrproc_t)xdr_explist_brief, (caddr_t)NULL)) syslog(LOG_ERR, "can't send reply"); if (dolog) syslog(LOG_NOTICE, "export request succeeded from %s", numerichost); return; default: svcerr_noproc(transp); return; } } /* * Xdr conversion for a dirpath string */ static int xdr_dir(XDR *xdrsp, char *dirp) { return (xdr_string(xdrsp, &dirp, MNTPATHLEN)); } /* * Xdr routine to generate file handle reply */ static int xdr_fhs(XDR *xdrsp, caddr_t cp) { struct fhreturn *fhrp = (struct fhreturn *)cp; u_long ok = 0, len, auth; int i; if (!xdr_long(xdrsp, &ok)) return (0); switch (fhrp->fhr_vers) { case 1: return (xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, NFSX_V2FH)); case 3: len = NFSX_V3FH; if (!xdr_long(xdrsp, &len)) return (0); if (!xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, len)) return (0); if (fhrp->fhr_numsecflavors) { if (!xdr_int(xdrsp, &fhrp->fhr_numsecflavors)) return (0); for (i = 0; i < fhrp->fhr_numsecflavors; i++) if (!xdr_int(xdrsp, &fhrp->fhr_secflavors[i])) return (0); return (1); } else { auth = AUTH_SYS; len = 1; if (!xdr_long(xdrsp, &len)) return (0); return (xdr_long(xdrsp, &auth)); } } return (0); } static int xdr_mlist(XDR *xdrsp, caddr_t cp __unused) { struct mountlist *mlp; int true = 1; int false = 0; char *strp; SLIST_FOREACH(mlp, &mlhead, next) { if (!xdr_bool(xdrsp, &true)) return (0); strp = &mlp->ml_host[0]; if (!xdr_string(xdrsp, &strp, MNTNAMLEN)) return (0); strp = &mlp->ml_dirp[0]; if (!xdr_string(xdrsp, &strp, MNTPATHLEN)) return (0); } if (!xdr_bool(xdrsp, &false)) return (0); return (1); } /* * Xdr conversion for export list */ static int xdr_explist_common(XDR *xdrsp, caddr_t cp __unused, int brief) { struct exportlist *ep; int false = 0; int putdef; sigset_t sighup_mask; int i; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); sigprocmask(SIG_BLOCK, &sighup_mask, NULL); for (i = 0; i < exphashsize; i++) SLIST_FOREACH(ep, &exphead[i], entries) { putdef = 0; if (put_exlist(ep->ex_dirl, xdrsp, ep->ex_defdir, &putdef, brief)) goto errout; if (ep->ex_defdir && putdef == 0 && put_exlist(ep->ex_defdir, xdrsp, NULL, &putdef, brief)) goto errout; } sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); if (!xdr_bool(xdrsp, &false)) return (0); return (1); errout: sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return (0); } /* * Called from xdr_explist() to traverse the tree and export the * directory paths. */ static int put_exlist(struct dirlist *dp, XDR *xdrsp, struct dirlist *adp, int *putdefp, int brief) { struct grouplist *grp; struct hostlist *hp; int true = 1; int false = 0; int gotalldir = 0; char *strp; if (dp) { if (put_exlist(dp->dp_left, xdrsp, adp, putdefp, brief)) return (1); if (!xdr_bool(xdrsp, &true)) return (1); strp = dp->dp_dirp; if (!xdr_string(xdrsp, &strp, MNTPATHLEN)) return (1); if (adp && !strcmp(dp->dp_dirp, adp->dp_dirp)) { gotalldir = 1; *putdefp = 1; } if (brief) { if (!xdr_bool(xdrsp, &true)) return (1); strp = "(...)"; if (!xdr_string(xdrsp, &strp, MNTPATHLEN)) return (1); } else if ((dp->dp_flag & DP_DEFSET) == 0 && (gotalldir == 0 || (adp->dp_flag & DP_DEFSET) == 0)) { hp = dp->dp_hosts; while (hp) { grp = hp->ht_grp; if (grp->gr_type == GT_HOST) { if (!xdr_bool(xdrsp, &true)) return (1); strp = grp->gr_ptr.gt_addrinfo->ai_canonname; if (!xdr_string(xdrsp, &strp, MNTNAMLEN)) return (1); } else if (grp->gr_type == GT_NET) { if (!xdr_bool(xdrsp, &true)) return (1); strp = grp->gr_ptr.gt_net.nt_name; if (!xdr_string(xdrsp, &strp, MNTNAMLEN)) return (1); } hp = hp->ht_next; if (gotalldir && hp == (struct hostlist *)NULL) { hp = adp->dp_hosts; gotalldir = 0; } } } if (!xdr_bool(xdrsp, &false)) return (1); if (put_exlist(dp->dp_right, xdrsp, adp, putdefp, brief)) return (1); } return (0); } static int xdr_explist(XDR *xdrsp, caddr_t cp) { return xdr_explist_common(xdrsp, cp, 0); } static int xdr_explist_brief(XDR *xdrsp, caddr_t cp) { return xdr_explist_common(xdrsp, cp, 1); } static char *line; static size_t linesize; static FILE *exp_file; /* * Get the export list from one, currently open file */ static void get_exportlist_one(int passno) { struct exportlist *ep; struct grouplist *grp, *tgrp, *savgrp; struct dirlist *dirhead; struct statfs fsb; struct expcred anon; char *cp, *endcp, *dirp, *hst, *usr, *dom, savedc; int len, has_host, got_nondir, dirplen, netgrp; uint64_t exflags; v4root_phase = 0; anon.cr_groups = NULL; dirhead = (struct dirlist *)NULL; while (get_line()) { if (debug) warnx("got line %s", line); cp = line; nextfield(&cp, &endcp); if (*cp == '#') goto nextline; /* * Set defaults. */ has_host = FALSE; anon.cr_groups = anon.cr_smallgrps; anon.cr_uid = UID_NOBODY; anon.cr_ngroups = 1; anon.cr_groups[0] = GID_NOGROUP; exflags = MNT_EXPORTED; got_nondir = 0; opt_flags = 0; ep = (struct exportlist *)NULL; dirp = NULL; /* * Handle the V4 root dir. */ if (*cp == 'V' && *(cp + 1) == '4' && *(cp + 2) == ':') { /* * V4: just indicates that it is the v4 root point, * so skip over that and set v4root_phase. */ if (v4root_phase > 0) { syslog(LOG_ERR, "V4:duplicate line, ignored"); goto nextline; } v4root_phase = 1; cp += 3; nextfield(&cp, &endcp); } /* * Create new exports list entry */ len = endcp-cp; tgrp = grp = get_grp(); while (len > 0) { if (len > MNTNAMLEN) { getexp_err(ep, tgrp, "mountpoint too long"); goto nextline; } if (*cp == '-') { if (ep == (struct exportlist *)NULL) { getexp_err(ep, tgrp, "flag before export path definition"); goto nextline; } if (debug) warnx("doing opt %s", cp); got_nondir = 1; if (do_opt(&cp, &endcp, ep, grp, &has_host, &exflags, &anon)) { getexp_err(ep, tgrp, NULL); goto nextline; } } else if (*cp == '/') { savedc = *endcp; *endcp = '\0'; if (v4root_phase > 1) { if (dirp != NULL) { getexp_err(ep, tgrp, "Multiple V4 dirs"); goto nextline; } } if (check_dirpath(cp) && statfs(cp, &fsb) >= 0) { if ((fsb.f_flags & MNT_AUTOMOUNTED) != 0) syslog(LOG_ERR, "Warning: exporting of " "automounted fs %s not supported", cp); if (got_nondir) { getexp_err(ep, tgrp, "dirs must be first"); goto nextline; } if (v4root_phase == 1) { if (dirp != NULL) { getexp_err(ep, tgrp, "Multiple V4 dirs"); goto nextline; } if (strlen(v4root_dirpath) == 0) { strlcpy(v4root_dirpath, cp, sizeof (v4root_dirpath)); } else if (strcmp(v4root_dirpath, cp) != 0) { syslog(LOG_ERR, "different V4 dirpath %s", cp); getexp_err(ep, tgrp, NULL); goto nextline; } dirp = cp; v4root_phase = 2; got_nondir = 1; ep = get_exp(); } else { if (ep) { if (fsidcmp(&ep->ex_fs, &fsb.f_fsid) != 0) { getexp_err(ep, tgrp, "fsid mismatch"); goto nextline; } } else { /* * See if this directory is already * in the list. */ ep = ex_search(&fsb.f_fsid, exphead); if (ep == (struct exportlist *)NULL) { ep = get_exp(); ep->ex_fs = fsb.f_fsid; ep->ex_fsdir = strdup(fsb.f_mntonname); if (ep->ex_fsdir == NULL) out_of_mem(); if (debug) warnx( "making new ep fs=0x%x,0x%x", fsb.f_fsid.val[0], fsb.f_fsid.val[1]); } else if (debug) warnx("found ep fs=0x%x,0x%x", fsb.f_fsid.val[0], fsb.f_fsid.val[1]); } /* * Add dirpath to export mount point. */ dirp = add_expdir(&dirhead, cp, len); dirplen = len; } } else { getexp_err(ep, tgrp, "symbolic link in export path or statfs failed"); goto nextline; } *endcp = savedc; } else { savedc = *endcp; *endcp = '\0'; got_nondir = 1; if (ep == (struct exportlist *)NULL) { getexp_err(ep, tgrp, "host(s) before export path definition"); goto nextline; } /* * Get the host or netgroup. */ setnetgrent(cp); netgrp = getnetgrent(&hst, &usr, &dom); do { if (has_host) { grp->gr_next = get_grp(); grp = grp->gr_next; } if (netgrp) { if (hst == 0) { syslog(LOG_ERR, "null hostname in netgroup %s, skipping", cp); grp->gr_type = GT_IGNORE; } else if (get_host(hst, grp, tgrp)) { syslog(LOG_ERR, "bad host %s in netgroup %s, skipping", hst, cp); grp->gr_type = GT_IGNORE; } } else if (get_host(cp, grp, tgrp)) { syslog(LOG_ERR, "bad host %s, skipping", cp); grp->gr_type = GT_IGNORE; } has_host = TRUE; } while (netgrp && getnetgrent(&hst, &usr, &dom)); endnetgrent(); *endcp = savedc; } cp = endcp; nextfield(&cp, &endcp); len = endcp - cp; } if (check_options(dirhead)) { getexp_err(ep, tgrp, NULL); goto nextline; } if (!has_host) { grp->gr_type = GT_DEFAULT; if (debug) warnx("adding a default entry"); /* * Don't allow a network export coincide with a list of * host(s) on the same line. */ } else if ((opt_flags & OP_NET) && tgrp->gr_next) { getexp_err(ep, tgrp, "network/host conflict"); goto nextline; /* * If an export list was specified on this line, make sure * that we have at least one valid entry, otherwise skip it. */ } else { grp = tgrp; while (grp && grp->gr_type == GT_IGNORE) grp = grp->gr_next; if (! grp) { getexp_err(ep, tgrp, "no valid entries"); goto nextline; } } if (v4root_phase == 1) { getexp_err(ep, tgrp, "V4:root, no dirp, ignored"); goto nextline; } /* * Loop through hosts, pushing the exports into the kernel. * After loop, tgrp points to the start of the list and * grp points to the last entry in the list. * Do not do the do_mount() for passno == 1, since the * second pass will do it, as required. */ grp = tgrp; do { grp->gr_exflags = exflags; cp_cred(&grp->gr_anon, &anon); if (v4root_phase == 2 && passno == 0) LOGDEBUG("do_mount v4root"); if (passno == 0 && do_mount(ep, grp, exflags, &anon, dirp, dirplen, &fsb, ep->ex_numsecflavors, ep->ex_secflavors)) { getexp_err(ep, tgrp, NULL); goto nextline; } } while (grp->gr_next && (grp = grp->gr_next)); /* * For V4: don't enter in mount lists. */ if (v4root_phase > 0 && v4root_phase <= 2) { /* * These structures are used for the reload, * so save them for that case. Otherwise, just * free them up now. */ if (passno == 1 && ep != NULL) { savgrp = tgrp; while (tgrp != NULL) { /* * Save the security flavors and exflags * for this host set in the groups. */ tgrp->gr_numsecflavors = ep->ex_numsecflavors; if (ep->ex_numsecflavors > 0) memcpy(tgrp->gr_secflavors, ep->ex_secflavors, sizeof(ep->ex_secflavors)); tgrp = tgrp->gr_next; } if (v4root_ep == NULL) { v4root_ep = ep; ep = NULL; /* Don't free below. */ } grp->gr_next = v4root_ep->ex_grphead; v4root_ep->ex_grphead = savgrp; } if (ep != NULL) free_exp(ep); while (tgrp != NULL) { grp = tgrp; tgrp = tgrp->gr_next; free_grp(grp); } goto nextline; } /* * Success. Update the data structures. */ if (has_host) { hang_dirp(dirhead, tgrp, ep, opt_flags, &anon, exflags); grp->gr_next = ep->ex_grphead; ep->ex_grphead = tgrp; } else { hang_dirp(dirhead, (struct grouplist *)NULL, ep, opt_flags, &anon, exflags); free_grp(grp); } dirhead = (struct dirlist *)NULL; if ((ep->ex_flag & EX_LINKED) == 0) { insert_exports(ep, exphead); ep->ex_flag |= EX_LINKED; } nextline: v4root_phase = 0; if (dirhead) { free_dir(dirhead); dirhead = (struct dirlist *)NULL; } if (anon.cr_groups != anon.cr_smallgrps) { free(anon.cr_groups); anon.cr_groups = NULL; } } } /* * Get the export list from all specified files */ static void get_exportlist(int passno) { struct export_args export; struct iovec *iov; struct statfs *mntbufp; char errmsg[255]; int error, i, nfs_maxvers, num; int iovlen; struct nfsex_args eargs; FILE *debug_file; size_t nfs_maxvers_size; if ((debug_file = fopen(_PATH_MOUNTDDEBUG, "r")) != NULL) { fclose(debug_file); logdebug = 1; } else logdebug = 0; LOGDEBUG("passno=%d", passno); v4root_dirpath[0] = '\0'; free_v4rootexp(); if (passno == 1) { /* * Save the current lists as old ones, so that the new lists * can be compared with the old ones in the 2nd pass. */ for (i = 0; i < exphashsize; i++) { SLIST_FIRST(&oldexphead[i]) = SLIST_FIRST(&exphead[i]); SLIST_INIT(&exphead[i]); } /* Note that the public fh has not yet been set. */ has_set_publicfh = 0; /* Read the export file(s) and process them */ read_exportfile(passno); } else { /* * Just make the old lists empty. * exphashsize == 0 for the first call, before oldexphead * has been initialized-->loop won't be executed. */ for (i = 0; i < exphashsize; i++) SLIST_INIT(&oldexphead[i]); } bzero(&export, sizeof(export)); export.ex_flags = MNT_DELEXPORT; iov = NULL; iovlen = 0; bzero(errmsg, sizeof(errmsg)); if (suspend_nfsd != 0) (void)nfssvc(NFSSVC_SUSPENDNFSD, NULL); /* * Delete the old V4 root dir. */ bzero(&eargs, sizeof (eargs)); eargs.export.ex_flags = MNT_DELEXPORT; if (nfssvc(NFSSVC_V4ROOTEXPORT | NFSSVC_NEWSTRUCT, (caddr_t)&eargs) < 0 && errno != ENOENT) syslog(LOG_ERR, "Can't delete exports for V4:"); build_iovec(&iov, &iovlen, "fstype", NULL, 0); build_iovec(&iov, &iovlen, "fspath", NULL, 0); build_iovec(&iov, &iovlen, "from", NULL, 0); build_iovec(&iov, &iovlen, "update", NULL, 0); build_iovec(&iov, &iovlen, "export", &export, sizeof(export)); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); /* * For passno == 1, compare the old and new lists updating the kernel * exports for any cases that have changed. * This call is doing the second pass through the lists. * If it fails, fall back on the bulk reload. */ if (passno == 1 && compare_nmount_exportlist(iov, iovlen, errmsg) == 0) { LOGDEBUG("compareok"); /* Free up the old lists. */ free_exports(oldexphead); } else { LOGDEBUG("doing passno=0"); /* * Clear flag that notes if a public fh has been exported. * It is set by do_mount() if MNT_EXPUBLIC is set for the entry. */ has_publicfh = 0; /* exphead == NULL if not yet allocated (first call). */ if (exphead != NULL) { /* * First, get rid of the old lists. */ free_exports(exphead); free_exports(oldexphead); } /* * And delete exports that are in the kernel for all local * filesystems. * XXX: Should know how to handle all local exportable * filesystems. */ num = getmntinfo(&mntbufp, MNT_NOWAIT); /* Allocate hash tables, for first call. */ if (exphead == NULL) { /* Target an average linked list length of 10. */ exphashsize = num / 10; if (exphashsize < 1) exphashsize = 1; else if (exphashsize > 100000) exphashsize = 100000; exphead = malloc(exphashsize * sizeof(*exphead)); oldexphead = malloc(exphashsize * sizeof(*oldexphead)); if (exphead == NULL || oldexphead == NULL) errx(1, "Can't malloc hash tables"); for (i = 0; i < exphashsize; i++) { SLIST_INIT(&exphead[i]); SLIST_INIT(&oldexphead[i]); } } for (i = 0; i < num; i++) delete_export(iov, iovlen, &mntbufp[i], errmsg); /* Read the export file(s) and process them */ read_exportfile(0); } if (strlen(v4root_dirpath) == 0) { /* Check to see if a V4: line is needed. */ nfs_maxvers_size = sizeof(nfs_maxvers); error = sysctlbyname("vfs.nfsd.server_max_nfsvers", &nfs_maxvers, &nfs_maxvers_size, NULL, 0); if (error != 0 || nfs_maxvers < NFS_VER2 || nfs_maxvers > NFS_VER4) { syslog(LOG_ERR, "sysctlbyname(vfs.nfsd." "server_max_nfsvers) failed, defaulting to NFSv3"); nfs_maxvers = NFS_VER3; } if (nfs_maxvers == NFS_VER4) syslog(LOG_ERR, "NFSv4 requires at least one V4: line"); } if (iov != NULL) { /* Free strings allocated by strdup() in getmntopts.c */ free(iov[0].iov_base); /* fstype */ free(iov[2].iov_base); /* fspath */ free(iov[4].iov_base); /* from */ free(iov[6].iov_base); /* update */ free(iov[8].iov_base); /* export */ free(iov[10].iov_base); /* errmsg */ /* free iov, allocated by realloc() */ free(iov); iovlen = 0; } /* * If there was no public fh, clear any previous one set. */ if (has_publicfh == 0) { LOGDEBUG("clear public fh"); (void) nfssvc(NFSSVC_NOPUBLICFH, NULL); } /* Resume the nfsd. If they weren't suspended, this is harmless. */ (void)nfssvc(NFSSVC_RESUMENFSD, NULL); LOGDEBUG("eo get_exportlist"); } /* * Insert an export entry in the appropriate list. */ static void insert_exports(struct exportlist *ep, struct exportlisthead *exhp) { uint32_t i; i = EXPHASH(&ep->ex_fs); LOGDEBUG("fs=%s hash=%i", ep->ex_fsdir, i); SLIST_INSERT_HEAD(&exhp[i], ep, entries); } /* * Free up the exports lists passed in as arguments. */ static void free_exports(struct exportlisthead *exhp) { struct exportlist *ep, *ep2; int i; for (i = 0; i < exphashsize; i++) { SLIST_FOREACH_SAFE(ep, &exhp[i], entries, ep2) { SLIST_REMOVE(&exhp[i], ep, exportlist, entries); free_exp(ep); } SLIST_INIT(&exhp[i]); } } /* * Read the exports file(s) and call get_exportlist_one() for each line. */ static void read_exportfile(int passno) { int done, i; /* * Read in the exports file and build the list, calling * nmount() as we go along to push the export rules into the kernel. */ done = 0; for (i = 0; exnames[i] != NULL; i++) { if (debug) warnx("reading exports from %s", exnames[i]); if ((exp_file = fopen(exnames[i], "r")) == NULL) { syslog(LOG_WARNING, "can't open %s", exnames[i]); continue; } get_exportlist_one(passno); fclose(exp_file); done++; } if (done == 0) { syslog(LOG_ERR, "can't open any exports file"); exit(2); } } /* * Compare the export lists against the old ones and do nmount() operations * for any cases that have changed. This avoids doing nmount() for entries * that have not changed. * Return 0 upon success, 1 otherwise. */ static int compare_nmount_exportlist(struct iovec *iov, int iovlen, char *errmsg) { struct exportlist *ep, *oep; struct grouplist *grp; struct statfs fs, ofs; int i, ret; /* * Loop through the current list and look for an entry in the old * list. * If found, check to see if it the same. * If it is not the same, delete and re-export. * Then mark it done on the old list. * else (not found) * export it. * Any entries left in the old list after processing must have their * exports deleted. */ for (i = 0; i < exphashsize; i++) SLIST_FOREACH(ep, &exphead[i], entries) { LOGDEBUG("foreach ep=%s", ep->ex_fsdir); oep = ex_search(&ep->ex_fs, oldexphead); if (oep != NULL) { /* * Check the mount paths are the same. * If not, return 1 so that the reload of the * exports will be done in bulk, the * passno == 0 way. */ LOGDEBUG("found old exp"); if (strcmp(ep->ex_fsdir, oep->ex_fsdir) != 0) return (1); LOGDEBUG("same fsdir"); /* * Test to see if the entry is the same. * If not the same delete exports and * re-export. */ if (compare_export(ep, oep) != 0) { /* * Clear has_publicfh if if was set * in the old exports, but only if it * has not been set during processing of * the exports for this pass, as * indicated by has_set_publicfh. */ if (has_set_publicfh == 0 && (oep->ex_flag & EX_PUBLICFH) != 0) has_publicfh = 0; /* Delete and re-export. */ if (statfs(ep->ex_fsdir, &fs) < 0) return (1); delete_export(iov, iovlen, &fs, errmsg); ret = do_export_mount(ep, &fs); if (ret != 0) return (ret); } oep->ex_flag |= EX_DONE; LOGDEBUG("exdone"); } else { LOGDEBUG("not found so export"); /* Not found, so do export. */ if (statfs(ep->ex_fsdir, &fs) < 0) return (1); ret = do_export_mount(ep, &fs); if (ret != 0) return (ret); } } /* Delete exports not done. */ for (i = 0; i < exphashsize; i++) SLIST_FOREACH(oep, &oldexphead[i], entries) { if ((oep->ex_flag & EX_DONE) == 0) { LOGDEBUG("not done delete=%s", oep->ex_fsdir); if (statfs(oep->ex_fsdir, &ofs) >= 0 && fsidcmp(&oep->ex_fs, &ofs.f_fsid) == 0) { LOGDEBUG("do delete"); /* * Clear has_publicfh if if was set * in the old exports, but only if it * has not been set during processing of * the exports for this pass, as * indicated by has_set_publicfh. */ if (has_set_publicfh == 0 && (oep->ex_flag & EX_PUBLICFH) != 0) has_publicfh = 0; delete_export(iov, iovlen, &ofs, errmsg); } } } /* Do the V4 root exports, as required. */ grp = NULL; if (v4root_ep != NULL) grp = v4root_ep->ex_grphead; v4root_phase = 2; while (v4root_ep != NULL && grp != NULL) { LOGDEBUG("v4root expath=%s", v4root_dirpath); ret = do_mount(v4root_ep, grp, grp->gr_exflags, &grp->gr_anon, v4root_dirpath, strlen(v4root_dirpath), &fs, grp->gr_numsecflavors, grp->gr_secflavors); if (ret != 0) { v4root_phase = 0; return (ret); } grp = grp->gr_next; } v4root_phase = 0; free_v4rootexp(); return (0); } /* * Compare old and current exportlist entries for the fsid and return 0 * if they are the same, 1 otherwise. */ static int compare_export(struct exportlist *ep, struct exportlist *oep) { struct grouplist *grp, *ogrp; if (strcmp(ep->ex_fsdir, oep->ex_fsdir) != 0) return (1); if ((ep->ex_flag & EX_DEFSET) != (oep->ex_flag & EX_DEFSET)) return (1); if ((ep->ex_defdir != NULL && oep->ex_defdir == NULL) || (ep->ex_defdir == NULL && oep->ex_defdir != NULL)) return (1); if (ep->ex_defdir != NULL && (ep->ex_defdir->dp_flag & DP_DEFSET) != (oep->ex_defdir->dp_flag & DP_DEFSET)) return (1); if ((ep->ex_flag & EX_DEFSET) != 0 && (ep->ex_defnumsecflavors != oep->ex_defnumsecflavors || ep->ex_defexflags != oep->ex_defexflags || compare_cred(&ep->ex_defanon, &oep->ex_defanon) != 0 || compare_secflavor(ep->ex_defsecflavors, oep->ex_defsecflavors, ep->ex_defnumsecflavors) != 0)) return (1); /* Now, check all the groups. */ for (ogrp = oep->ex_grphead; ogrp != NULL; ogrp = ogrp->gr_next) ogrp->gr_flag = 0; for (grp = ep->ex_grphead; grp != NULL; grp = grp->gr_next) { for (ogrp = oep->ex_grphead; ogrp != NULL; ogrp = ogrp->gr_next) if ((ogrp->gr_flag & GR_FND) == 0 && grp->gr_numsecflavors == ogrp->gr_numsecflavors && grp->gr_exflags == ogrp->gr_exflags && compare_cred(&grp->gr_anon, &ogrp->gr_anon) == 0 && compare_secflavor(grp->gr_secflavors, ogrp->gr_secflavors, grp->gr_numsecflavors) == 0) break; if (ogrp != NULL) ogrp->gr_flag |= GR_FND; else return (1); } for (ogrp = oep->ex_grphead; ogrp != NULL; ogrp = ogrp->gr_next) if ((ogrp->gr_flag & GR_FND) == 0) return (1); return (0); } /* * This algorithm compares two arrays of "n" items. It returns 0 if they are * the "same" and 1 otherwise. Although suboptimal, it is always safe to * return 1, which makes compare_nmount_export() reload the exports entry. * "same" refers to having the same set of values in the two arrays. * The arrays are in no particular order and duplicates (multiple entries * in an array with the same value) is allowed. * The algorithm is inefficient, but the common case of indentical arrays is * handled first and "n" is normally fairly small. * Since the two functions need the same algorithm but for arrays of * different types (gid_t vs int), this is done as a macro. */ #define COMPARE_ARRAYS(a1, a2, n) \ do { \ int fnd, fndarray[(n)], i, j; \ /* Handle common case of identical arrays. */ \ for (i = 0; i < (n); i++) \ if ((a1)[i] != (a2)[i]) \ break; \ if (i == (n)) \ return (0); \ for (i = 0; i < (n); i++) \ fndarray[i] = 0; \ for (i = 0; i < (n); i++) { \ fnd = 0; \ for (j = 0; j < (n); j++) { \ if ((a1)[i] == (a2)[j]) { \ fndarray[j] = 1; \ fnd = 1; \ } \ } \ if (fnd == 0) \ return (1); \ } \ for (i = 0; i < (n); i++) \ if (fndarray[i] == 0) \ return (1); \ return (0); \ } while (0) /* * Compare two struct expcred's. Return 0 if the same and 1 otherwise. */ static int compare_cred(struct expcred *cr0, struct expcred *cr1) { if (cr0->cr_uid != cr1->cr_uid || cr0->cr_ngroups != cr1->cr_ngroups) return (1); COMPARE_ARRAYS(cr0->cr_groups, cr1->cr_groups, cr0->cr_ngroups); } /* * Compare two lists of security flavors. Return 0 if the same and 1 otherwise. */ static int compare_secflavor(int *sec1, int *sec2, int nsec) { COMPARE_ARRAYS(sec1, sec2, nsec); } /* * Delete an exports entry. */ static void delete_export(struct iovec *iov, int iovlen, struct statfs *fsp, char *errmsg) { struct xvfsconf vfc; if (getvfsbyname(fsp->f_fstypename, &vfc) != 0) { syslog(LOG_ERR, "getvfsbyname() failed for %s", fsp->f_fstypename); return; } /* * We do not need to delete "export" flag from * filesystems that do not have it set. */ if (!(fsp->f_flags & MNT_EXPORTED)) return; /* * Do not delete export for network filesystem by * passing "export" arg to nmount(). * It only makes sense to do this for local filesystems. */ if (vfc.vfc_flags & VFCF_NETWORK) return; iov[1].iov_base = fsp->f_fstypename; iov[1].iov_len = strlen(fsp->f_fstypename) + 1; iov[3].iov_base = fsp->f_mntonname; iov[3].iov_len = strlen(fsp->f_mntonname) + 1; iov[5].iov_base = fsp->f_mntfromname; iov[5].iov_len = strlen(fsp->f_mntfromname) + 1; errmsg[0] = '\0'; /* * EXDEV is returned when path exists but is not a * mount point. May happens if raced with unmount. */ if (nmount(iov, iovlen, fsp->f_flags) < 0 && errno != ENOENT && errno != ENOTSUP && errno != EXDEV) { syslog(LOG_ERR, "can't delete exports for %s: %m %s", fsp->f_mntonname, errmsg); } } /* * Allocate an export list element */ static struct exportlist * get_exp(void) { struct exportlist *ep; ep = (struct exportlist *)calloc(1, sizeof (struct exportlist)); if (ep == (struct exportlist *)NULL) out_of_mem(); return (ep); } /* * Allocate a group list element */ static struct grouplist * get_grp(void) { struct grouplist *gp; gp = (struct grouplist *)calloc(1, sizeof (struct grouplist)); if (gp == (struct grouplist *)NULL) out_of_mem(); return (gp); } /* * Clean up upon an error in get_exportlist(). */ static void getexp_err(struct exportlist *ep, struct grouplist *grp, const char *reason) { struct grouplist *tgrp; if (!(opt_flags & OP_QUIET)) { if (reason != NULL) syslog(LOG_ERR, "bad exports list line '%s': %s", line, reason); else syslog(LOG_ERR, "bad exports list line '%s'", line); } if (ep && (ep->ex_flag & EX_LINKED) == 0) free_exp(ep); while (grp) { tgrp = grp; grp = grp->gr_next; free_grp(tgrp); } } /* * Search the export list for a matching fs. */ static struct exportlist * ex_search(fsid_t *fsid, struct exportlisthead *exhp) { struct exportlist *ep; uint32_t i; i = EXPHASH(fsid); SLIST_FOREACH(ep, &exhp[i], entries) { if (fsidcmp(&ep->ex_fs, fsid) == 0) return (ep); } return (ep); } /* * Add a directory path to the list. */ static char * add_expdir(struct dirlist **dpp, char *cp, int len) { struct dirlist *dp; dp = malloc(sizeof (struct dirlist)); if (dp == (struct dirlist *)NULL) out_of_mem(); dp->dp_left = *dpp; dp->dp_right = (struct dirlist *)NULL; dp->dp_flag = 0; dp->dp_hosts = (struct hostlist *)NULL; dp->dp_dirp = strndup(cp, len); if (dp->dp_dirp == NULL) out_of_mem(); *dpp = dp; return (dp->dp_dirp); } /* * Hang the dir list element off the dirpath binary tree as required * and update the entry for host. */ static void hang_dirp(struct dirlist *dp, struct grouplist *grp, struct exportlist *ep, int flags, struct expcred *anoncrp, uint64_t exflags) { struct hostlist *hp; struct dirlist *dp2; if (flags & OP_ALLDIRS) { if (ep->ex_defdir) free((caddr_t)dp); else ep->ex_defdir = dp; if (grp == (struct grouplist *)NULL) { ep->ex_flag |= EX_DEFSET; ep->ex_defdir->dp_flag |= DP_DEFSET; /* Save the default security flavors list. */ ep->ex_defnumsecflavors = ep->ex_numsecflavors; if (ep->ex_numsecflavors > 0) memcpy(ep->ex_defsecflavors, ep->ex_secflavors, sizeof(ep->ex_secflavors)); cp_cred(&ep->ex_defanon, anoncrp); ep->ex_defexflags = exflags; } else while (grp) { hp = get_ht(); hp->ht_grp = grp; hp->ht_next = ep->ex_defdir->dp_hosts; ep->ex_defdir->dp_hosts = hp; /* Save the security flavors list for this host set. */ grp->gr_numsecflavors = ep->ex_numsecflavors; if (ep->ex_numsecflavors > 0) memcpy(grp->gr_secflavors, ep->ex_secflavors, sizeof(ep->ex_secflavors)); grp = grp->gr_next; } } else { /* * Loop through the directories adding them to the tree. */ while (dp) { dp2 = dp->dp_left; add_dlist(&ep->ex_dirl, dp, grp, flags, ep, anoncrp, exflags); dp = dp2; } } } /* * Traverse the binary tree either updating a node that is already there * for the new directory or adding the new node. */ static void add_dlist(struct dirlist **dpp, struct dirlist *newdp, struct grouplist *grp, int flags, struct exportlist *ep, struct expcred *anoncrp, uint64_t exflags) { struct dirlist *dp; struct hostlist *hp; int cmp; dp = *dpp; if (dp) { cmp = strcmp(dp->dp_dirp, newdp->dp_dirp); if (cmp > 0) { add_dlist(&dp->dp_left, newdp, grp, flags, ep, anoncrp, exflags); return; } else if (cmp < 0) { add_dlist(&dp->dp_right, newdp, grp, flags, ep, anoncrp, exflags); return; } else free((caddr_t)newdp); } else { dp = newdp; dp->dp_left = (struct dirlist *)NULL; *dpp = dp; } if (grp) { /* * Hang all of the host(s) off of the directory point. */ do { hp = get_ht(); hp->ht_grp = grp; hp->ht_next = dp->dp_hosts; dp->dp_hosts = hp; /* Save the security flavors list for this host set. */ grp->gr_numsecflavors = ep->ex_numsecflavors; if (ep->ex_numsecflavors > 0) memcpy(grp->gr_secflavors, ep->ex_secflavors, sizeof(ep->ex_secflavors)); grp = grp->gr_next; } while (grp); } else { ep->ex_flag |= EX_DEFSET; dp->dp_flag |= DP_DEFSET; /* Save the default security flavors list. */ ep->ex_defnumsecflavors = ep->ex_numsecflavors; if (ep->ex_numsecflavors > 0) memcpy(ep->ex_defsecflavors, ep->ex_secflavors, sizeof(ep->ex_secflavors)); cp_cred(&ep->ex_defanon, anoncrp); ep->ex_defexflags = exflags; } } /* * Search for a dirpath on the export point. */ static struct dirlist * dirp_search(struct dirlist *dp, char *dirp) { int cmp; if (dp) { cmp = strcmp(dp->dp_dirp, dirp); if (cmp > 0) return (dirp_search(dp->dp_left, dirp)); else if (cmp < 0) return (dirp_search(dp->dp_right, dirp)); else return (dp); } return (dp); } /* * Scan for a host match in a directory tree. */ static int chk_host(struct dirlist *dp, struct sockaddr *saddr, int *defsetp, int *hostsetp, int *numsecflavors, int **secflavorsp) { struct hostlist *hp; struct grouplist *grp; struct addrinfo *ai; if (dp) { if (dp->dp_flag & DP_DEFSET) *defsetp = dp->dp_flag; hp = dp->dp_hosts; while (hp) { grp = hp->ht_grp; switch (grp->gr_type) { case GT_HOST: ai = grp->gr_ptr.gt_addrinfo; for (; ai; ai = ai->ai_next) { if (!sacmp(ai->ai_addr, saddr, NULL)) { *hostsetp = (hp->ht_flag | DP_HOSTSET); if (numsecflavors != NULL) { *numsecflavors = grp->gr_numsecflavors; *secflavorsp = grp->gr_secflavors; } return (1); } } break; case GT_NET: if (!sacmp(saddr, (struct sockaddr *) &grp->gr_ptr.gt_net.nt_net, (struct sockaddr *) &grp->gr_ptr.gt_net.nt_mask)) { *hostsetp = (hp->ht_flag | DP_HOSTSET); if (numsecflavors != NULL) { *numsecflavors = grp->gr_numsecflavors; *secflavorsp = grp->gr_secflavors; } return (1); } break; } hp = hp->ht_next; } } return (0); } /* * Scan tree for a host that matches the address. */ static int scan_tree(struct dirlist *dp, struct sockaddr *saddr) { int defset, hostset; if (dp) { if (scan_tree(dp->dp_left, saddr)) return (1); if (chk_host(dp, saddr, &defset, &hostset, NULL, NULL)) return (1); if (scan_tree(dp->dp_right, saddr)) return (1); } return (0); } /* * Traverse the dirlist tree and free it up. */ static void free_dir(struct dirlist *dp) { if (dp) { free_dir(dp->dp_left); free_dir(dp->dp_right); free_host(dp->dp_hosts); free(dp->dp_dirp); free(dp); } } /* * Parse a colon separated list of security flavors */ static int parsesec(char *seclist, struct exportlist *ep) { char *cp, savedc; int flavor; ep->ex_numsecflavors = 0; for (;;) { cp = strchr(seclist, ':'); if (cp) { savedc = *cp; *cp = '\0'; } if (!strcmp(seclist, "sys")) flavor = AUTH_SYS; else if (!strcmp(seclist, "krb5")) flavor = RPCSEC_GSS_KRB5; else if (!strcmp(seclist, "krb5i")) flavor = RPCSEC_GSS_KRB5I; else if (!strcmp(seclist, "krb5p")) flavor = RPCSEC_GSS_KRB5P; else { if (cp) *cp = savedc; syslog(LOG_ERR, "bad sec flavor: %s", seclist); return (1); } if (ep->ex_numsecflavors == MAXSECFLAVORS) { if (cp) *cp = savedc; syslog(LOG_ERR, "too many sec flavors: %s", seclist); return (1); } ep->ex_secflavors[ep->ex_numsecflavors] = flavor; ep->ex_numsecflavors++; if (cp) { *cp = savedc; seclist = cp + 1; } else { break; } } return (0); } /* * Parse the option string and update fields. * Option arguments may either be -