kern/136865: NFS exports atomic and on-the-fly atomic updates
Andrey Simonenko
simon at comsys.ntu-kpi.kiev.ua
Fri Jul 17 11:20:06 UTC 2009
>Number: 136865
>Category: kern
>Synopsis: NFS exports atomic and on-the-fly atomic updates
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Fri Jul 17 11:20:05 UTC 2009
>Closed-Date:
>Last-Modified:
>Originator: Andrey Simonenko
>Release: FreeBSD 7.2-STABLE amd64
>Organization:
>Environment:
FreeBSD 7.2-STABLE, but possible to modify for 8.0.
>Description:
Here I want to describe changes that allow to make atomic updates of
NFS exports lists, dynamic on-the-fly atomic updates of NFS exports
lists and improve security of NFS exports.
Solved tasks:
-------------
1. NFS export specifications (spec -- for short) updates are atomic. NFS
server's users will not get EACCES for exported file systems or wrong
access rights while exports list is reloaded.
2. The mountd utility has an option for testing configuration, now one can
check and see real configuration not loading it into nfsserver.
3. The mountd utility does not load incomplete settings to nfsserver,
wrong configuration will not allow denied exports.
4. Atomic on-the-fly modifications of NFS export specifications were
implemented, it is possible to change exports settings dynamically.
5. If some file system is mounted or unmounted, then SIGHUP signal sending
to mountd is not required, this change removes several race conditions.
6. NFS exports related code is a part of nfsserver/ code, NFS export related
data can be removed from directories not related to NFS.
Which actions are atomic?
-------------------------
1. Loading export settings from exports(5) file into nfsserver is atomic for
each exported file system and for all exported file systems.
2. Loading updates into nfsserver is atomic for each exported file system and
for all exported file systems.
3. If a file system was mounted, then loading export settings for it into
nfssever is atomic for this file system.
Which actions are not atomic?
-----------------------------
1. Loading export specifications for WebNFS file system and specifying
WebNFS related settings require more than one system call.
2. Since VFS events such as mounting and unmounting are asynchronous,
events for all exported and not exported file systems are checked
as separate system calls.
Since nmount(2) is not used in this implementation and subdirectories exports
are not allowed, it is unlikely that these changes will be accepted.
If absence of insecure subdirectories exports is not a problem, then it
it is possible to support both existent mountd and new API on 7-STABLE
(see nfsserver/nfs_srvsubs.c:nfsrv_fhtovp() function patch).
The mountd utility was completely rewritten, actually the better name
for new utility with new properties would be "nfse". The single source
file mountd.c was split into three .c and three .h files: mountd.c
(previous code was rewritten and new code was added), mountd.h (new code),
mountd_conf.c (new code), mountd_conf.h (new code), mountd_xdr.c (previous
code was updated to support new data structure and options), mountd_xdr.h
(previous code).
The analog of kern/vfs_export.c was written from zero and now it is called
nfssever/nfs_export.c.
This version of mountd can be used on modified 7.2 system.
It was tested (except WebNFS related settings) on amd64 and i386 arch.
Support of 8.0 system is possible, it is necessary to modify the
nfs_export.c:nfse_check() function (~60 lines) and add new NFSv4 related
options, but since there are sys/nfssever/ and sys/fs/nfsserver/ that have
functions that call VFS_CHECKEXP and there is activity in NFS development,
it is unclear which arguments nfse_check() should accept. It is better
to discuss arguments list and semantic of nfse_check() first. I think this
is the only one function that does not allow to use these changes on 8.0
system.
Available patches have only necessary changes to sys/nfsserver/, sys/kern/,
sys/sys/ and sys/conf/. Complete changes require removing all NFS exports
related data and code from directories not related to NFS. Also nfs_pub
structure should be a part of the new nfsserver/nfs_export.c file.
I did not make all these changes (all file systems, all NFS related flags
in mount.h, etc.), just to show the main parts of changes.
The following text contains more or less detail description of changes
(definitely I forgot to mention something here).
Major improvements:
-------------------
* Now all export spec updates are atomic. mountd uses nfssvc(2) for this
(the new nfssvc(NFSSVC_EXPORT) call is used). Now it is safe to reload
exports files.
* New nfs_export.c file was added to nfsserver/, all API details are
located in nfs_export.h. All NFS related flags and structures are part
of nfsserver.
* Now mountd uses kernel event EVFILT_FS to see mount and umount VFS events.
The mount(8) utility should not send sighup signal to mountd any more.
New EVENTHANDLERs were declared: vfs_mount_event and vfs_unmount_event.
Registered function for these handlers are invoked when a file system is
mounted or unmounted respectively. The nfsserver uses these event handlers
to synchronizes own data with available file systems. Memory leak was
removed when an exported file system is unmounted. Now the nfssever
understands covered file systems (file system mounted on mount point of
another file system).
* The mountd utility has a new option -c, that allows to modify export spec
on-the-fly. One can clear, add, update, delete export spec. All updates
are atomic. One commands set works like a transaction with changes,
it is applied completely or is not applied at all.
* Now mountd has the -t switch: parse configuration files or commands and
output all settings to stdout. This option allows to check and see real
configuration.
Incompatible security changes:
------------------------------
* Now subdirectory export is disallowed. Subdirectory export does not
improve security, instead it is the right way for misconfiguration
(export settings for a subdirectory can be completely unrelated to this
subdirectory and does not protect access to another parts of exported
file system).
The nfsserver exports file systems, not directories, looks like that
subdirectory export for NFS is too complex or impossible to implement
completely. Anyway there is nullfs.
Having read RFCs, documentation for another NFS implementations and
thoughts in user groups in Internet I think that this (radical)
modification will improve security. This version of mountd has the same
logic of export rules as nfsserver has.
* Now mountd allows to mount file systems, subdirectories and regular files
(if the -r flag is on) in exported file systems by default. The -alldirs
option became obsolete.
Compatible security changes:
----------------------------
* Ignoring exports files is not safe, since remote users can get wrong access
rights. Alternative compatible solution: all exports file must be present,
a user can specify directory/ and all regular files from the given
directories will be loaded (any directory can be absent).
* Now if mountd cannot correctly parse export specification for some file
system, then it does not load anything to nfsserver for this file system.
Ignoring something in exports file is not safe.
* Now security flavors are per address specification settings in nfsserver
and mountd.
* In rare cases mountd completely ignore settings in exports file, and does
not load anything into nfsserver (this can happen if mistake in
configuration does not allow to finish file parsing).
Updates for mountd:
-------------------
* Now everywhere IPv4 and IPv6 addresses are used, since the kernel knows
nothing about domain names, netgroups, etc. Now mountdtab file contains
only address, MOUNT protocol's procedure EXPORT and DUMP output addresses.
This removes problems with reverse name resolving, but sometimes entries
are not removed on unmount (depends on used address).
* Better output for MOUNT protocol's procedure EXPORT: host is an address,
network is an address with prefix.
* Now mountdtab is parsed more carefully.
* Zone scope index checking was removed for IPv6 addresses, nfsserver does
not check zone scope index.
* Now mountdtab is saved only when mountd exists, no other program in the base
system uses this file. The representation of mountdtab file's content in
memory was optimized.
* Do not leave PID file if some error occurred and mountd exited.
* Allowed to use loopback addresses in the -h option. (I do not like design
idea of -h, -p and similar options.)
* Corrected incorrect binding when -p option is not used (nobody saw this
because this can happen very seldom, but I could reproduce this error).
* Wrong implementation of mask creation when prefix length is given as
/prefixlength was corrected.
Updates for nfsserver:
----------------------
* Previous nfsserver could access released memory returned by VFS_CHECKEXP.
New code does have this problem.
Updates for exports(5):
-----------------------
* Added new option -host to allow to use host names and the same netgroups
names at once.
* Added new option -rw: read-write access.
* Added flag `!' for hosts and networks, this flag means "deny access".
* Added new line "options: ...", right now it is used for global -sec,
-no_mntproc_dump and -no_mntproc_export options, later it can be used for
NFSv4.
* Added new option -nospec, that means "this line does not have any address
specifications".
* Added new option -no_mntproc_dump to disable MOUNT protocol's procedure
DUMP.
* Added new option -no_mntproc_export to disable MOUNT protocol's procedure
EXPORT.
* exports(5) says that -o is the only one compatible option. Actually there
are others: -root and -r for -maproot, -m for -mask and -n for -network.
Now mountd logs a warning message if an obsolete option is used.
* Do not allow to use any option between -network and -mask.
* Now #-comment can be anywhere in a line.
* \xxx octal number can be used in directories names and option's arguments
for representing an arbitrary character.
* Now it is possible to mix hosts and netgroups with networks:
"host1 -network=somenetwork host2".
* Now it is possible to change options for particular host/network in one
line: "-ro -mapall=user1 host1 -mapall=user2 host2" (host2 will inherit
previous option -ro, but will get new -mapall option). Since previously
exports(5) says that options must be given before hosts and networks, this
change is backward compatible and allows to represent all settings for one
file system in one logical line.
* Content of exports(5) was simplified and updated.
Open questions and tasks:
-------------------------
* There must be a global solution to check whether it is possible to unload
a KLD module when no process currently is working with its syscalls.
* Looks like that first argument for nfssvc(2) is not a set of flags any more
(according to STABLE, CURRENT and NFSv4 implementation). May be there is
a sense to make it a value, not flags.
* WebNFS related data in nfsserver is protected when export settings are set
(in vfs_export.c and new nfs_export.c), but when other parts of nfsserver
access WebNFS related data no synchronization is performed.
* If a file system cannot be exported in NFS, then there must be some flag
to indicate this (MNT_NFSEXPORTABLE or something more general, see
fs/msdosfs/msdosfs_vfsops.c for example).
* Should signals be checked more often in mountd? Right now signals are
checked when mountd is waiting for RPC request, or if nfssvc's commands
transaction timeout occurred. Previous code has race conditions with
signals and does too many things disallowed by SUSv3 in signal handlers.
* Should be there any limitation in nfsserver on number of export
specifications and number of command transactions?
* May be mountd should be renamed to something another, eg. nfse. NFSv4
does not use MNT procedure, but still needs utility for configuring
access rights as I understand. Also nfse command name is more obvious
for -c commands, eg. "nfse -c 'add /fs -ro'". As well /etc/exports can
be renamed to /etc/nfs.exports, /var/db/mountdtab -> /var/db/nfse.mounts.
Also such renaming will allow to use mountd and new nfse in 7-STABLE at
the same time and mount(8) from 7-STABLE will not send SIGHUP to nfse,
since its PID will be saved in the nfse.pid file.
* netgroup.5 can be moved to src/lib/libc/gen/ or src/share/man/man5, this
documentation is not part of mountd.
Examples:
---------
1. Correct file:
exports file:
options: -no_mntproc_dump
/fs -host 1.1.1.1 -ro 2.2.2.2
/fs -network 10.20.30.40
/home -mapall nobody -network 10/8 -mapall operator -network 20.1/8
"mountd -t" output:
configure: reading file exports
Global options:
-no_mntproc_dump
Directory /fs
Export specifications:
-ro -sec sys -maproot=-2:-2 -host 2.2.2.2
-rw -sec sys -maproot=-2:-2 -host 1.1.1.1
-rw -sec sys -maproot=-2:-2 -network 10.0.0.0/8
Directory /home (mount point)
Export specifications:
-rw -sec sys -mapall 2:5 -network 20.0.0.0/8
-rw -sec sys -mapall 65534:65534 -network 10.0.0.0/8
2. Wrong file:
exports:
/fs -ro 1.1.1.1
/fs -network 10/8 -host 1.1.1.1
/home -quiet -ro
"mountd -t" output:
configure: reading file exports
parsing error: exports:2: duplicated address specification 1.1.1.1 was found
in this line
parsing error: exports:2: -host option's argument parsing failed
Directory /fs
Wrong configuration
Directory /home (mount point)
File system options:
-quiet
Export specifications:
-ro -sec sys -maproot=-2:-2 (default)
3. Commands testing:
mountd -t -c 'add /fs -ro -mapall nobody -host 1.1.1.1 -network !10/8' \
-c 'flush /home' -c 'update /usr -ro -mapall operator'
configure: parsing -c commands
Directory /fs
Commands:
-c add -ro -sec sys -mapall 65534:65534 -host 1.1.1.1
-c add -ro -sec sys -mapall 65534:65534 -network !10.0.0.0/8
Directory /home (mount point)
Commands:
-c flush
Directory /usr (mount point)
Commands:
-c update -ro -sec sys -mapall 2:5 (default)
4. Specifying exports(5) files
mountd /etc/exports /etc/local.exports /usr/local/nfs-export/
Files /etc/exports and /etc/local.exports must be present.
The nfs-export directory can be absent, if it present or will be
present, then all regular files from it are read.
Related PR
----------
kern/9619: Restarting mountd kills existing mounts
kern/131342: [nfs] mounting/unmounting of disks causes NFS to fail
Sources
-------
http://comsys.ntu-kpi.kiev.ua/~simon/nfse/
>How-To-Repeat:
Try to copy files from some NFS exported directory and send a SIGHUP signal
to mountd(8) several times.
>Fix:
Patch is not a single file, because modified files are located in different
directories and rewritten mountd(8) source code is given as is (without diff).
src/sys/conf/files:
#########################################################################
--- files.orig 2009-03-23 11:53:09.000000000 +0200
+++ files 2009-05-26 18:09:36.000000000 +0300
@@ -2084,6 +2084,7 @@
nfsclient/nfs_vfsops.c optional nfsclient
nfsclient/nfs_vnops.c optional nfsclient
nfsclient/nfs_lock.c optional nfsclient
+nfsserver/nfs_export.c optional nfsserver
nfsserver/nfs_serv.c optional nfsserver
nfsserver/nfs_srvsock.c optional nfsserver
nfsserver/nfs_srvcache.c optional nfsserver
#########################################################################
src/sys/kern/vfs_mount.c:
#########################################################################
--- vfs_mount.c.orig 2009-03-02 11:23:05.000000000 +0200
+++ vfs_mount.c 2009-04-11 14:40:00.000000000 +0300
@@ -1083,6 +1083,7 @@
mtx_lock(&mountlist_mtx);
TAILQ_INSERT_TAIL(&mountlist, mp, mnt_list);
mtx_unlock(&mountlist_mtx);
+ EVENTHANDLER_INVOKE(vfs_mount_event, mp);
vfs_event_signal(NULL, VQ_MOUNT, 0);
if (VFS_ROOT(mp, LK_EXCLUSIVE, &newdp, td))
panic("mount: lost mount");
@@ -1336,6 +1337,7 @@
coveredvp->v_mountedhere = NULL;
vput(coveredvp);
}
+ EVENTHANDLER_INVOKE(vfs_unmount_event, mp);
vfs_event_signal(NULL, VQ_UNMOUNT, 0);
lockmgr(&mp->mnt_lock, LK_RELEASE, NULL, td);
vfs_mount_destroy(mp);
#########################################################################
src/sys/sys/mount.h:
#########################################################################
--- mount.h.orig 2009-03-16 13:28:22.000000000 +0200
+++ mount.h 2009-04-11 14:38:40.000000000 +0300
@@ -727,6 +727,13 @@
vfs_extattrctl_t vfs_stdextattrctl;
vfs_sysctl_t vfs_stdsysctl;
+#include <sys/eventhandler.h>
+
+typedef void (*vfs_mount_event_fn)(void *, const struct mount *);
+typedef void (*vfs_unmount_event_fn)(void *, const struct mount *);
+EVENTHANDLER_DECLARE(vfs_mount_event, vfs_mount_event_fn);
+EVENTHANDLER_DECLARE(vfs_unmount_event, vfs_unmount_event_fn);
+
#else /* !_KERNEL */
#include <sys/cdefs.h>
#########################################################################
src/sys/nfssever/:
#########################################################################
diff -ruN nfsserver.orig/nfs.h nfsserver/nfs.h
--- nfsserver.orig/nfs.h 2007-11-26 09:01:29.000000000 +0200
+++ nfsserver/nfs.h 2009-05-26 18:06:04.000000000 +0300
@@ -105,6 +105,7 @@
/*
* Flags for nfssvc() system call.
*/
+#define NFSSVC_EXPORT 0x001
#define NFSSVC_NFSD 0x004
#define NFSSVC_ADDSOCK 0x008
diff -ruN nfsserver.orig/nfs_export.c nfsserver/nfs_export.c
--- nfsserver.orig/nfs_export.c 1970-01-01 03:00:00.000000000 +0300
+++ nfsserver/nfs_export.c 2009-06-06 13:44:45.000000000 +0300
@@ -0,0 +1,1568 @@
+/*-
+ * Copyright (c) 2009 Andrey Simonenko
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD:$
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD:$");
+
+#include "opt_inet.h"
+#include "opt_inet6.h"
+
+#include <sys/param.h>
+#include <sys/eventhandler.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mount.h>
+#include <sys/proc.h>
+#include <sys/queue.h>
+#include <sys/rwlock.h>
+#include <sys/stddef.h>
+#include <sys/socket.h>
+#include <sys/systm.h>
+#include <sys/ucred.h>
+
+#if defined(INET) || defined(INET6)
+#include <net/radix.h>
+#include <netinet/in.h>
+#endif
+
+#include <rpc/types.h>
+#include <rpc/auth.h>
+
+#include <nfs/rpcv2.h>
+#include <nfsserver/nfs_export.h>
+
+#define NFSE_EXFLAG_RDONLY 0x0001 /* Read-only access. */
+#define NFSE_EXFLAG_MAPALL 0x0002 /* Change credentials for everyone. */
+#define NFSE_EXFLAG_DENY 0x0004 /* Deny access. */
+
+#define RPCSEC_GSS_KRB5 100
+#define RPCSEC_GSS_KRB5I 200
+#define RPCSEC_GSS_KRB5P 300
+
+MALLOC_DEFINE(M_NFSE, "nfse", "NFS export specifications data");
+
+#define NFSE_EXPORT_RLOCK() rw_rlock(&nfse_export_rwlock)
+#define NFSE_EXPORT_RUNLOCK() rw_runlock(&nfse_export_rwlock)
+#define NFSE_EXPORT_WLOCK() rw_wlock(&nfse_export_rwlock)
+#define NFSE_EXPORT_WUNLOCK() rw_wunlock(&nfse_export_rwlock)
+
+int nfse_api_used = 0; /* Non-zero if NFSE API is used. */
+
+static eventhandler_tag nfse_mount_event_tag;
+static eventhandler_tag nfse_unmount_event_tag;
+
+/*
+ * Read-write lock protects exports list, mutex protects exports data
+ * that is not changed and cannot disappear while nfse_check() is running.
+ */
+static struct rwlock nfse_export_rwlock;
+static struct mtx nfse_export_mtx;
+
+/*
+ * Credentials specification.
+ */
+struct nfse_cred_exp {
+ LIST_ENTRY(nfse_cred_exp) link; /* For list building. */
+ uid_t uid; /* Credentials UID. */
+ u_int ngids; /* Number of credentials GIDs. */
+ gid_t gids[NGROUPS]; /* Credentials GIDs. */
+ u_int ref_count; /* Reference counter. */
+};
+
+/* Global list of all credential specifications. */
+static LIST_HEAD(, nfse_cred_exp) nfse_cred_exp_list =
+ LIST_HEAD_INITIALIZER(nfse_cred_exp_list);
+
+/*
+ * Security flavors specification.
+ */
+struct nfse_secflav {
+ LIST_ENTRY(nfse_secflav) link; /* For list building. */
+ u_int nsec; /* Number of security flavors. */
+ int sec[NFSE_NSECFLAV]; /* Security flavors. */
+ u_int ref_count; /* Reference counter. */
+};
+
+/* Global list of all security flavors. */
+static LIST_HEAD(, nfse_secflav) nfse_secflav_list =
+ LIST_HEAD_INITIALIZER(&nfse_secflav_list);
+
+/*
+ * Export specification for one host or network (variable-sized structure).
+ */
+struct nfse_addr_exp {
+#if defined(INET) || defined(INET6)
+ struct radix_node rnodes[2]; /* For building radix tree. */
+#endif
+ u_int flags; /* Export flags. */
+ struct nfse_secflav *secflav; /* Security flavors. */
+ struct nfse_cred_exp *cred_exp; /* Export credentials. */
+ struct sockaddr sockaddr; /* Alignment for addresses. */
+};
+
+#define NFSE_ADDR_EXP_SIZE(size) \
+ (offsetof(struct nfse_addr_exp, sockaddr) + size)
+
+#define NFSE_ADDR_EXP_ADDR4(p) \
+ ((struct sockaddr_in *)&(p)->sockaddr)
+
+#define NFSE_ADDR_EXP_MASK4(p) \
+ (NFSE_ADDR_EXP_ADDR4(p) + 1)
+
+#define NFSE_ADDR_EXP_ADDR6(p) \
+ ((struct sockaddr_in6 *)&(p)->sockaddr)
+
+#define NFSE_ADDR_EXP_MASK6(p) \
+ (NFSE_ADDR_EXP_ADDR6(p) + 1)
+
+/*
+ * All information about one exported file system.
+ */
+struct nfse_fs_exp {
+ LIST_ENTRY(nfse_fs_exp) link; /* For list building. */
+ struct mount *mp; /* Exported file system. */
+ struct nfse_addr_exp *ae_def; /* Default export spec. */
+#ifdef INET
+ struct radix_node_head *ae4_rnh; /* Export spec. for IPv4. */
+#endif
+#ifdef INET6
+ struct radix_node_head *ae6_rnh; /* Export spec. for IPv6. */
+#endif
+};
+
+/* Export specification settings for all file systems. */
+static LIST_HEAD(, nfse_fs_exp) nfse_fs_exp_list =
+ LIST_HEAD_INITIALIZER(&nfse_fs_exp_list);
+
+/*
+ * User command descriptor.
+ */
+struct nfse_ucmd {
+ STAILQ_ENTRY(nfse_ucmd) link; /* For list building. */
+ u_int command; /* Command code. */
+ void *udata; /* Command's user data. */
+ void *kdata; /* Command's kernel data. */
+};
+
+/* List of user commands from one transaction. */
+STAILQ_HEAD(nfse_ucmd_list, nfse_ucmd);
+
+/*
+ * User export specification from NFSE_CMD_EXPORT command.
+ */
+struct nfse_ucmd_export_spec {
+ STAILQ_ENTRY(nfse_ucmd_export_spec) link; /* For list building. */
+ uint32_t command; /* Command code. */
+ u_int maskbits; /* Number of bits in mask. */
+ sa_family_t family; /* Address family. */
+ struct nfse_addr_exp *addr_exp; /* Associated export spec. */
+};
+
+/*
+ * User NFSE_CMD_EXPORT command descriptor.
+ */
+struct nfse_ucmd_export {
+ char *path; /* Mount point. */
+ fsid_t fsid; /* File system ID. */
+ uint32_t status; /* Status flags. */
+ struct nfse_fs_exp *fs_exp; /* New exported file system. */
+ STAILQ_HEAD(, nfse_ucmd_export_spec) es_list; /* Spec. list. */
+};
+
+/*
+ * User NFSE_CMD_EVENT command descriptor.
+ */
+struct nfse_ucmd_event {
+ char *path; /* Mount point. */
+ fsid_t fsid; /* File system ID. */
+ uint32_t status; /* Status flags. */
+};
+
+/*
+ * User NFSE_CMD_WEBNFS command descriptor.
+ */
+struct nfse_ucmd_webnfs {
+ char *path; /* Mount point. */
+ char *index; /* WebNFS index file. */
+ fsid_t fsid; /* File system ID. */
+ struct fid fid; /* File ID of root vnode. */
+ uint32_t status; /* Status flags. */
+ fhandle_t handle; /* Filehandle. */
+};
+
+#define NFSE_TR_TIMEOUT 20 /* Transaction timeout in seconds. */
+
+#define NFSE_TR_BUSY 1 /* Transaction is being updated. */
+#define NFSE_TR_ACTIVE 2 /* Transaction was recently updated. */
+#define NFSE_TR_INACTIVE 3 /* Transaction was not updated. */
+
+/*
+ * Transaction with user commands descriptor.
+ */
+struct nfse_tr {
+ LIST_ENTRY(nfse_tr) link; /* For list building. */
+ pid_t pid; /* PID of transaction owner. */
+ uid_t uid; /* UID of transaction owner. */
+ u_int trid; /* Transaction ID. */
+ u_int state; /* Transaction state. */
+ struct nfse_ucmd_list uc_list; /* User commands list. */
+};
+
+/* List of all transactions. */
+static LIST_HEAD(, nfse_tr) nfse_tr_list =
+ LIST_HEAD_INITIALIZER(nfse_tr_list);
+
+static struct mtx nfse_tr_mtx; /* Transactions list mutex. */
+
+static struct callout nfse_tr_callout; /* Callout to monitor transactions. */
+
+/*
+ * Release memory used by nfse_addr_exp structure.
+ */
+static void
+nfse_addr_exp_free(struct nfse_addr_exp *ae)
+{
+ struct nfse_secflav *sf;
+ struct nfse_cred_exp *ce;
+
+ ce = ae->cred_exp;
+ sf = ae->secflav;
+
+ /* Drop references on shared data. */
+ mtx_lock(&nfse_export_mtx);
+ ce->ref_count--;
+ if (ce->ref_count == 0) {
+ LIST_REMOVE(ce, link);
+ free(ce, M_NFSE);
+ }
+ sf->ref_count--;
+ if (sf->ref_count == 0) {
+ LIST_REMOVE(sf, link);
+ free(sf, M_NFSE);
+ }
+ mtx_unlock(&nfse_export_mtx);
+
+ /* Release own memory. */
+ free(ae, M_NFSE);
+}
+
+/*
+ * Release memory related to WebNFS.
+ */
+static void
+nfse_webnfs_free(void)
+{
+ if (nfs_pub.np_valid) {
+ nfs_pub.np_valid = 0;
+ free(nfs_pub.np_index, M_TEMP);
+ nfs_pub.np_index = NULL;
+ }
+}
+
+/*
+ * Create new nfse_fs_exp structure.
+ */
+static struct nfse_fs_exp *
+nfse_fs_exp_new(void)
+{
+ struct nfse_fs_exp *fe;
+
+ fe = malloc(sizeof(*fe), M_NFSE, M_WAITOK);
+ fe->ae_def = NULL;
+#ifdef INET6
+ fe->ae6_rnh = NULL;
+#endif
+#ifdef INET
+ fe->ae4_rnh = NULL;
+ if (!rn_inithead((void **)&fe->ae4_rnh,
+ offsetof(struct sockaddr_in, sin_addr) << 3))
+ return (NULL);
+#endif
+#ifdef INET6
+ if (!rn_inithead((void **)&fe->ae6_rnh,
+ offsetof(struct sockaddr_in6, sin6_addr) << 3))
+ return (NULL);
+#endif
+ return (fe);
+}
+
+#if defined(INET) || defined(INET6)
+/*
+ * Release memory used by one nfse_addr_exp node in the given radix tree.
+ */
+static int
+nfse_addr_exp_rn_free(struct radix_node *rn, void *h)
+{
+ struct radix_node_head *rnh;
+
+ rnh = h;
+ rnh->rnh_deladdr(rn->rn_key, rn->rn_mask, rnh);
+ nfse_addr_exp_free((struct nfse_addr_exp *)rn);
+ return (0);
+}
+
+/*
+ * Release memory used by the given radix tree of nfse_addr_exp structures.
+ */
+static void
+nfse_addr_exp_rnh_free(struct radix_node_head *rnh, const int clear)
+{
+ rnh->rnh_walktree(rnh, nfse_addr_exp_rn_free, rnh);
+ if (!clear) {
+ RADIX_NODE_HEAD_DESTROY(rnh);
+ free(rnh, M_RTABLE);
+ }
+}
+#endif
+
+/*
+ * Release memory used by one nfse_fs_exp structure.
+ */
+static void
+nfse_fs_exp_free(struct nfse_fs_exp *fe, const int clear)
+{
+ if (fe->ae_def != NULL) {
+ nfse_addr_exp_free(fe->ae_def);
+ fe->ae_def = NULL;
+ }
+#ifdef INET
+ nfse_addr_exp_rnh_free(fe->ae4_rnh, clear);
+#endif
+#ifdef INET6
+ nfse_addr_exp_rnh_free(fe->ae6_rnh, clear);
+#endif
+ if (!clear) {
+ if (nfs_pub.np_valid && nfs_pub.np_mount == fe->mp)
+ nfse_webnfs_free();
+ free(fe, M_NFSE);
+ }
+}
+
+/*
+ * Release one user command.
+ */
+static void
+nfse_ucmd_free(struct nfse_ucmd *uc)
+{
+ struct nfse_ucmd_event *uc_event;
+ struct nfse_ucmd_export *uc_export;
+ struct nfse_ucmd_export_spec *uc_es, *uc_es_next;
+ struct nfse_ucmd_webnfs *uc_webnfs;
+
+ switch (uc->command) {
+ case NFSE_CMD_EXPORT:
+ uc_export = uc->kdata;
+ free(uc_export->path, M_NFSE);
+ if (uc_export->fs_exp != NULL)
+ nfse_fs_exp_free(uc_export->fs_exp, 0);
+ STAILQ_FOREACH_SAFE(uc_es, &uc_export->es_list, link,
+ uc_es_next) {
+ switch (uc_es->command) {
+ case NFSE_CMD_EXPORT_ADD:
+ case NFSE_CMD_EXPORT_UPDATE:
+ if (uc_es->addr_exp != NULL)
+ nfse_addr_exp_free(uc_es->addr_exp);
+ break;
+ case NFSE_CMD_EXPORT_DELETE:
+ free(uc_es->addr_exp, M_NFSE);
+ break;
+ }
+ free(uc_es, M_NFSE);
+ }
+ break;
+ case NFSE_CMD_EVENT:
+ uc_event = uc->kdata;
+ free(uc_event->path, M_NFSE);
+ break;
+ case NFSE_CMD_WEBNFS:
+ uc_webnfs = uc->kdata;
+ free(uc_webnfs->path, M_NFSE);
+ free(uc_webnfs->index, M_TEMP);
+ break;
+ }
+}
+
+/*
+ * Release list of user commands.
+ */
+static void
+nfse_ucmd_list_free(struct nfse_ucmd_list *uc_list)
+{
+ struct nfse_ucmd *uc, *uc_next;
+
+ STAILQ_FOREACH_SAFE(uc, uc_list, link, uc_next) {
+ if (uc->kdata != NULL) {
+ nfse_ucmd_free(uc);
+ free(uc->kdata, M_NFSE);
+ }
+ free(uc, M_NFSE);
+ }
+}
+
+/*
+ * Create a new transaction.
+ */
+static struct nfse_tr *
+nfse_tr_new(const struct thread *td)
+{
+ const struct nfse_tr *tr0;
+ struct nfse_tr *tr;
+
+ tr = malloc(sizeof(*tr), M_NFSE, M_WAITOK);
+ tr->pid = td->td_proc->p_pid;
+ tr->uid = td->td_ucred->cr_uid;
+ tr->trid = 1;
+ tr->state = NFSE_TR_BUSY;
+ STAILQ_INIT(&tr->uc_list);
+
+ mtx_lock(&nfse_tr_mtx);
+ for (;;) {
+ LIST_FOREACH(tr0, &nfse_tr_list, link)
+ if (tr0->pid == tr->pid && tr0->uid == tr->uid &&
+ tr0->trid == tr->trid) {
+ tr->trid++;
+ break;
+ }
+ if (tr0 == NULL)
+ break;
+ }
+ LIST_INSERT_HEAD(&nfse_tr_list, tr, link);
+ mtx_unlock(&nfse_tr_mtx);
+
+ return (tr);
+}
+
+/*
+ * Release memory used by one transaction including memory used
+ * by user commands.
+ */
+static void
+nfse_tr_free(struct nfse_tr *tr)
+{
+ nfse_ucmd_list_free(&tr->uc_list);
+ free(tr, M_NFSE);
+}
+
+/*
+ * Release memory used by all transactions.
+ */
+static void
+nfse_tr_free_all(void)
+{
+ struct nfse_tr *tr, *tr_next;
+
+ LIST_FOREACH_SAFE(tr, &nfse_tr_list, link, tr_next)
+ nfse_tr_free(tr);
+}
+
+/*
+ * Callout function for transactions list.
+ */
+static void
+nfse_tr_timeout(void *arg __unused)
+{
+ struct nfse_tr *tr, *tr_next;
+
+ LIST_FOREACH_SAFE(tr, &nfse_tr_list, link, tr_next)
+ switch (tr->state) {
+ case NFSE_TR_ACTIVE:
+ tr->state = NFSE_TR_INACTIVE;
+ break;
+ case NFSE_TR_INACTIVE:
+ LIST_REMOVE(tr, link);
+ nfse_tr_free(tr);
+ break;
+ }
+ if (!LIST_EMPTY(&nfse_tr_list)) {
+ callout_reset(&nfse_tr_callout, NFSE_TR_TIMEOUT * hz,
+ nfse_tr_timeout, NULL);
+ }
+}
+
+/*
+ * Find transaction with the given ID and context, unlink it if the
+ * trci flag is set or mark it as busy.
+ */
+static struct nfse_tr *
+nfse_tr_find(const struct thread *td, const u_int trid, const int trci)
+{
+ struct nfse_tr *tr;
+ pid_t pid;
+ uid_t uid;
+
+ pid = td->td_proc->p_pid;
+ uid = td->td_ucred->cr_uid;
+ mtx_lock(&nfse_tr_mtx);
+ LIST_FOREACH(tr, &nfse_tr_list, link)
+ if (tr->pid == pid && tr->uid == uid && tr->trid == trid) {
+ if (tr->state == NFSE_TR_BUSY) {
+ tr = NULL;
+ break;
+ }
+ if (trci) {
+ LIST_REMOVE(tr, link);
+ if (LIST_EMPTY(&nfse_tr_list))
+ callout_stop(&nfse_tr_callout);
+ } else
+ tr->state = NFSE_TR_BUSY;
+ break;
+ }
+ mtx_unlock(&nfse_tr_mtx);
+
+ return (tr);
+}
+
+/*
+ * Reset a transaction activity.
+ */
+static void
+nfse_tr_reset(struct nfse_tr *tr)
+{
+ mtx_lock(&nfse_tr_mtx);
+ if (LIST_NEXT(LIST_FIRST(&nfse_tr_list), link) == NULL)
+ callout_reset(&nfse_tr_callout, NFSE_TR_TIMEOUT * hz,
+ nfse_tr_timeout, NULL);
+ tr->state = NFSE_TR_ACTIVE;
+ mtx_unlock(&nfse_tr_mtx);
+}
+
+/*
+ * Cancel transaction and release its memory.
+ */
+static void
+nfse_tr_cancel(struct nfse_tr *tr)
+{
+ mtx_lock(&nfse_tr_mtx);
+ LIST_REMOVE(tr, link);
+ if (LIST_EMPTY(&nfse_tr_list))
+ callout_stop(&nfse_tr_callout);
+ mtx_unlock(&nfse_tr_mtx);
+ nfse_tr_free(tr);
+}
+
+/*
+ * Get one export specification from NFSE_CMD_EXPORT command.
+ */
+static int
+nfse_cmd_export_spec_get(struct nfse_ucmd_export_spec **uc_es_p,
+ struct nfse_cmd_export_spec *cmd_es)
+{
+ struct nfse_ucmd_export_spec *uc_es;
+ struct nfse_addr_exp *ae;
+ struct nfse_cred_exp *ce;
+ struct nfse_secflav *sf;
+ size_t size;
+ u_int i, j, addrlen, maskbits;
+ int error;
+ uint32_t command;
+ sa_family_t family;
+
+ /* Check whether the given address family is supported. */
+ uc_es = *uc_es_p;
+ family = cmd_es->family;
+ switch (family) {
+ case AF_INET:
+#ifdef INET
+ size = sizeof(struct sockaddr_in);
+ addrlen = 4;
+ break;
+#else
+ free(uc_es, M_NFSE);
+ *uc_es_p = NULL;
+ return (0);
+#endif
+ case AF_INET6:
+#ifdef INET6
+ size = sizeof(struct sockaddr_in6);
+ addrlen = 16;
+ break;
+#else
+ free(uc_es, M_NFSE);
+ *uc_es_p = NULL;
+ return (0);
+#endif
+ case AF_UNSPEC:
+ size = 0;
+ addrlen = 0;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ /* Verify mask length. */
+ maskbits = uc_es->maskbits = cmd_es->maskbits;
+ if (maskbits != 0) {
+ if (maskbits > addrlen * 8)
+ return (EINVAL);
+ size *= 2;
+ }
+
+ /* Verify command and allocate needed data structures. */
+ ce = NULL;
+ sf = NULL;
+ command = uc_es->command = cmd_es->flags & NFSE_CMD_EXPORT_CMDMASK;
+ switch (command) {
+ case NFSE_CMD_EXPORT_ADD:
+ case NFSE_CMD_EXPORT_UPDATE:
+ ce = malloc(sizeof(*ce), M_NFSE, M_WAITOK);
+ sf = malloc(sizeof(*sf), M_NFSE, M_WAITOK);
+ /* FALLTHROUGH */
+ case NFSE_CMD_EXPORT_DELETE:
+ ae = malloc(NFSE_ADDR_EXP_SIZE(size), M_NFSE, M_WAITOK|M_ZERO);
+ break;
+ case NFSE_CMD_EXPORT_CLEAR:
+ return (0);
+ default:
+ return (EINVAL);
+ }
+
+ if (command != NFSE_CMD_EXPORT_DELETE) {
+ /* Copy in and verify credentials and security flavors. */
+ if (cmd_es->nsec == 0 || cmd_es->nsec > NFSE_NSECFLAV ||
+ cmd_es->ngids > NGROUPS) {
+ error = EINVAL;
+ goto failed;
+ }
+ ce->uid = cmd_es->uid;
+ if (cmd_es->ngids > 0) {
+ error = copyin(cmd_es->gids, ce->gids,
+ cmd_es->ngids * sizeof(*cmd_es->gids));
+ if (error != 0)
+ goto failed;
+ }
+ ce->ngids = cmd_es->ngids;
+ for (i = 0; i < ce->ngids; ++i)
+ for (j = i + 1; j < ce->ngids; ++j)
+ if (ce->gids[i] == ce->gids[j]) {
+ error = EINVAL;
+ goto failed;
+ }
+ error = copyin(cmd_es->sec, sf->sec,
+ cmd_es->nsec * sizeof(*cmd_es->sec));
+ if (error != 0)
+ goto failed;
+ sf->nsec = cmd_es->nsec;
+ for (i = 0; i < sf->nsec; ++i) {
+ switch (sf->sec[i]) {
+ case AUTH_SYS:
+ case RPCSEC_GSS_KRB5:
+ case RPCSEC_GSS_KRB5I:
+ case RPCSEC_GSS_KRB5P:
+ break;
+ default:
+ error = EINVAL;
+ goto failed;
+ }
+ for (j = i + 1; j < sf->nsec; ++j)
+ if (sf->sec[i] == sf->sec[j]) {
+ error = EINVAL;
+ goto failed;
+ }
+ }
+
+ /* Translate flags. */
+ if (cmd_es->flags & NFSE_CMD_EXPORT_RDONLY)
+ ae->flags |= NFSE_EXFLAG_RDONLY;
+ if (cmd_es->flags & NFSE_CMD_EXPORT_MAPALL)
+ ae->flags |= NFSE_EXFLAG_MAPALL;
+ if (cmd_es->flags & NFSE_CMD_EXPORT_DENY)
+ ae->flags |= NFSE_EXFLAG_DENY;
+ }
+
+ /*
+ * If this is not a default export, then copy in address
+ * and create mask.
+ */
+ if (addrlen != 0) {
+ uint8_t *mask;
+
+ mask = NULL;
+#ifdef INET
+ if (family == AF_INET) {
+ struct sockaddr_in *sa4;
+
+ sa4 = NFSE_ADDR_EXP_ADDR4(ae);
+ sa4->sin_len = sizeof(*sa4);
+ error = copyin(cmd_es->addr, &sa4->sin_addr.s_addr,
+ sizeof(sa4->sin_addr.s_addr));
+ if (error != 0)
+ goto failed;
+ if (maskbits != 0) {
+ sa4 = NFSE_ADDR_EXP_MASK4(ae);
+ sa4->sin_len = sizeof(*sa4);
+ mask = (uint8_t *)&sa4->sin_addr.s_addr;
+ }
+ }
+#endif
+#ifdef INET6
+ if (family == AF_INET6) {
+ struct sockaddr_in6 *sa6;
+
+ sa6 = NFSE_ADDR_EXP_ADDR6(ae);
+ sa6->sin6_len = sizeof(*sa6);
+ error = copyin(cmd_es->addr, &sa6->sin6_addr,
+ sizeof(sa6->sin6_addr));
+ if (error != 0)
+ goto failed;
+ if (maskbits != 0) {
+ sa6 = NFSE_ADDR_EXP_MASK6(ae);
+ sa6->sin6_len = sizeof(*sa6);
+ mask = (uint8_t *)&sa6->sin6_addr;
+ }
+ }
+#endif
+ if (mask != NULL) {
+ j = maskbits / 8;
+ for (i = 0; i < j; ++i)
+ mask[i] = UINT8_MAX;
+ maskbits %= 8;
+ if (maskbits != 0) {
+ mask[i] = ~((1 << (8 - maskbits)) - 1);
+ ++i;
+ }
+ for (; i < addrlen; ++i)
+ mask[i] = 0;
+ }
+ }
+
+ /* Try to find existent credentials and security flavors. */
+ if (command != NFSE_CMD_EXPORT_DELETE) {
+ struct nfse_cred_exp *ce0;
+ struct nfse_secflav *sf0;
+
+ mtx_lock(&nfse_export_mtx);
+ LIST_FOREACH(ce0, &nfse_cred_exp_list, link) {
+ if (ce->uid != ce0->uid || ce->ngids != ce0->ngids)
+ continue;
+ for (i = 0; i < ce->ngids; ++i)
+ if (ce->gids[i] != ce0->gids[i])
+ break;
+ if (i == ce->ngids) {
+ free(ce, M_NFSE);
+ ce = ce0;
+ ce->ref_count++;
+ break;
+ }
+ }
+ if (ce0 == NULL) {
+ LIST_INSERT_HEAD(&nfse_cred_exp_list, ce, link);
+ ce->ref_count = 1;
+ }
+ LIST_FOREACH(sf0, &nfse_secflav_list, link)
+ if (sf->nsec == sf0->nsec) {
+ for (i = 0; i < sf->nsec; ++i)
+ if (sf->sec[i] != sf0->sec[i])
+ break;
+ if (i == sf->nsec) {
+ free(sf, M_NFSE);
+ sf = sf0;
+ sf->ref_count++;
+ break;
+ }
+ }
+ if (sf0 == NULL) {
+ LIST_INSERT_HEAD(&nfse_secflav_list, sf, link);
+ sf->ref_count = 1;
+ }
+ mtx_unlock(&nfse_export_mtx);
+
+ ae->secflav = sf;
+ ae->cred_exp = ce;
+ }
+
+ /* Everything is ready can finish. */
+ uc_es->family = family;
+ uc_es->addr_exp = ae;
+ return (0);
+
+failed:
+ /* Release all allocated memory (it is not shared). */
+ free(ae, M_NFSE);
+ free(ce, M_NFSE);
+ free(sf, M_NFSE);
+ return (error);
+}
+
+/*
+ * Get one NFSE_CMD_EXPORT command.
+ */
+static int
+nfse_cmd_export_get(struct nfse_ucmd_list *uc_list, const struct nfse_cmd *cmd)
+{
+ struct nfse_cmd_export cmd_export;
+ struct nfse_ucmd_export *uc_export;
+ struct nfse_ucmd_export_spec *uc_es;
+ struct nfse_cmd_export_spec *cmd_es;
+ struct nfse_ucmd *uc;
+ u_int i;
+ int error;
+
+ if (cmd->size != sizeof(cmd_export))
+ return (EINVAL);
+
+ error = copyin(cmd->data, &cmd_export, sizeof(cmd_export));
+ if (error != 0)
+ return (error);
+
+ if (cmd_export.path != NULL) {
+ char *path;
+
+ /* A new file system settings. */
+ if (cmd_export.path_size == 0 ||
+ cmd_export.path_size > RPCMNT_PATHLEN + 1)
+ return (EINVAL);
+ path = malloc(cmd_export.path_size, M_NFSE, M_WAITOK);
+ error = copyin(cmd_export.path, path, cmd_export.path_size);
+ if (error != 0 || path[cmd_export.path_size - 1] != '\0') {
+ free(path, M_NFSE);
+ return (error != 0 ? error : EINVAL);
+ }
+ uc = malloc(sizeof(*uc), M_NFSE, M_WAITOK);
+ STAILQ_INSERT_TAIL(uc_list, uc, link);
+ uc->command = NFSE_CMD_EXPORT;
+ uc->udata = cmd->data;
+ uc_export = malloc(sizeof(*uc_export), M_NFSE, M_WAITOK);
+ uc->kdata = uc_export;
+ uc_export->path = path;
+ STAILQ_INIT(&uc_export->es_list);
+ if (cmd_export.status & NFSE_CMD_EXPORT_EXPORTED) {
+ uc_export->fsid = cmd_export.fsid;
+ uc_export->fs_exp = NULL;
+ } else {
+ uc_export->fs_exp = nfse_fs_exp_new();
+ if (uc_export->fs_exp == NULL)
+ return (ENOMEM);
+ }
+ } else {
+ /* Continue to work with the last file system. */
+ uc = STAILQ_LAST(uc_list, nfse_ucmd, link);
+ if (uc == NULL || uc->command != NFSE_CMD_EXPORT)
+ return (EINVAL);
+ uc_export = uc->kdata;
+ }
+
+ /* Read one-by-one all export specifications. */
+ cmd_es = malloc(sizeof(*cmd_es), M_NFSE, M_WAITOK);
+ error = 0;
+ for (i = 0; i < cmd_export.nspec; ++i) {
+ error = copyin(&cmd_export.spec[i], cmd_es, sizeof(*cmd_es));
+ if (error != 0)
+ break;
+ uc_es = malloc(sizeof(*uc_es), M_NFSE, M_WAITOK);
+ error = nfse_cmd_export_spec_get(&uc_es, cmd_es);
+ if (error != 0) {
+ free(uc_es, M_NFSE);
+ break;
+ }
+ if (uc_es != NULL)
+ STAILQ_INSERT_TAIL(&uc_export->es_list, uc_es, link);
+ }
+ free(cmd_es, M_NFSE);
+
+ return (error);
+}
+
+/*
+ * Get one NFSE_CMD_CLEAR command.
+ */
+static int
+nfse_cmd_clear_get(const struct nfse_ucmd_list *uc_list,
+ struct nfse_ucmd *uc, const struct nfse_cmd *cmd)
+{
+ /* This command accepts no data and must be the first command. */
+ if (cmd->size != 0 || cmd->data != NULL || STAILQ_FIRST(uc_list) != uc)
+ return (EINVAL);
+ return (0);
+}
+
+/*
+ * Get one NFSE_CMD_EVENT command.
+ */
+static int
+nfse_cmd_event_get(struct nfse_ucmd *uc, const struct nfse_cmd *cmd)
+{
+ struct nfse_cmd_event cmd_event;
+ struct nfse_ucmd_event *uc_event;
+ int error;
+
+ if (cmd->size != sizeof(cmd_event))
+ return (EINVAL);
+ error = copyin(cmd->data, &cmd_event, sizeof(cmd_event));
+ if (error != 0)
+ return (error);
+ if (cmd_event.path_size == 0 ||
+ cmd_event.path_size > RPCMNT_PATHLEN + 1)
+ return (EINVAL);
+ uc_event = malloc(sizeof(*uc_event), M_NFSE, M_WAITOK);
+ uc->kdata = uc_event;
+ uc_event->path = malloc(cmd_event.path_size, M_NFSE, M_WAITOK);
+ error = copyin(cmd_event.path, uc_event->path, cmd_event.path_size);
+ if (error != 0)
+ return (error);
+ if (uc_event->path[cmd_event.path_size - 1] != '\0')
+ return (EINVAL);
+ uc_event->fsid = cmd_event.fsid;
+ uc_event->status = cmd_event.status;
+ return (0);
+}
+
+/*
+ * Get one NFSE_CMD_WEBNFS command.
+ */
+static int
+nfse_cmd_webnfs_get(struct nfse_ucmd *uc, const struct nfse_cmd *cmd)
+{
+ struct nfse_cmd_webnfs cmd_webnfs;
+ struct nfse_ucmd_webnfs *uc_webnfs;
+ int error;
+
+ if (cmd->size != sizeof(cmd_webnfs))
+ return (EINVAL);
+ error = copyin(cmd->data, &cmd_webnfs, sizeof(cmd_webnfs));
+ if (error != 0)
+ return (error);
+ if (cmd_webnfs.path_size > RPCMNT_PATHLEN + 1 ||
+ cmd_webnfs.index_size > PATH_MAX)
+ return (EINVAL);
+ if ((cmd_webnfs.path != NULL && cmd_webnfs.path_size == 0) ||
+ (cmd_webnfs.index != NULL && cmd_webnfs.index_size == 0))
+ return (EINVAL);
+ uc_webnfs = malloc(sizeof(*uc_webnfs), M_NFSE, M_WAITOK);
+ uc->kdata = uc_webnfs;
+ if (cmd_webnfs.path == NULL) {
+ uc_webnfs->path = uc_webnfs->index = NULL;
+ return (0);
+ }
+ uc_webnfs->path = malloc(cmd_webnfs.path_size, M_NFSE, M_WAITOK);
+ uc_webnfs->index = cmd_webnfs.index != NULL ?
+ malloc(cmd_webnfs.index_size, M_TEMP, M_WAITOK) : NULL;
+ error = copyin(cmd_webnfs.path, uc_webnfs->path,
+ cmd_webnfs.path_size);
+ if (error != 0)
+ return (error);
+ if (uc_webnfs->path[cmd_webnfs.path_size - 1] != '\0')
+ return (EINVAL);
+ if (uc_webnfs->index != NULL) {
+ error = copyin(cmd_webnfs.index, uc_webnfs->index,
+ cmd_webnfs.index_size);
+ if (error != 0)
+ return (error);
+ if (uc_webnfs->index[cmd_webnfs.index_size - 1] != '\0')
+ return (EINVAL);
+ error = strchr(uc_webnfs->index, '/') != NULL;
+ if (error == 0 && uc_webnfs->index[0] == '.')
+ switch (uc_webnfs->index[1]) {
+ case '\0':
+ error = 1;
+ break;
+ case '.':
+ if (uc_webnfs->index[2] == '\0')
+ error = 1;
+ break;
+ }
+ if (error != 0)
+ return (EINVAL);
+ }
+ if (cmd_webnfs.fid.fid_len <= offsetof(struct fid, fid_data) ||
+ cmd_webnfs.fid.fid_len > sizeof(cmd_webnfs.fid))
+ return (EINVAL);
+ uc_webnfs->fsid = cmd_webnfs.fsid;
+ uc_webnfs->fid = cmd_webnfs.fid;
+ return (0);
+}
+
+/*
+ * Update export specification. A user application must verify that
+ * all export specification updates can be applied to the configuration
+ * it manages. If some command cannot be applied to some file system,
+ * then deny access to this file system completely, since a user application
+ * already incorrectly manages own settings.
+ */
+static int
+nfse_cmd_export_run(struct nfse_ucmd_export *uc_export)
+{
+ const char *path;
+ struct nfse_fs_exp *fe;
+ struct nfse_ucmd_export_spec *uc_es;
+ struct nfse_addr_exp *ae;
+
+#if defined(INET) || defined(INET6)
+ struct radix_node_head *rnh;
+ void *addr, *mask;
+#endif
+
+ /* Get exported or new nfse_fs_exp structure. */
+ path = uc_export->path;
+ LIST_FOREACH(fe, &nfse_fs_exp_list, link)
+ if (strcmp(fe->mp->mnt_stat.f_mntonname, path) == 0)
+ break;
+ if (uc_export->fs_exp == NULL) {
+ /* Update for exported file system. */
+ if (fe == NULL) {
+ /* File system is not exported any more. */
+ uc_export->status = 0;
+ return (0);
+ }
+ if (uc_export->fsid.val[0] != fe->mp->mnt_stat.f_fsid.val[0] ||
+ uc_export->fsid.val[1] != fe->mp->mnt_stat.f_fsid.val[1]) {
+ /* File system is exported, but its ID is different. */
+ uc_export->status = NFSE_CMD_EXPORT_WRONG_ID |
+ NFSE_CMD_EXPORT_MOUNTED | NFSE_CMD_EXPORT_EXPORTED;
+ return (0);
+ }
+ } else {
+ struct mount *mp;
+
+ /* New file system to be exported. */
+ if (fe != NULL) {
+ /* File system is already exported. */
+ uc_export->status = NFSE_CMD_EXPORT_WRONG_ID;
+ return (0);
+ }
+ /*
+ * Since a new file system is linked to the tail of the
+ * mount list, let's do reverse search to skip all matched
+ * but covered file systems.
+ */
+ mtx_lock(&mountlist_mtx);
+ TAILQ_FOREACH_REVERSE(mp, &mountlist, mntlist, mnt_list)
+ if (strcmp(mp->mnt_stat.f_mntonname, path) == 0)
+ break;
+ mtx_unlock(&mountlist_mtx);
+ if (mp == NULL) {
+ /* File system is not mounted. */
+ uc_export->status = 0;
+ return (0);
+ }
+ fe = uc_export->fs_exp;
+ fe->mp = mp;
+ uc_export->fsid = mp->mnt_stat.f_fsid;
+ uc_export->fs_exp = NULL;
+ LIST_INSERT_HEAD(&nfse_fs_exp_list, fe, link);
+ }
+
+ /* Apply commands one-by-one. */
+ STAILQ_FOREACH(uc_es, &uc_export->es_list, link) {
+ if (uc_es->command == NFSE_CMD_EXPORT_CLEAR) {
+ if (STAILQ_NEXT(uc_es, link) == NULL) {
+ LIST_REMOVE(fe, link);
+ nfse_fs_exp_free(fe, 0);
+ uc_export->status = 0;
+ return (0);
+ } else
+ nfse_fs_exp_free(fe, 1);
+ continue;
+ }
+#ifdef INET
+ if (uc_es->family == AF_INET) {
+ rnh = fe->ae4_rnh;
+ addr = NFSE_ADDR_EXP_ADDR4(uc_es->addr_exp);
+ mask = uc_es->maskbits != 0 ?
+ NFSE_ADDR_EXP_MASK4(uc_es->addr_exp) : NULL;
+ } else
+#endif
+#ifdef INET6
+ if (uc_es->family == AF_INET6) {
+ rnh = fe->ae6_rnh;
+ addr = NFSE_ADDR_EXP_ADDR6(uc_es->addr_exp);
+ mask = uc_es->maskbits != 0 ?
+ NFSE_ADDR_EXP_MASK6(uc_es->addr_exp) : NULL;
+ } else
+#endif
+ {
+ ae = uc_es->addr_exp;
+ switch (uc_es->command) {
+ case NFSE_CMD_EXPORT_ADD:
+ if (fe->ae_def != NULL)
+ goto failed;
+ fe->ae_def = ae;
+ uc_es->addr_exp = NULL;
+ break;
+ case NFSE_CMD_EXPORT_UPDATE:
+ if (fe->ae_def == NULL)
+ goto failed;
+ fe->ae_def->flags = ae->flags;
+ fe->ae_def->secflav = ae->secflav;
+ fe->ae_def->cred_exp = ae->cred_exp;
+ ae->secflav->ref_count++;
+ ae->cred_exp->ref_count++;
+ break;
+ case NFSE_CMD_EXPORT_DELETE:
+ if (fe->ae_def == NULL)
+ goto failed;
+ nfse_addr_exp_free(fe->ae_def);
+ fe->ae_def = NULL;
+ break;
+ }
+ continue;
+ }
+#if defined(INET) || defined(INET6)
+ switch (uc_es->command) {
+ case NFSE_CMD_EXPORT_ADD:
+ if (rnh->rnh_addaddr(addr, mask, rnh,
+ (struct radix_node *)uc_es->addr_exp) == NULL)
+ goto failed;
+ uc_es->addr_exp = NULL;
+ break;
+ case NFSE_CMD_EXPORT_UPDATE:
+ ae = (struct nfse_addr_exp *)
+ rnh->rnh_lookup(addr, mask, rnh);
+ if (ae == NULL)
+ goto failed;
+ ae->flags = uc_es->addr_exp->flags;
+ ae->secflav = uc_es->addr_exp->secflav;
+ ae->cred_exp = uc_es->addr_exp->cred_exp;
+ ae->secflav->ref_count++;
+ ae->cred_exp->ref_count++;
+ break;
+ case NFSE_CMD_EXPORT_DELETE:
+ ae = (struct nfse_addr_exp *)
+ rnh->rnh_deladdr(addr, mask, rnh);
+ if (ae == NULL)
+ goto failed;
+ nfse_addr_exp_free(ae);
+ break;
+ }
+#endif
+ }
+
+ uc_export->status = NFSE_CMD_EXPORT_MOUNTED | NFSE_CMD_EXPORT_EXPORTED;
+ return (0);
+
+failed:
+ LIST_REMOVE(fe, link);
+ nfse_fs_exp_free(fe, 0);
+ return (EPERM);
+}
+
+/*
+ * Clear all export settings.
+ */
+static void
+nfse_cmd_clear_run(void)
+{
+ struct nfse_fs_exp *fe, *fe_next;
+
+ LIST_FOREACH_SAFE(fe, &nfse_fs_exp_list, link, fe_next) {
+ LIST_REMOVE(fe, link);
+ nfse_fs_exp_free(fe, 0);
+ }
+ nfse_webnfs_free();
+}
+
+/*
+ * Verify VFS event.
+ */
+static void
+nfse_cmd_event_run(struct nfse_ucmd_event *uc_event)
+{
+ const struct nfse_fs_exp *fe;
+ const struct mount *mp;
+ const char *path;
+
+ /*
+ * Try to find file system in the exported list. If it is present
+ * in the exported list, then it is mounted.
+ */
+ path = uc_event->path;
+ LIST_FOREACH(fe, &nfse_fs_exp_list, link)
+ if (strcmp(fe->mp->mnt_stat.f_mntonname, path) == 0)
+ break;
+ if (fe != NULL) {
+ /* File system is exported. */
+ if ((uc_event->status & NFSE_CMD_EVENT_EXPORTED) &&
+ uc_event->fsid.val[0] == fe->mp->mnt_stat.f_fsid.val[0] &&
+ uc_event->fsid.val[1] == fe->mp->mnt_stat.f_fsid.val[1]) {
+ uc_event->status =
+ NFSE_CMD_EVENT_MOUNTED | NFSE_CMD_EVENT_EXPORTED;
+ } else {
+ /*
+ * File system's ID is different or it is not
+ * exported by this process.
+ */
+ uc_event->status = NFSE_CMD_EXPORT_WRONG_ID |
+ NFSE_CMD_EVENT_MOUNTED | NFSE_CMD_EVENT_EXPORTED;
+ }
+ } else {
+ /*
+ * Try to find file system in the mount list. Since a new
+ * file system is linked to the tail of the mount list, let's
+ * do reverse search to speed up finding of just mounted
+ * file systems.
+ */
+ uc_event->status = 0;
+ mtx_lock(&mountlist_mtx);
+ TAILQ_FOREACH_REVERSE(mp, &mountlist, mntlist, mnt_list)
+ if (strcmp(mp->mnt_stat.f_mntonname, path) == 0) {
+ uc_event->status = NFSE_CMD_EVENT_MOUNTED;
+ break;
+ }
+ mtx_unlock(&mountlist_mtx);
+ }
+}
+
+/*
+ * Apply WebNFS settings.
+ */
+static void
+nfse_cmd_webnfs_run(struct nfse_ucmd_webnfs *uc_webnfs)
+{
+ const struct nfse_fs_exp *fe;
+ const char *path;
+
+ nfse_webnfs_free();
+
+ path = uc_webnfs->path;
+ if (path == NULL)
+ return;
+
+ LIST_FOREACH(fe, &nfse_fs_exp_list, link)
+ if (strcmp(fe->mp->mnt_stat.f_mntonname, path) == 0)
+ break;
+ if (fe == NULL) {
+ /* File system is not exported. */
+ uc_webnfs->status = 0;
+ return;
+ }
+ if (uc_webnfs->fsid.val[0] != fe->mp->mnt_stat.f_fsid.val[0] ||
+ uc_webnfs->fsid.val[1] != fe->mp->mnt_stat.f_fsid.val[1]) {
+ /* File system is exported, but file system ID is different. */
+ uc_webnfs->status = NFSE_CMD_WEBNFS_EXPORTED |
+ NFSE_CMD_WEBNFS_WRONG_ID;
+ return;
+ }
+
+ /* Apply new WebNFS settings. */
+ nfs_pub.np_handle.fh_fsid = uc_webnfs->fsid;
+ nfs_pub.np_handle.fh_fid = uc_webnfs->fid;
+ nfs_pub.np_mount = fe->mp;
+ nfs_pub.np_index = uc_webnfs->index;
+ nfs_pub.np_valid = 1;
+
+ uc_webnfs->index = NULL;
+ uc_webnfs->status = NFSE_CMD_WEBNFS_EXPORTED;
+}
+
+/*
+ * Send result for the given succeeded command.
+ */
+static int
+nfse_cmd_succeeded(const struct nfse_ucmd *uc)
+{
+ uint32_t *uaddr;
+ int error;
+ uint32_t status;
+
+ switch (uc->command) {
+ case NFSE_CMD_EXPORT:
+ error = copyout(&((struct nfse_ucmd_export *)uc->kdata)->fsid,
+ &((struct nfse_cmd_export *)uc->udata)->fsid,
+ sizeof(((struct nfse_ucmd_export *)uc->kdata)->fsid));
+ if (error != 0)
+ return (error);
+ status = ((struct nfse_ucmd_export *)uc->kdata)->status;
+ uaddr = &((struct nfse_cmd_export *)uc->udata)->status;
+ break;
+ case NFSE_CMD_EVENT:
+ status = ((struct nfse_ucmd_event *)uc->kdata)->status;
+ uaddr = &((struct nfse_cmd_event *)uc->udata)->status;
+ break;
+ case NFSE_CMD_WEBNFS:
+ status = ((struct nfse_ucmd_webnfs *)uc->kdata)->status;
+ uaddr = &((struct nfse_cmd_webnfs *)uc->udata)->status;
+ break;
+ default:
+ return (0);
+ }
+ return (copyout(&status, uaddr, sizeof(status)));
+}
+
+/*
+ * Get all user commands and run them.
+ */
+int
+nfse_cmds(struct thread *td, struct nfse_cmds_hdr *uap)
+{
+ struct nfse_cmds_hdr hdr;
+ struct nfse_cmd cmd;
+ struct nfse_ucmd_list uc_list_s;
+ struct nfse_ucmd_list *uc_list;
+ struct nfse_ucmd *uc;
+ struct nfse_tr *tr;
+ u_int i;
+ int error, trci;
+
+ error = copyin(uap, &hdr, sizeof(hdr));
+ if (error != 0)
+ return (error);
+ if (hdr.version != NFSE_API_VERSION)
+ return (EINVAL);
+ trci = (hdr.flags & NFSE_HDR_TR_COMMIT) ? 1 : 0;
+ if (hdr.flags & NFSE_HDR_TR_START) {
+ if (hdr.trid != 0)
+ return (EINVAL);
+ if (trci) {
+ tr = NULL;
+ uc_list = &uc_list_s;
+ STAILQ_INIT(uc_list);
+ } else {
+ tr = nfse_tr_new(td);
+ uc_list = &tr->uc_list;
+ }
+ } else {
+ if (hdr.trid == 0)
+ return (EINVAL);
+ tr = nfse_tr_find(td, hdr.trid, trci);
+ if (tr == NULL)
+ return (ESRCH);
+ uc_list = &tr->uc_list;
+ }
+
+ error = 0;
+ for (i = 0; i < hdr.ncmds; ++i) {
+ error = copyin(&hdr.cmds[i], &cmd, sizeof(cmd));
+ if (error != 0)
+ return (error);
+ if (cmd.command != NFSE_CMD_EXPORT) {
+ uc = malloc(sizeof(*uc), M_NFSE, M_WAITOK);
+ STAILQ_INSERT_TAIL(uc_list, uc, link);
+ uc->command = cmd.command;
+ uc->udata = cmd.data;
+ uc->kdata = NULL;
+ }
+ switch (cmd.command) {
+ case NFSE_CMD_EXPORT:
+ error = nfse_cmd_export_get(uc_list, &cmd);
+ break;
+ case NFSE_CMD_CLEAR:
+ error = nfse_cmd_clear_get(uc_list, uc, &cmd);
+ break;
+ case NFSE_CMD_EVENT:
+ error = nfse_cmd_event_get(uc, &cmd);
+ break;
+ case NFSE_CMD_WEBNFS:
+ error = nfse_cmd_webnfs_get(uc, &cmd);
+ break;
+ default:
+ error = EINVAL;
+ }
+ if (error != 0)
+ break;
+ }
+
+ if (trci) {
+ if (error == 0) {
+ void *data;
+
+ NFSE_EXPORT_WLOCK();
+ STAILQ_FOREACH(uc, uc_list, link) {
+ data = uc->kdata;
+ switch (uc->command) {
+ case NFSE_CMD_EXPORT:
+ error = nfse_cmd_export_run(data);
+ break;
+ case NFSE_CMD_CLEAR:
+ nfse_cmd_clear_run();
+ break;
+ case NFSE_CMD_EVENT:
+ nfse_cmd_event_run(data);
+ break;
+ case NFSE_CMD_WEBNFS:
+ nfse_cmd_webnfs_run(data);
+ break;
+ }
+ if (error != 0)
+ break;
+ }
+ NFSE_EXPORT_WUNLOCK();
+ if (error == 0)
+ STAILQ_FOREACH(uc, uc_list, link) {
+ error = nfse_cmd_succeeded(uc);
+ if (error != 0)
+ break;
+ }
+ }
+ if (tr != NULL)
+ nfse_tr_free(tr);
+ else
+ nfse_ucmd_list_free(uc_list);
+ } else {
+ if (error == 0 && (hdr.flags & NFSE_HDR_TR_START))
+ error = copyout(&tr->trid, &uap->trid,
+ sizeof(tr->trid));
+ if (error != 0)
+ nfse_tr_cancel(tr);
+ else
+ nfse_tr_reset(tr);
+ }
+
+ return (error);
+}
+
+/*
+ * Check whether a host with the given address is allowed to
+ * access a file with the given handle.
+ */
+int
+nfse_check(const fhandle_t *fhp, struct sockaddr *sa, struct mount **mpp,
+ struct ucred *ucred, int *rdonlyp)
+{
+ const struct nfse_fs_exp *fe;
+ const struct nfse_addr_exp *ae;
+ int rv;
+
+ NFSE_EXPORT_RLOCK();
+ LIST_FOREACH(fe, &nfse_fs_exp_list, link)
+ if (fe->mp->mnt_stat.f_fsid.val[0] == fhp->fh_fsid.val[0] &&
+ fe->mp->mnt_stat.f_fsid.val[1] == fhp->fh_fsid.val[1])
+ break;
+ if (fe != NULL) {
+ if (sa == NULL) {
+ ae = fe->ae_def;
+ } else
+#ifdef INET
+ if (sa->sa_family == AF_INET) {
+ ae = (struct nfse_addr_exp *)
+ fe->ae4_rnh->rnh_matchaddr(sa, fe->ae4_rnh);
+ if (ae == NULL ||
+ (ae->rnodes[0].rn_flags & RNF_ROOT))
+ ae = fe->ae_def;
+ } else
+#endif
+#ifdef INET6
+ if (sa->sa_family == AF_INET6) {
+ ae = (struct nfse_addr_exp *)
+ fe->ae6_rnh->rnh_matchaddr(sa, fe->ae6_rnh);
+ if (ae == NULL ||
+ (ae->rnodes[0].rn_flags & RNF_ROOT))
+ ae = fe->ae_def;
+ } else
+#endif
+ {
+ ae = NULL;
+ }
+ if (ae != NULL && !(ae->flags & NFSE_EXFLAG_DENY)) {
+ if (ucred->cr_uid == 0 ||
+ (ae->flags & NFSE_EXFLAG_MAPALL)) {
+ const struct nfse_cred_exp *ce;
+ u_int i;
+
+ ce = ae->cred_exp;
+ ucred->cr_uid = ce->uid;
+ for (i = 0; i < ce->ngids; ++i)
+ ucred->cr_groups[i] = ce->gids[i];
+ ucred->cr_ngroups = ce->ngids;
+ }
+ vfs_ref(fe->mp);
+ *mpp = fe->mp;
+ *rdonlyp = ae->flags & NFSE_EXFLAG_RDONLY;
+ rv = 0;
+ } else
+ rv = EACCES;
+ } else
+ rv = ESTALE;
+ NFSE_EXPORT_RUNLOCK();
+ return (rv);
+}
+
+/*
+ * Handle VFS mount event: if a file system that is being mounted
+ * covers exported file system, then forget about this export since
+ * such behaviour is logical from a user process point of view.
+ */
+static void
+nfse_event_mount(void *arg1 __unused, void *arg2)
+{
+ const struct mount *mp;
+ struct nfse_fs_exp *fe, *fe_next;
+
+ mp = arg2;
+ NFSE_EXPORT_WLOCK();
+ LIST_FOREACH_SAFE(fe, &nfse_fs_exp_list, link, fe_next)
+ if (strcmp(fe->mp->mnt_stat.f_mntonname,
+ mp->mnt_stat.f_mntonname) == 0) {
+ LIST_REMOVE(fe, link);
+ break;
+ }
+ NFSE_EXPORT_WUNLOCK();
+ if (fe != NULL)
+ nfse_fs_exp_free(fe, 0);
+}
+
+/*
+ * Handle VFS unmount event: if a file system that is being unmounted
+ * is exported, then forget about this export.
+ */
+static void
+nfse_event_unmount(void *arg1 __unused, void *arg2)
+{
+ struct nfse_fs_exp *fe, *fe_next;
+
+ NFSE_EXPORT_WLOCK();
+ LIST_FOREACH_SAFE(fe, &nfse_fs_exp_list, link, fe_next)
+ if (fe->mp == arg2) {
+ LIST_REMOVE(fe, link);
+ break;
+ }
+ NFSE_EXPORT_WUNLOCK();
+ if (fe != NULL)
+ nfse_fs_exp_free(fe, 0);
+}
+
+/*
+ * Initialize NFS export code.
+ */
+void
+nfse_init(void)
+{
+ rw_init(&nfse_export_rwlock, "NFS export list");
+ mtx_init(&nfse_export_mtx, "NFS export data", NULL, MTX_DEF);
+ mtx_init(&nfse_tr_mtx, "NFS export transaction", NULL, MTX_DEF);
+ callout_init_mtx(&nfse_tr_callout, &nfse_tr_mtx, 0);
+ nfse_mount_event_tag = EVENTHANDLER_REGISTER(vfs_mount_event,
+ nfse_event_mount, NULL, EVENTHANDLER_PRI_FIRST);
+ nfse_unmount_event_tag = EVENTHANDLER_REGISTER(vfs_unmount_event,
+ nfse_event_unmount, NULL, EVENTHANDLER_PRI_FIRST);
+}
+
+/*
+ * Deinitialize NFS export code.
+ */
+void
+nfse_deinit(void)
+{
+ EVENTHANDLER_DEREGISTER(vfs_unmount_event, nfse_mount_event_tag);
+ EVENTHANDLER_DEREGISTER(vfs_unmount_event, nfse_unmount_event_tag);
+ callout_drain(&nfse_tr_callout);
+ mtx_destroy(&nfse_tr_mtx);
+ mtx_destroy(&nfse_export_mtx);
+ rw_destroy(&nfse_export_rwlock);
+ nfse_tr_free_all();
+ nfse_cmd_clear_run();
+}
diff -ruN nfsserver.orig/nfs_export.h nfsserver/nfs_export.h
--- nfsserver.orig/nfs_export.h 1970-01-01 03:00:00.000000000 +0300
+++ nfsserver/nfs_export.h 2009-06-06 13:44:45.000000000 +0300
@@ -0,0 +1,185 @@
+/*-
+ * Copyright (c) 2009 Andrey Simonenko
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD:$
+ */
+
+#ifndef _NFSSERVER_NFS_EXPORT_H_
+#define _NFSSERVER_NFS_EXPORT_H_
+
+#define NFSE_API_VERSION 1 /* API version. */
+
+#define NFSE_NSECFLAV 5 /* Number of security flavors. */
+
+/*
+ * Flags for the nfse_cmds_hdr structure.
+ */
+#define NFSE_HDR_TR_START 0x0001 /* Start a new transaction. */
+#define NFSE_HDR_TR_COMMIT 0x0002 /* Commit transaction. */
+
+/*
+ * Argument for nfssvc(2) (do not change it).
+ */
+struct nfse_cmds_hdr {
+ u_int version; /* NFSE_API_VERSION */
+ uint32_t flags; /* Header flags. */
+ u_int trid; /* Transaction ID. */
+ u_int ncmds; /* Number of commands. */
+ struct nfse_cmd *cmds; /* Array of commands. */
+};
+
+/*
+ * Command code for the nfse_cmd structure.
+ */
+#define NFSE_CMD_EXPORT 1 /* Update current export spec. */
+#define NFSE_CMD_CLEAR 2 /* Clear all export spec. */
+#define NFSE_CMD_EVENT 3 /* Check (sync) VFS events. */
+#define NFSE_CMD_WEBNFS 4 /* Update WebNFS settings. */
+
+/*
+ * Command descriptor.
+ */
+struct nfse_cmd {
+ u_int command; /* Command code. */
+ size_t size; /* Size of command's data. */
+ void *data; /* Pointer to command's data. */
+};
+
+/*
+ * The NFSE_CMD_EXPORT command modifies export specifications for
+ * a file system.
+ */
+
+/*
+ * Status flags for the nfse_cmd_export structure.
+ */
+#define NFSE_CMD_EXPORT_MOUNTED 0x0001 /* File system is mounted. */
+#define NFSE_CMD_EXPORT_EXPORTED 0x0002 /* File system is exported. */
+#define NFSE_CMD_EXPORT_WRONG_ID 0x0004 /* File system has another ID. */
+
+/*
+ * NFSE_CMD_EXPORT command descriptor.
+ */
+struct nfse_cmd_export {
+ const char *path; /* Mount point. */
+ size_t path_size; /* Size of path name. */
+ fsid_t fsid; /* File system ID. */
+ uint32_t status; /* Status flags. */
+ u_int nspec; /* Number of export spec. */
+ const struct nfse_cmd_export_spec *spec; /* Export spec. array. */
+};
+
+/*
+ * Command codes and flags for the nfse_cmd_export_spec structure.
+ */
+#define NFSE_CMD_EXPORT_ADD 0x0001 /* Add a new export spec. */
+#define NFSE_CMD_EXPORT_UPDATE 0x0002 /* Update existent export spec. */
+#define NFSE_CMD_EXPORT_DELETE 0x0003 /* Delete existent export spec. */
+#define NFSE_CMD_EXPORT_CLEAR 0x0004 /* Clear all export spec. */
+#define NFSE_CMD_EXPORT_RDONLY 0x0010 /* Read-only access (f). */
+#define NFSE_CMD_EXPORT_MAPALL 0x0020 /* Change cred. for everyone (f). */
+#define NFSE_CMD_EXPORT_DENY 0x0040 /* Deny access (f). */
+
+#define NFSE_CMD_EXPORT_CMDMASK 0x000f /* Mask for command. */
+
+/*
+ * Export specification descriptor.
+ */
+struct nfse_cmd_export_spec {
+ uint32_t flags; /* Command and flags. */
+ uid_t uid; /* Credentials UID. */
+ u_int ngids; /* Number of credentials GIDs. */
+ const gid_t *gids; /* Credentials GIDs. */
+ u_int nsec; /* Number of security flavors. */
+ const int *sec; /* Pointer to security flavors. */
+ sa_family_t family; /* Address family. */
+ u_char maskbits; /* Number of bits in mask. */
+ const uint8_t *addr; /* Pointer to address. */
+};
+
+/*
+ * The NFSE_CMD_CLEAR command clears all export specifications and
+ * it does not require additional data.
+ */
+
+/*
+ * The NFSE_CMD_EVENT command synchronizes userland and kernel vision of
+ * exported file system.
+ */
+
+/*
+ * Status flags for the nfse_cmd_export structure.
+ */
+#define NFSE_CMD_EVENT_MOUNTED 0x0001 /* File system is mounted. */
+#define NFSE_CMD_EVENT_EXPORTED 0x0002 /* File system is exported. */
+#define NFSE_CMD_EVENT_WRONG_ID 0x0004 /* File system has another ID. */
+
+/*
+ * NFSE_CMD_EVENT command descriptor.
+ */
+struct nfse_cmd_event {
+ const char *path; /* Mount point. */
+ size_t path_size; /* Size of path name. */
+ fsid_t fsid; /* File system ID. */
+ uint32_t status; /* Status flags. */
+};
+
+/*
+ * The NFSE_CMD_WEBNFS command changes WebNFS settings.
+ */
+
+/*
+ * Status flags for the nfse_cmd_webnfs structure.
+ */
+#define NFSE_CMD_WEBNFS_EXPORTED 0x0001 /* File system is exported. */
+#define NFSE_CMD_WEBNFS_WRONG_ID 0x0004 /* File system has another ID. */
+
+/*
+ * NFSE_CMD_WEBNFS command descriptor.
+ */
+struct nfse_cmd_webnfs {
+ const char *path; /* Mount point. */
+ const char *index; /* WebNFS index file. */
+ size_t path_size; /* Size of path name. */
+ size_t index_size; /* Size of index name. */
+ fsid_t fsid; /* File system ID. */
+ struct fid fid; /* File ID of root vnode. */
+ uint32_t status; /* Status flags. */
+};
+
+#ifdef _KERNEL
+
+extern int nfse_api_used;
+
+extern int nfse_cmds(struct thread *, struct nfse_cmds_hdr *);
+extern int nfse_check(const fhandle_t *, struct sockaddr *,
+ struct mount **, struct ucred *, int *);
+
+extern void nfse_init(void);
+extern void nfse_deinit(void);
+
+#endif /* _KERNEL */
+
+#endif /* !_NFSSERVER_NFS_EXPORT_H_ */
diff -ruN nfsserver.orig/nfs_srvsubs.c nfsserver/nfs_srvsubs.c
--- nfsserver.orig/nfs_srvsubs.c 2008-02-11 12:46:20.000000000 +0200
+++ nfsserver/nfs_srvsubs.c 2009-05-26 20:10:17.000000000 +0300
@@ -71,6 +71,7 @@
#include <nfs/nfsproto.h>
#include <nfsserver/nfs.h>
#include <nfs/xdr_subs.h>
+#include <nfsserver/nfs_export.h>
#include <nfsserver/nfsm_subs.h>
#include <netinet/in.h>
@@ -551,6 +552,7 @@
callout_init(&nfsrv_callout, CALLOUT_MPSAFE);
NFSD_UNLOCK();
nfsrv_timer(0);
+ nfse_init();
error = syscall_register(&nfssvc_offset, &nfssvc_sysent,
&nfssvc_prev_sysent);
@@ -570,6 +572,7 @@
callout_drain(&nfsrv_callout);
nfsrv_destroycache(); /* Free the server request cache */
mtx_destroy(&nfsd_mtx);
+ nfse_deinit();
break;
default:
error = EOPNOTSUPP;
@@ -1101,13 +1104,20 @@
fhp = &nfs_pub.np_handle;
}
- mp = vfs_getvfs(&fhp->fh_fsid);
- if (!mp)
- return (ESTALE);
- vfslocked = VFS_LOCK_GIANT(mp);
- error = VFS_CHECKEXP(mp, nam, &exflags, &credanon);
- if (error)
- goto out;
+ if (nfse_api_used) {
+ error = nfse_check(fhp, nam, &mp, cred, rdonlyp);
+ if (error != 0)
+ return (error);
+ vfslocked = VFS_LOCK_GIANT(mp);
+ } else {
+ mp = vfs_getvfs(&fhp->fh_fsid);
+ if (!mp)
+ return (ESTALE);
+ vfslocked = VFS_LOCK_GIANT(mp);
+ error = VFS_CHECKEXP(mp, nam, &exflags, &credanon);
+ if (error)
+ goto out;
+ }
error = VFS_FHTOVP(mp, &fhp->fh_fid, vpp);
if (error)
goto out;
@@ -1124,19 +1134,21 @@
}
}
#endif
- /*
- * Check/setup credentials.
- */
- if (cred->cr_uid == 0 || (exflags & MNT_EXPORTANON)) {
- cred->cr_uid = credanon->cr_uid;
- for (i = 0; i < credanon->cr_ngroups && i < NGROUPS; i++)
- cred->cr_groups[i] = credanon->cr_groups[i];
- cred->cr_ngroups = i;
- }
- if (exflags & MNT_EXRDONLY)
- *rdonlyp = 1;
- else
- *rdonlyp = 0;
+ if (!nfse_api_used) {
+ /*
+ * Check/setup credentials.
+ */
+ if (cred->cr_uid == 0 || (exflags & MNT_EXPORTANON)) {
+ cred->cr_uid = credanon->cr_uid;
+ for (i = 0; i < credanon->cr_ngroups && i < NGROUPS; i++)
+ cred->cr_groups[i] = credanon->cr_groups[i];
+ cred->cr_ngroups = i;
+ }
+ if (exflags & MNT_EXRDONLY)
+ *rdonlyp = 1;
+ else
+ *rdonlyp = 0;
+ }
if (!lockflag)
VOP_UNLOCK(*vpp, 0, td);
diff -ruN nfsserver.orig/nfs_syscalls.c nfsserver/nfs_syscalls.c
--- nfsserver.orig/nfs_syscalls.c 2008-08-02 12:59:25.000000000 +0300
+++ nfsserver/nfs_syscalls.c 2009-06-01 12:22:43.000000000 +0300
@@ -70,6 +70,7 @@
#include <nfs/rpcv2.h>
#include <nfs/nfsproto.h>
#include <nfsserver/nfs.h>
+#include <nfsserver/nfs_export.h>
#include <nfsserver/nfsm_subs.h>
#include <nfsserver/nfsrvcache.h>
@@ -143,7 +144,10 @@
"nfsd init", 0);
}
NFSD_UNLOCK();
- if (uap->flag & NFSSVC_ADDSOCK) {
+ if (uap->flag & NFSSVC_EXPORT) {
+ nfse_api_used = 1;
+ error = nfse_cmds(td, (void *)uap->argp);
+ } else if (uap->flag & NFSSVC_ADDSOCK) {
error = copyin(uap->argp, (caddr_t)&nfsdarg, sizeof(nfsdarg));
if (error)
return (error);
#########################################################################
src/usr.sbin/mountd (this is not a diff):
#########################################################################
diff -ruN empty/Makefile mountd/Makefile
--- empty/Makefile 1970-01-01 03:00:00.000000000 +0300
+++ mountd/Makefile 2009-07-17 13:39:38.000000000 +0300
@@ -0,0 +1,15 @@
+# From: @(#)Makefile 8.3 (Berkeley) 1/25/94
+# $FreeBSD: src/usr.sbin/mountd/Makefile,v 1.17 2006/05/23 17:10:17 rodrigc Exp $
+
+PROG= mountd
+SRCS= mountd.c mountd_conf.c mountd_xdr.c
+MAN= exports.5 netgroup.5 mountd.8
+
+DEBUG_FLAGS= -DDEBUG_MEMORY_LEAK
+
+WARNS?= 4
+
+DPADD= ${LIBUTIL}
+LDADD= -lutil
+
+.include <bsd.prog.mk>
diff -ruN empty/exports.5 mountd/exports.5
--- empty/exports.5 1970-01-01 03:00:00.000000000 +0300
+++ mountd/exports.5 2009-06-01 12:12:57.000000000 +0300
@@ -0,0 +1,477 @@
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)exports.5 8.3 (Berkeley) 3/29/95
+.\" $FreeBSD: src/usr.sbin/mountd/exports.5,v 1.32 2008/11/03 10:38:00 dfr Exp $
+.\"
+.Dd April 27, 2009
+.Dt EXPORTS 5
+.Os
+.Sh NAME
+.Nm exports
+.Nd configuration file for
+.Xr mountd 8
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+file specifies configuration for the
+.Xr mountd 8
+utility.
+See
+.%T "Network File System Protocol Specification" ,
+RFC1094, Appendix A,
+.%T "NFS: Network File System Version 3 Protocol Specification" ,
+RFC1813, Appendix I,
+.%T "WebNFS Client Specification" ,
+RFC2054 and
+.%T "WebNFS Server Specification" ,
+RFC2055.
+.Pp
+The
+.Nm
+file consists of lines.
+All characters after a
+.Ql #
+character (including this character) are ignored and considered as a comment.
+A long line may be split over several lines by ending all but the
+last line with a backslash
+.Pq Ql \e .
+Any character can be represented as a backslash
+.Pq Ql \e
+followed by its three digits octal code (this can be used for representing
+a character that is a part of the syntax format).
+.Pp
+There are two types of configuration lines.
+A line of the first type starts with
+.Dq options:
+and specifies global options that must be given before all other lines.
+A line of the second type starts with absolute path name followed by
+export specifications for the given path name.
+This path name will be exported only if this path name is or will be
+a mount point for some file system and there is no mistake in any
+line related to this path name.
+If a file system is exported to some client, then all its subdirectories
+and files (that belong to this file system) are also exported to this client.
+.Pp
+Each option starts with
+.Ql -
+followed by its name
+.No ( Fl opt ) .
+If an option has an argument, then an option's name and its argument
+should be separated by white space
+.Sm off
+.No ( Fl opt No \ Sy arg )
+.Sm on
+or by a
+.Ql =
+character
+.Sm off
+.No ( Fl opt No = Sy arg ) .
+.Sm on
+Options can be grouped using
+.Ql \&,
+(in this case only the first option must have
+.Ql -
+before its name and options' names and their arguments should be separated by a
+.Ql =
+character).
+.Pp
+Available options:
+.Pp
+.Bl -tag -width indent
+.Sm off
+.It Fl sec Li = Sy flavor1 Op Sy :flavor2:flavor3...
+.Sm on
+Specify 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.
+This option can be used as a global option, in this case its value
+will be used as a default value for all export specifications.
+.It Fl ro
+Specify that the file system should be exported read-only (default read/write).
+.It Fl rw
+Specify that the file system should be exported read-write (this is default).
+.Sm off
+.It Fl maproot No = Sy user Op Sy :group1:group2...
+.Sm on
+The credential of the specified user is used for remote access by root.
+The user may be specified by name or number.
+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 group names or numbers.
+Note that
+.Sy user:
+should be used to distinguish a credential containing no groups from
+a complete credential for that user.
+.Sm off
+.It Fl mapall No = Sy user Op Sy :group1:group2:...
+.Sm on
+The credential of the specified user is used for remote access by all
+users (including root).
+In the absence of
+.Fl maproot
+and
+.Fl mapall
+options, remote accesses by root will result in using a credential of
+.No -2:-2 ,
+all other users will be mapped to their remote credential.
+This option is mutually exclusive with
+.Fl maproot
+option.
+.It Fl public
+.Tn WebNFS
+exports strictly according to the RFC 2054 and RFC 2055 can be done with
+this option.
+However, this option in itself allows read-write access to all files in
+the file system (default behaviour), not requiring reserved ports and
+not remapping user credentials.
+It is only provided to conform to the specification, and should normally
+not be used.
+.It Fl webnfs
+Use this flag for a
+.Tn WebNFS
+export, which implies
+.Fl public ,
+.Sm off
+.Fl mapall No = Sy -2:-2
+.Sm on
+and
+.Fl ro .
+Note that only one file system can be
+.Tn WebNFS
+exported on a server.
+.Sm off
+.It Fl index No = Pa file
+.Sm on
+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 behaviour of
+.Tn URL Ns s .
+If this option is not specified, a directory filehandle will be returned
+as usual.
+This option only makes sense for
+.Tn WebNFS .
+.It Fl no_mntproc_dump
+Disable
+.Tn MOUNT
+protocol's procedure
+.Tn DUMP
+(return mount entries)
+for
+.Tn NFSv2/3 .
+This option can be used as a global option, in this case it will be used
+for all file systems.
+If it is used for a file system together with the
+.Fl nospec
+option, then it is used for all mounts for this file system.
+Else it is used for hosts that match particular export specification.
+.It Fl no_mntproc_export
+Disable
+.Tn MOUNT
+protocol's procedure
+.Tn EXPORT
+(return export list).
+This option can be used as a global option, in this case it will be used
+for all file systems.
+If it is used for a file system together with the
+.Fl nospec
+option, then it is used for all export specifications for this file system.
+Else it is used for particular export specification.
+.It Fl nospec
+Specify that this line does not have any address specification (including
+the default one).
+.It Fl quiet
+If a directory path name given at the beginning of a line is not
+a mount point, then all its settings will be ignored and by default
+.Xr mountd 8
+will log message about this.
+This option allows to suppress these messages.
+.Sm off
+.It Fl Oo ! Oc Cm host No = Sy hostname
+.Sm on
+Specify that corresponding file system is exported to this host, that
+can be given by name or by address.
+All associated addresses with the given hostname are added to this line.
+.Sm off
+.It Fl Oo ! Oc Cm network No = Sy netname Op Li / Sy prefixlength
+.Sm on
+Specify that corresponding file system is exported to this network.
+If the prefix length is not specified and
+.Fl mask
+option is not used, then it will default to the mask for that
+.Tn IPv4
+network class (A, B or C).
+Also a network can be specified as a
+.Dq network name
+as defined in the
+.Pa /etc/networks
+file (see
+.Xr networks 5 ) .
+.Sm off
+.It Fl mask No = Sy netmask
+.Sm on
+Specify a network mask for previous
+.Fl network
+option (that should not have a prefix length).
+The given network mask and network must belong to the same address family.
+Also a netmask can be specified as a
+.Dq network name .
+.El
+.Pp
+Options without
+.Ql - :
+.Bl -tag -width indent
+.It Oo ! Oc Ns Sy hostname
+Specify that corresponding file system is exported to this hostname.
+All hostnames are checked to see if they are
+.Dq netgroup
+names as defined in the
+.Pa /etc/netgroup
+file (see
+.Xr netgroup 5 )
+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.
+All associated addresses with this hostname are added to this line.
+.El
+.Pp
+Export specifications for one file system can be written in several lines.
+Options
+.Fl host ,
+.Fl network
+and
+.Sy hostname
+can be used multiple times.
+After any address specification it is possible to use already specified
+option and its value will overwrite previous option's value.
+.Pp
+Before any address specification it is possible to use the
+.Ql \&!
+character, that means
+.Dq deny access
+for all clients that match this address specification.
+.Pp
+If a configuration line for some file system does not have any address
+specification, then this line defines default export for all other hosts
+and should be used only when the file system contains public information.
+.Pp
+The
+.Xr nfsd 8
+and
+.Xr mountd 8
+always choose the most specific address specification (longest prefix match)
+for a client's address and uses options specified for the chosen most
+specific address specification.
+.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 sighup signal, check the
+.Xr syslogd 8
+output to see whether
+.Xr mountd 8
+logged any parsing errors in the
+.Nm
+file.
+.Pp
+Alternatively
+the
+.Xr mountd 8
+utility can be made to re-read the
+.Nm
+file by sending it a
+.Ar reload
+command.
+Read the
+.Xr mountd 8
+for detail information.
+.Sh FILES
+.Bl -tag -width /etc/exports -compact
+.It Pa /etc/exports
+default file with the list of exported file systems
+.El
+.Sh EXAMPLES
+Path names
+.Pa /usr , /u , /a , /b
+and
+.Pa /u2
+are local file system mount points.
+.Pp
+.Bd -literal -offset indent
+/usr -maproot 0:10 friends
+/usr -maproot daemon grumpy.cis.uoguelph.ca 131.104.48.16
+/usr -ro -mapall nobody
+.Ed
+.Pp
+The file system rooted at
+.Pa /usr
+is exported read-write to hosts in
+.Dq friends
+where
+.Dq friends
+is specified in the netgroup file with users mapped to their
+remote credentials and root mapped to
+.Tn UID
+0 and
+.Tn GID
+10.
+It is exported read-write to
+.Li 131.104.48.16
+and
+.Li 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
+.Bd -literal -offset indent
+/u -maproot bin: -network 131.104.48 -mask 255.255.255.0
+.Ed
+.Pp
+The file system rooted at
+.Pa /u
+is exported to all hosts on the subnetwork
+.Li 131.104.48
+with root mapped to the
+.Tn UID
+for
+.Dq bin
+and with no group access.
+.Pp
+.Bd -literal -offset indent
+/a -network 192.168.0/24
+/a -network 3ffe:1ce1:1:fe80::/64
+.Ed
+.Pp
+The file system rooted at
+.Pa /a
+is exported read-write to the network
+.Li 192.168.0.0 ,
+with a netmask of
+.Li 255.255.255.0 .
+It is also exported read-write to the
+.Tn IPv6
+network
+.Li 3ffe:1ce1:1:fe80::
+address, using the upper 64 bits as the prefix.
+Note that, unlike with
+.Tn IPv4
+network addresses, the specified network
+address must be complete, and not just contain the upper bits.
+.Pp
+.Bd -literal -offset indent
+/u2 -maproot root friends
+/u2 -network cis-net -mask cis-mask
+.Ed
+.Pp
+The file system rooted at
+.Pa /u2
+is exported read-write to the hosts in
+.Dq friends
+with root mapped to
+.Tn UID
+and groups associated with
+.Dq root ;
+it is exported read-write to all hosts on network
+.Dq cis-net
+with mask
+.Dq cis-mask
+(both are specified as
+.Dq network names ) .
+.Pp
+.Bd -literal -offset indent
+/cdrom -quiet,ro -network 192.168.33.0 -mask 255.255.255.0
+.Ed
+.Pp
+The file system rooted at
+.Pa /cdrom
+will be exported read-only to the entire network 192.168.33.0/24.
+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
+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, the kernel will notify
+.Xr mountd 8
+about this event, and the
+.Pa /cdrom
+file system will be exported as intended.
+.Pp
+.Bd -literal -offset indent
+/private -sec krb5i
+/secret -sec krb5p
+.Ed
+.Pp
+The file system rooted at
+.Pa /private
+will be exported read-write using Kerberos 5 authentication and will require
+integrity protected messages for all accesses.
+The file system rooted at
+.Pa /secret
+will also be exported read-write using Kerberos 5 authentication and all
+messages used to access it will be encrypted.
+.Pp
+.Bd -literal -offset indent
+/b -ro -mapall nobody -host 10.1.1.1 -rw -host 10.2.2.2
+.Ed
+.Pp
+The file system rooted at
+.Pa /b
+is exported read-only to
+.Li 10.1.1.1
+and with read-write access to
+.Li 10.2.2.2
+with all users mapped to the user and groups associated with
+.Dq nobody .
+The second host inherit the
+.Fl mapall
+option.
+.Sh SEE ALSO
+.Xr netgroup 5 ,
+.Xr mountd 8 ,
+.Xr nfsd 8 ,
+.Xr showmount 8
diff -ruN empty/mountd.8 mountd/mountd.8
--- empty/mountd.8 1970-01-01 03:00:00.000000000 +0300
+++ mountd/mountd.8 2009-06-02 18:01:58.000000000 +0300
@@ -0,0 +1,304 @@
+.\" 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.
+.\" 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.
+.\"
+.\" @(#)mountd.8 8.4 (Berkeley) 4/28/95
+.\" $FreeBSD: src/usr.sbin/mountd/mountd.8,v 1.31 2007/10/20 11:25:34 matteo Exp $
+.\"
+.Dd April 27, 2009
+.Dt MOUNTD 8
+.Os
+.Sh NAME
+.Nm mountd
+.Nd configure export specifications for
+.Xr nfsd 8
+and service
+.Tn NFSv2/3 MOUNT
+requests
+.Sh SYNOPSIS
+.Nm
+.Op Fl 2dlnrt
+.Op Fl c Ar command
+.Op Fl h Ar bindip
+.Op Fl p Ar port
+.Op Ar exportsfile ...
+.Sh DESCRIPTION
+The
+.Nm
+utility configures export specifications for
+.Xr nfsd 8
+and is the server for
+.Tn NFSv2/3 MOUNT
+requests from client machines.
+See
+.%T "Network File System Protocol Specification" ,
+RFC1094, Appendix A,
+.%T "NFS: Network File System Version 3 Protocol Specification" ,
+RFC1813, Appendix I and
+.%T "WebNFS Server Specification" ,
+RFC2055.
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl 2
+Allow the administrator to force clients to use only the
+.Tn NFSv2
+protocol to mount file systems from this server.
+.It Fl d
+.Nm
+will not detach from the controlling terminal and will print
+log and debugging messages to standard error stream.
+.It Fl c Ar command
+Send a command to the running
+.Nm .
+The
+.Ar reload
+command reloads
+.Xr exports 5
+files.
+The
+.Ar flush
+command flushes all export specifications for all file systems.
+Other commands change export specifications for file systems and can be
+specified multiple times:
+.Ar add
+new export specification,
+.Ar update
+existent export specification,
+.Ar delete
+existent export specification,
+.Ar flush
+all export specifications for the given file system,
+.Ar file
+means to read commands from the given file.
+The
+.Ar add ,
+.Ar update
+and
+.Ar delete
+commands accept option
+.Fl f Ar filename ,
+in this case all export specifications from the given file are
+added or deleted respectively.
+.It Fl h Ar bindip
+Specify specific
+.Tn IP
+addresses to bind to for
+.Tn TCP
+and
+.Tn UDP
+requests.
+This option may be specified multiple times.
+If no
+.Fl h
+option is specified,
+.Nm
+will accept connections on any interface with
+.Tn IP
+address.
+Note that when specifying
+.Tn IP
+addresses with
+.Fl h ,
+.Nm
+will automatically add
+.Li 127.0.0.1
+and
+.Li ::1
+to the list.
+.It Fl l
+Cause all succeeded client requests to be logged.
+.It Fl n
+Allow
+.Tn MOUNT
+protocol's procedures
+.Tn MNT ,
+.Tn UMNT
+and
+.Tn UMNTALL
+requests from unprivileged ports to be served.
+This should only be specified if there are clients that require it.
+It will automatically clear the vfs.nfsrv.nfs_privport sysctl flag, which
+controls if the kernel will accept
+.Tn NFS
+requests from reserved ports only.
+.It Fl r
+Allow
+.Tn MOUNT
+protocol's procedure
+.Tn MNT
+requests for regular files to be served.
+Although this seems to violate the
+.Tn MOUNT
+protocol specification, some diskless workstations do
+.Tn MNT
+requests for their swapfiles and expect them to be regular files.
+.It Fl p Ar port
+Force
+.Nm
+to bind to the specified port, for both
+.Dv AF_INET
+and
+.Dv AF_INET6
+address families.
+This is typically done to ensure that the port which
+.Nm
+binds to is a known quantity which can be used in firewall rulesets.
+If
+.Nm
+cannot bind to this port, an appropriate error will be recorded in
+the system log, and the daemon will then exit.
+.It Fl t
+Parse
+.Xr exports 5
+files or commands in
+.Fl c
+options and output configuration to the standard output.
+.It Ar exportsfile
+Specify an alternate location
+for the
+.Xr exports 5
+file.
+More than one file name can be specified.
+All given files must exist in the file system.
+Also its possible to specify directory names, finish their names with slash
+.Pq Ql / .
+In this case all files from the given directories will be used.
+Any given directory can be absent in the file system.
+.El
+.Pp
+When
+.Nm
+is started, it parses
+.Xr exports 5
+files and loads export specifications into the kernel only for file systems
+that have correct configuration.
+After changing the
+.Xr exports 5
+file a hangup signal should be sent to the
+.Nm
+to get it to reload export specifications and forget all changes in
+export specifications made by commands (alternative method is the
+.Ar reload
+command).
+After sending the sighup signal check log messages to see if
+.Nm
+logged any parsing errors.
+.Pp
+.Nm
+loads and changes export specifications atomically, so reloading of
+.Xr exports 5
+files or applying commands to the current configuration will not cause
+temporal failures for clients that currently are using and are allowed
+to use the same exported file systems in the new configuration.
+.Pp
+If some command in the
+.Fl c
+option cannot be applied to the current configuration, then all given
+commands will not be applied to the current configuration.
+A set of commands can be considered as one transaction with changes.
+.Pp
+If
+.Nm
+detects that the running kernel does not include
+.Tn NFS
+support, it will attempt to load a loadable kernel module containing
+.Tn NFS
+code, using
+.Xr kldload 2 .
+If this fails, or no
+.Tn NFS KLD
+was available,
+.Nm
+exits with an error code.
+.Pp
+While servicing requests and parsing
+.Xr exports 5
+files
+.Nm
+can exit with an error code only if some critical error occurs
+(such as not enough memory error or some file cannot be closed).
+.Sh FILES
+.Bl -tag -width /var/run/mountd.pid -compact
+.It Pa /etc/exports
+default file with the list of exported file systems
+.It Pa /var/run/mountd.pid
+the
+.Tn PID
+of the currently running
+.Nm
+.It Pa /var/run/mountd.socket
+local domain
+.Tn TCP
+socket for control messages
+.It Pa /var/db/mountdtab
+the current list of remote mounted file systems when
+.Nm
+is not running
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh EXAMPLES
+Test two files and files from the directory for correctness:
+.Pp
+.Dl "mountd -t export-lan exports-all /usr/local/etc/exports/"
+.Pp
+Test commands (no command is sent):
+.Pp
+.Dl "mountd -t -c 'add /fs -ro host1' -c 'file commands.txt'
+.Pp
+Add export specification:
+.Pp
+.Dl "mountd -c 'add /fs -rw host1 host2'"
+.Pp
+Change export specifications:
+.Pp
+.Dl "mountd -c 'delete /fs host1' -c 'update /fs -ro host2'"
+.Pp
+Flush export specifications and add new ones from the file:
+.Pp
+.Dl "mountd -c 'flush /fs' -c 'add -f /etc/fs-exports'
+.Pp
+Reload
+.Xr exports 5
+files (unlike sending the sighup signal this command is synchronous,
+the sender always knows when a new settings were loaded into the kernel):
+.Pp
+.Dl "mountd -c reload"
+.Sh SEE ALSO
+.Xr nfsstat 1 ,
+.Xr kldload 2 ,
+.Xr exports 5 ,
+.Xr nfsd 8 ,
+.Xr rpcbind 8 ,
+.Xr showmount 8
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Bx 4.4 .
diff -ruN empty/mountd.c mountd/mountd.c
--- empty/mountd.c 1970-01-01 03:00:00.000000000 +0300
+++ mountd/mountd.c 2009-06-26 14:12:38.000000000 +0300
@@ -0,0 +1,3084 @@
+/*-
+ * Copyright (c) 2009 Andrey Simonenko
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Herb Hasler and Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /*not lint*/
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)mountd.c 8.15 (Berkeley) 5/1/95";
+#endif /*not lint*/
+#endif
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.sbin/mountd/mountd.c,v 1.98 2008/11/03 10:38:00 dfr Exp $");
+
+#include <sys/param.h>
+#include <sys/event.h>
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/linker.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysctl.h>
+#include <sys/time.h>
+#include <sys/ucred.h>
+#include <sys/un.h>
+
+#include <arpa/inet.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpc_com.h>
+
+#include <nfs/rpcv2.h>
+#include <nfs/nfsproto.h>
+#include <nfsserver/nfs.h>
+#include <nfsserver/nfs_export.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <libutil.h>
+#include <limits.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "mountd.h"
+#include "mountd_conf.h"
+#include "mountd_xdr.h"
+#include "pathnames.h"
+
+#define KLD_NFSSERVER "nfsserver"
+
+#define MNTLISTFILE _PATH_RMOUNTLIST
+
+#define CTL_TIMEOUT 30 /* Timeout in seconds for command. */
+
+char have_ipv4 = 0; /* Set if have IPv4 bound socket. */
+char have_ipv6 = 0; /* Set if have IPv6 bound socket. */
+
+char no_mntproc_dump; /* Global -no_mntproc_dump option. */
+char no_mntproc_export; /* Global -no_mntproc_export option. */
+
+char syserr_flag = 0; /* Set if a system error occurred. */
+
+struct fs_exp_list fs_exp_list1; /* These two lists keep new and old */
+struct fs_exp_list fs_exp_list2; /* configuration. */
+struct fs_exp_list *fs_exp_list; /* Current configuration. */
+struct fs_exp_list *fs_exp_list_prev; /* Previous configuration. */
+struct fs_exp_list fs_exp_list_update; /* Updates to configuration. */
+
+char *index_webnfs = NULL; /* Index file for WebNFS. */
+
+struct ctl_cmd_hdr ctl_cmd_hdr; /* Control command header. */
+void *ctl_cmd_buf; /* Control command buffer. */
+
+char subpath_empty[] = ""; /* No subpath (to simplify code). */
+
+char test_conf = 0; /* -t */
+
+static char force_nfsv2 = 0; /* -2 */
+static char log_cli_reqs = 0; /* -l */
+static char dir_only = 1; /* -r */
+
+static int resvport_only = 1; /* -n */
+static in_port_t svcport = 0; /* -p port */
+
+static int ctl_listen_fd = -1; /* Control listen socket descriptor. */
+static char ctl_socket_bound = 0; /* Non-zero if socket file is bound. */
+
+/*
+ * netconfig(5) file has network configuration for RPC.
+ * This structure has configuration for one network ID.
+ */
+struct netconf {
+ const char *netid; /* Network ID. */
+ const struct netconfig *nc; /* Corresponding netconfig. */
+ int visible; /* NC_VISIBLE flag. */
+};
+
+#define NETID_UDP 0
+#define NETID_TCP 1
+#define NETID_UDP6 2
+#define NETID_TCP6 3
+
+/*
+ * Table of all supported network configurations.
+ */
+static struct netconf netconf_tbl[] = {
+ [NETID_UDP] = { .netid = "udp", .nc = NULL },
+ [NETID_TCP] = { .netid = "tcp", .nc = NULL },
+ [NETID_UDP6] = { .netid = "udp6", .nc = NULL },
+ [NETID_TCP6] = { .netid = "tcp6", .nc = NULL }
+};
+
+#define NC_TBL_SIZE (sizeof(netconf_tbl) / sizeof(netconf_tbl[0]))
+
+/*
+ * Union for remembering registered services.
+ */
+static union {
+ uint8_t net[4];
+ uint32_t any;
+} rpcmnt_reg[2];
+
+/*
+ * Requirements of RPC mount procedure.
+ */
+struct mntproc_req {
+ int priv; /* Requires privilege port. */
+ const char *name; /* Name of this procedure. */
+};
+
+/*
+ * Table of RPC mount procedures.
+ * NFSv2 and NFSv3 define RPC mount procedures numbers (both standards
+ * use the same numbers for the same procedures, NULLPROC has 0 number,
+ * RPCMNT_* have numbers 1, 2, 3, 4, 5).
+ */
+static const struct mntproc_req mntproc_req[] = {
+ [NULLPROC] = { .priv = 0, .name = "null" },
+ [RPCMNT_MOUNT] = { .priv = 1, .name = "mnt" },
+ [RPCMNT_DUMP] = { .priv = 0, .name = "dump" },
+ [RPCMNT_UMOUNT] = { .priv = 1, .name = "umnt" },
+ [RPCMNT_UMNTALL] = { .priv = 1, .name = "umntall" },
+ [RPCMNT_EXPORT] = { .priv = 0, .name = "export" }
+};
+
+#define RPCMNT_MAX_NUMBER RPCMNT_EXPORT
+
+/* Pipe for IPC from asynchronous signals handlers. */
+static int sig_pipe[2];
+
+/* Flags for signal handles. */
+static volatile sig_atomic_t signal_flag = 0;
+static volatile sig_atomic_t reconf_flag = 0;
+static volatile sig_atomic_t shutdown_flag = 0;
+
+#define EXPSPEC_ES_NUM 20 /* Number of export specifications
+ per one nfssvc() system call. */
+
+#define EXPSPEC_TO_SLEEP 3 /* Sleep this number of seconds
+ after transaction timeout. */
+
+#define EXPSPEC_RET_OK 0 /* Function succeeded. */
+#define EXPSPEC_RET_FAILED 1 /* Function failed. */
+#define EXPSPEC_RET_TIMEOUT 2 /* Commands transaction timeout. */
+
+#define EXPSPEC_FN_CLEAR 0 /* Clear all export specifications. */
+#define EXPSPEC_FN_RELOAD 1 /* Load new configuration. */
+#define EXPSPEC_FN_UPDATE 2 /* Update current configuration. */
+#define EXPSPEC_FN_EVENT 3 /* Handle VFS events. */
+
+static int expspec_clear(void);
+static int expspec_reload(void);
+static int expspec_update(void);
+static int expspec_event(void);
+
+/*
+ * Table of functions that work with export specifications in nfsserver.
+ * Do not call expspec_*() functions directly outside of expspec_*()
+ * functions, use the expspec_func() function instead.
+ */
+static struct {
+ int (*func)(void); /* Pointer to function. */
+ const char *name; /* Function name. */
+} expspec_func_tbl[] = {
+ [EXPSPEC_FN_CLEAR] =
+ { .func = expspec_clear, .name = "expspec_clear" },
+ [EXPSPEC_FN_RELOAD] =
+ { .func = expspec_reload, .name = "expspec_reload" },
+ [EXPSPEC_FN_UPDATE] =
+ { .func = expspec_update, .name = "expspec_update" },
+ [EXPSPEC_FN_EVENT] =
+ { .func = expspec_event, .name = "expspec_event" }
+};
+
+/*
+ * SIGHUP handler.
+ */
+/* ARGSUSED */
+static void
+sig_hup(int signo __unused)
+{
+ (void)write(sig_pipe[1], "", 1);
+ signal_flag = 1;
+ reconf_flag = 1;
+}
+
+/*
+ * SIGTERM (and SIGINT) handler.
+ */
+/* ARGSUSED */
+static void
+sig_term(int signo __unused)
+{
+ (void)write(sig_pipe[1], "", 1);
+ signal_flag = 1;
+ shutdown_flag = 1;
+}
+
+/*
+ * Make descriptor non-blockable.
+ */
+static int
+set_nonblock(const int fd)
+{
+ int val;
+
+ val = fcntl(fd, F_GETFL, 0);
+ if (val < 0) {
+ syslog(LOG_ERR, "set_nonblock: fcntl(%d, F_GETFL): %m", fd);
+ return (-1);
+ }
+ if (fcntl(fd, F_SETFL, val | O_NONBLOCK) < 0) {
+ syslog(LOG_ERR, "set_nonblock: fcntl(%d, F_SETFL): %m", fd);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Make descriptor blockable.
+ */
+static int
+set_block(const int fd)
+{
+ int val;
+
+ val = fcntl(fd, F_GETFL, 0);
+ if (val < 0) {
+ syslog(LOG_ERR, "set_block: fcntl(%d, F_GETFL): %m", fd);
+ return (-1);
+ }
+ if (fcntl(fd, F_SETFL, val & ~O_NONBLOCK) < 0) {
+ syslog(LOG_ERR, "set_block: fcntl(%d, F_SETFL): %m", fd);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Find exported fs_exp by a file system ID.
+ */
+static struct fs_exp *
+fs_exp_by_id(const fsid_t *fsid)
+{
+ struct fs_exp *fe;
+
+ TAILQ_FOREACH(fe, fs_exp_list, link)
+ if ((fe->oflags & OPT_EXPORTED) &&
+ fe->fsid.val[0] == fsid->val[0] &&
+ fe->fsid.val[1] == fsid->val[1])
+ break;
+ return (fe);
+}
+
+/*
+ * Find fs_exp by a directory path.
+ */
+struct fs_exp *
+fs_exp_by_path(const struct fs_exp_list *fe_list, const char *path)
+{
+ struct fs_exp *fe;
+
+ TAILQ_FOREACH(fe, fe_list, link)
+ if (strcmp(fe->path, path) == 0)
+ break;
+ return (fe);
+}
+
+/*
+ * Send commands to nfsserver.
+ */
+static int
+nfsserver_call(struct nfse_cmds_hdr *hdr)
+{
+ if (nfssvc(NFSSVC_EXPORT, hdr) == 0)
+ return (EXPSPEC_RET_OK);
+
+ if (errno == ESRCH) {
+ syslog(LOG_WARNING, "nfsserver_call: nfssvc(NFSSVC_EXPORT): "
+ "commands transaction timeout");
+ return (EXPSPEC_RET_TIMEOUT);
+ }
+
+ syslog(LOG_ERR, "nfsserver_call: nfssvc(NFSSVC_EXPORT): %m");
+ return (EXPSPEC_RET_FAILED);
+}
+
+/*
+ * Clear all export specifications.
+ */
+static int
+expspec_clear(void)
+{
+ static struct nfse_cmds_hdr hdr = {
+ .version = NFSE_API_VERSION,
+ .flags = NFSE_HDR_TR_START|NFSE_HDR_TR_COMMIT,
+ .trid = 0,
+ .ncmds = 1
+ };
+ static struct nfse_cmd cmd = {
+ .command = NFSE_CMD_CLEAR,
+ .size = 0,
+ .data = NULL
+ };
+
+ hdr.cmds = &cmd;
+ return (nfsserver_call(&hdr));
+}
+
+/*
+ * Copy content of the addr_exp structure to the nfse_cmd_export_spec
+ * structure.
+ */
+static void
+copy_ae_to_es(const sa_family_t family, const struct addr_exp *ae,
+ struct nfse_cmd_export_spec *cmd_es)
+{
+ uint32_t cmd;
+
+ cmd = _CMD_GET(ae->oflags);
+ switch (cmd) {
+ case CMD_ADD:
+ cmd_es->flags = NFSE_CMD_EXPORT_ADD;
+ break;
+ case CMD_UPDATE:
+ cmd_es->flags = NFSE_CMD_EXPORT_UPDATE;
+ break;
+ case CMD_DELETE:
+ cmd_es->flags = NFSE_CMD_EXPORT_DELETE;
+ break;
+ }
+ if (cmd != CMD_DELETE) {
+ if (ae->oflags & OPT_RO)
+ cmd_es->flags |= NFSE_CMD_EXPORT_RDONLY;
+ if (ae->oflags & OPT_MAPALL)
+ cmd_es->flags |= NFSE_CMD_EXPORT_MAPALL;
+ if (ae->oflags & OPT_DENY)
+ cmd_es->flags |= NFSE_CMD_EXPORT_DENY;
+ cmd_es->nsec = ae->secflavors->nsec;
+ cmd_es->sec = ae->secflavors->sec;
+ cmd_es->uid = ae->cred_exp->uid;
+ cmd_es->ngids = ae->cred_exp->ngids;
+ cmd_es->gids = ae->cred_exp->gids;
+ }
+ cmd_es->family = family;
+ if (family != AF_UNSPEC) {
+ const struct addr_spec *as;
+
+ as = ae->addr_spec;
+ cmd_es->maskbits = (u_char)as->maskbits;
+ cmd_es->addr = family == AF_INET ?
+ (const uint8_t *)&ADDR4_SPEC_CPTR(as)->addr.s_addr :
+ (const uint8_t *)&ADDR6_SPEC_CPTR(as)->addr.s6_addr;
+ } else
+ cmd_es->maskbits = 0;
+}
+
+/*
+ * This function copies not more than *n_p export specifications from fs_exp
+ * to the array of nfse_cmd_export_spec structures. First call to this
+ * function for a new fs_exp must be with AF_UNSPEC family and *ae_p equal
+ * to NULL. The value *ae_p should not be interpreted by a caller. If this
+ * function returns zero, then all address specifications were copied.
+ * On return *n_p is equal to the free entries in the cmd_es array.
+ */
+static int
+copy_fe_to_es(const struct fs_exp *const fe, sa_family_t *family_p,
+ struct addr_exp **const ae_p, u_int *n_p,
+ struct nfse_cmd_export_spec *cmd_es)
+{
+ struct addr_exp *ae;
+ u_int i;
+ sa_family_t family;
+
+ family = *family_p;
+ ae = *ae_p;
+ i = *n_p;
+
+ if (i == 0)
+ return (1);
+
+ if (family == AF_UNSPEC) {
+ if (ae == NULL && (fe->oflags & CMD_OPT_FLUSH)) {
+ cmd_es->flags = NFSE_CMD_EXPORT_CLEAR;
+ cmd_es->family = AF_UNSPEC;
+ cmd_es->maskbits = 0;
+ ++cmd_es;
+ --i;
+ }
+ ae = fe->ae_def;
+ if (ae != NULL)
+ if (i != 0) {
+ copy_ae_to_es(AF_UNSPEC, ae, cmd_es++);
+ --i;
+ ae = NULL;
+ }
+ if (ae == NULL) {
+ family = AF_INET;
+ ae = TAILQ_FIRST(&fe->ae4_list);
+ }
+ }
+ if (family == AF_INET) {
+ for (; i != 0 && ae != NULL; ae = TAILQ_NEXT(ae, link), --i)
+ copy_ae_to_es(AF_INET, ae, cmd_es++);
+ if (ae == NULL) {
+ family = AF_INET6;
+ ae = TAILQ_FIRST(&fe->ae6_list);
+ }
+ }
+ if (family == AF_INET6) {
+ for (; i != 0 && ae != NULL; ae = TAILQ_NEXT(ae, link), --i)
+ copy_ae_to_es(AF_INET6, ae, cmd_es++);
+ }
+
+ *family_p = family;
+ *ae_p = ae;
+ *n_p = i;
+ return (ae != NULL);
+}
+
+/*
+ * Check result of the NFSE_CMD_EXPORT command.
+ */
+static int
+check_cmd_export(struct fs_exp *fe, const struct nfse_cmd_export *cmd_export,
+ const int verbose_type)
+{
+ uint32_t quiet;
+
+ if (cmd_export->status & NFSE_CMD_EXPORT_WRONG_ID) {
+ if (!(fe->oflags & OPT_EXPORTED))
+ syslog(LOG_ERR, "check_cmd_export: file system "
+ "%s was exported by another process", fe->path);
+ else
+ syslog(LOG_ERR, "check_cmd_export: file system ID "
+ "mismatch for %s, it was re-exported by another "
+ "process", fe->path);
+ return (-1);
+ }
+ quiet = fe->oflags & OPT_QUIET;
+ if (cmd_export->status & NFSE_CMD_EXPORT_EXPORTED) {
+ if (verbose_type != 0 && !quiet)
+ syslog(LOG_WARNING, "directory %s now is a mount "
+ "point and is exported", fe->path);
+ fe->oflags |= OPT_EXPORTED;
+ fe->fsid = cmd_export->fsid;
+ } else if (fe->oflags & OPT_EXPORTED) {
+ fe->oflags &= ~OPT_EXPORTED;
+ mntlist_free_mh_list(&fe->mh4_list);
+ mntlist_free_mh_list(&fe->mh6_list);
+ if (verbose_type != 0 && !quiet)
+ syslog(LOG_WARNING, "directory %s now is not a mount "
+ "point and is not exported any more", fe->path);
+ } else {
+ if (verbose_type == 0 && !quiet)
+ syslog(LOG_WARNING, "directory %s is not a mount "
+ "point and cannot be exported", fe->path);
+ }
+ return (0);
+}
+
+/*
+ * Load settings for WebNFS, this loading should be done as one commands
+ * transaction, that cannot have timeout. In several points this function
+ * fails, but returns no error code, since that mistakes mean that a file
+ * system was unmounted or re-mounted (configuration will be synchronized
+ * soon).
+ */
+static int
+expspec_webnfs(const struct fs_exp *fe)
+{
+ static struct nfse_cmds_hdr hdr = {
+ .version = NFSE_API_VERSION,
+ .flags = NFSE_HDR_TR_START|NFSE_HDR_TR_COMMIT,
+ .trid = 0,
+ .ncmds = 1
+ };
+ static struct nfse_cmd cmd = {
+ .command = NFSE_CMD_WEBNFS,
+ .size = sizeof(struct nfse_cmd_webnfs)
+ };
+
+ struct nfse_cmd_webnfs cmd_webnfs;
+ fhandle_t fh;
+ int rv;
+
+ if (!(fe->oflags & OPT_EXPORTED))
+ return (EXPSPEC_RET_OK);
+
+ if (getfh(fe->path, &fh) < 0) {
+ if (crit_fs_err(errno)) {
+ syslog(LOG_ERR, "expspec_webnfs: getfh: %m");
+ return (EXPSPEC_RET_FAILED);
+ } else
+ return (EXPSPEC_RET_OK);
+ }
+ if (fh.fh_fsid.val[0] != fe->fsid.val[0] ||
+ fh.fh_fsid.val[1] != fe->fsid.val[1])
+ return (EXPSPEC_RET_OK);
+
+ hdr.cmds = &cmd;
+ cmd.data = &cmd_webnfs;
+
+ cmd_webnfs.path = fe->path;
+ cmd_webnfs.path_size = fe->path_len + 1;
+ if (index_webnfs == NULL) {
+ cmd_webnfs.index = NULL;
+ cmd_webnfs.index_size = 0;
+ } else {
+ cmd_webnfs.index = index_webnfs;
+ cmd_webnfs.index_size = strlen(index_webnfs) + 1;
+ }
+ cmd_webnfs.fsid = fe->fsid;
+ cmd_webnfs.fid = fh.fh_fid;
+
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ return (rv);
+
+ if (cmd_webnfs.status & NFSE_CMD_WEBNFS_EXPORTED) {
+ if (cmd_webnfs.status & NFSE_CMD_WEBNFS_WRONG_ID) {
+ syslog(LOG_ERR, "expspec_webnfs: file system ID "
+ "mismatch for %s, it was re-exported by another "
+ "process", fe->path);
+ return (EXPSPEC_RET_FAILED);
+ }
+ } else {
+ /* Public file system is not exported any more. */
+ }
+ return (EXPSPEC_RET_OK);
+}
+
+/*
+ * Reload export specifications for all file systems.
+ * This implementation does not make difference of settings in fs_exp_list
+ * and in fs_exp_list_prev, it simply clear everything and add new settings.
+ */
+static int
+expspec_reload(void)
+{
+ struct nfse_cmd cmds[EXPSPEC_ES_NUM + 1];
+ struct nfse_cmd_export_spec cmd_es[EXPSPEC_ES_NUM];
+ struct nfse_cmds_hdr hdr;
+ const struct fs_exp_list *fe_list;
+ struct nfse_cmd_export *cmd_export;
+ struct fs_exp *fe, *fe_webnfs;
+ struct addr_exp *ae;
+ u_int n, ci, cei, cesi;
+ int rv;
+ sa_family_t family;
+
+ n = 0;
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH(fe, fe_list, link)
+ ++n;
+ if (n != 0) {
+ cmd_export = malloc(n * sizeof(*cmd_export));
+ if (cmd_export == NULL) {
+ syslog(LOG_ERR, "expspec_reload: malloc: %m");
+ return (EXPSPEC_RET_FAILED);
+ }
+ } else
+ cmd_export = NULL;
+
+ hdr.version = NFSE_API_VERSION;
+ hdr.flags = NFSE_HDR_TR_START;
+ hdr.trid = 0;
+ hdr.ncmds = 1;
+ hdr.cmds = cmds;
+
+ cmds[0].command = NFSE_CMD_CLEAR;
+ cmds[0].size = 0;
+ cmds[0].data = NULL;
+
+ fe_webnfs = NULL;
+
+ ci = 1;
+ cei = 0;
+ cesi = 0;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ hdr.ncmds++;
+ cmds[ci].command = NFSE_CMD_EXPORT;
+ cmds[ci].size = sizeof(*cmd_export);
+ cmds[ci].data = &cmd_export[cei];
+
+ cmd_export[cei].path = fe->path;
+ cmd_export[cei].path_size = fe->path_len + 1;
+ cmd_export[cei].status = 0;
+
+ ae = NULL;
+ family = AF_UNSPEC;
+ for (n = EXPSPEC_ES_NUM - cesi;; n = EXPSPEC_ES_NUM) {
+ if (copy_fe_to_es(fe, &family, &ae, &n,
+ &cmd_es[cesi]) == 0)
+ break;
+ cmd_export[cei].nspec = EXPSPEC_ES_NUM - cesi;
+ cmd_export[cei].spec = &cmd_es[cesi];
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ goto failed;
+ hdr.flags = 0;
+ hdr.ncmds = 1;
+ cmds[0].command = NFSE_CMD_EXPORT;
+ cmds[0].size = sizeof(*cmd_export);
+ cmds[0].data = &cmd_export[cei];
+ cmd_export[cei].path = NULL;
+ ci = 1;
+ cesi = 0;
+ }
+
+ cmd_export[cei].nspec = EXPSPEC_ES_NUM - cesi - n;
+ cmd_export[cei].spec = &cmd_es[cesi];
+
+ if (n == 0) {
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ goto failed;
+ hdr.flags = 0;
+ hdr.ncmds = 0;
+ ci = 0;
+ cesi = 0;
+ } else {
+ ci++;
+ cesi = EXPSPEC_ES_NUM - n;
+ }
+
+ cei++;
+
+ if (fe->oflags & OPT_PUBLIC)
+ fe_webnfs = fe;
+ }
+
+ hdr.flags |= NFSE_HDR_TR_COMMIT;
+
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ goto failed;
+
+ cei = 0;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ if (check_cmd_export(fe, &cmd_export[cei], 0) < 0) {
+ rv = EXPSPEC_RET_FAILED;
+ goto failed;
+ }
+ cei++;
+ }
+
+ free(cmd_export);
+
+ rv = (fe_webnfs == NULL) ? EXPSPEC_RET_OK : expspec_webnfs(fe_webnfs);
+ return (rv);
+
+failed:
+ free(cmd_export);
+ return (rv);
+}
+
+/*
+ * Update export specifications.
+ */
+static int
+expspec_update(void)
+{
+ struct nfse_cmd cmds[EXPSPEC_ES_NUM];
+ struct nfse_cmd_export_spec cmd_es[EXPSPEC_ES_NUM];
+ struct nfse_cmds_hdr hdr;
+ const struct fs_exp_list *fe_list;
+ struct nfse_cmd_export *cmd_export;
+ struct fs_exp *fe;
+ struct addr_exp *ae;
+ u_int n, ci, cei, cesi;
+ int rv;
+ sa_family_t family;
+
+ n = 0;
+ fe_list = &fs_exp_list_update;
+ TAILQ_FOREACH(fe, fe_list, link)
+ if (fe->oflags & (OPT_EXPORTED|OPT_NEWEXPORT))
+ ++n;
+ if (n == 0)
+ return (EXPSPEC_RET_OK);
+ cmd_export = malloc(n * sizeof(*cmd_export));
+ if (cmd_export == NULL) {
+ syslog(LOG_ERR, "expspec_update: malloc: %m");
+ return (EXPSPEC_RET_FAILED);
+ }
+
+ hdr.version = NFSE_API_VERSION;
+ hdr.flags = NFSE_HDR_TR_START;
+ hdr.trid = 0;
+ hdr.ncmds = 0;
+ hdr.cmds = cmds;
+
+ ci = 0;
+ cei = 0;
+ cesi = 0;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ if (!(fe->oflags & (OPT_EXPORTED|OPT_NEWEXPORT)))
+ continue;
+
+ hdr.ncmds++;
+ cmds[ci].command = NFSE_CMD_EXPORT;
+ cmds[ci].size = sizeof(*cmd_export);
+ cmds[ci].data = &cmd_export[cei];
+
+ cmd_export[cei].path = fe->path;
+ cmd_export[cei].path_size = fe->path_len + 1;
+ if (fe->oflags & OPT_EXPORTED) {
+ cmd_export[cei].fsid = fe->fsid;
+ cmd_export[cei].status = NFSE_CMD_EXPORT_EXPORTED;
+ } else
+ cmd_export[cei].status = 0;
+
+ ae = NULL;
+ family = AF_UNSPEC;
+ for (n = EXPSPEC_ES_NUM - cesi;; n = EXPSPEC_ES_NUM) {
+ if (copy_fe_to_es(fe, &family, &ae, &n,
+ &cmd_es[cesi]) == 0)
+ break;
+ cmd_export[cei].nspec = EXPSPEC_ES_NUM - cesi;
+ cmd_export[cei].spec = &cmd_es[cesi];
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ goto failed;
+ hdr.flags = 0;
+ hdr.ncmds = 1;
+ cmds[0].command = NFSE_CMD_EXPORT;
+ cmds[0].size = sizeof(*cmd_export);
+ cmds[0].data = &cmd_export[cei];
+ cmd_export[cei].path = NULL;
+ ci = 1;
+ cesi = 0;
+ }
+
+ cmd_export[cei].nspec = EXPSPEC_ES_NUM - cesi - n;
+ cmd_export[cei].spec = &cmd_es[cesi];
+
+ if (n == 0) {
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ goto failed;
+ hdr.flags = 0;
+ hdr.ncmds = 0;
+ ci = 0;
+ cesi = 0;
+ } else {
+ ci++;
+ cesi = EXPSPEC_ES_NUM - n;
+ }
+
+ cei++;
+ }
+
+ hdr.flags |= NFSE_HDR_TR_COMMIT;
+
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ goto failed;
+
+ cei = 0;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ if (!(fe->oflags & (OPT_EXPORTED|OPT_NEWEXPORT)))
+ continue;
+ fe->oflags &= ~OPT_NEWEXPORT;
+ if (check_cmd_export(fe, &cmd_export[cei], 0) < 0) {
+ rv = EXPSPEC_RET_FAILED;
+ goto failed;
+ }
+ cei++;
+ }
+
+ free(cmd_export);
+ return (EXPSPEC_RET_OK);
+
+failed:
+ free(cmd_export);
+ return (rv);
+}
+
+/*
+ * Load settings for one file system, but do not log warning messages
+ * if this file system is not mounted.
+ */
+static int
+expspec_load_one(struct fs_exp *fe)
+{
+ struct nfse_cmd_export_spec cmd_es[EXPSPEC_ES_NUM];
+ struct nfse_cmd cmd;
+ struct nfse_cmds_hdr hdr;
+ struct nfse_cmd_export cmd_export;
+ struct addr_exp *ae;
+ u_int n;
+ int rv;
+ sa_family_t family;
+
+ hdr.version = NFSE_API_VERSION;
+ hdr.flags = NFSE_HDR_TR_START;
+ hdr.trid = 0;
+ hdr.ncmds = 1;
+ hdr.cmds = &cmd;
+
+ cmd.command = NFSE_CMD_EXPORT;
+ cmd.size = sizeof(cmd_export);
+ cmd.data = &cmd_export;
+
+ cmd_export.path = fe->path;
+ cmd_export.path_size = fe->path_len + 1;
+ cmd_export.status = 0;
+ cmd_export.nspec = EXPSPEC_ES_NUM;
+ cmd_export.spec = cmd_es;
+
+ ae = NULL;
+ family = AF_UNSPEC;
+ for (n = EXPSPEC_ES_NUM;; n = EXPSPEC_ES_NUM) {
+ if (copy_fe_to_es(fe, &family, &ae, &n, cmd_es) == 0)
+ break;
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ return (rv);
+ hdr.flags = 0;
+ cmd_export.path = NULL;
+ }
+
+ cmd_export.nspec = EXPSPEC_ES_NUM - n;
+ hdr.flags |= NFSE_HDR_TR_COMMIT;
+
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ return (rv);
+
+ if (check_cmd_export(fe, &cmd_export, 1) < 0)
+ return (EXPSPEC_RET_FAILED);
+
+ rv = !(fe->oflags & OPT_PUBLIC) ? EXPSPEC_RET_OK : expspec_webnfs(fe);
+ return (rv);
+}
+
+/*
+ * Synchronize VFS events.
+ * Since VFS events are asynchronous, let's check all events one by one.
+ */
+static int
+expspec_event(void)
+{
+ static struct nfse_cmds_hdr hdr = {
+ .version = NFSE_API_VERSION,
+ .flags = NFSE_HDR_TR_START|NFSE_HDR_TR_COMMIT,
+ .trid = 0,
+ .ncmds = 1
+ };
+ static struct nfse_cmd cmd = {
+ .command = NFSE_CMD_EVENT,
+ .size = sizeof(struct nfse_cmd_event)
+ };
+
+ struct nfse_cmd_event cmd_event;
+ const struct fs_exp_list *fe_list;
+ struct fs_exp *fe;
+ int rv;
+
+ hdr.cmds = &cmd;
+ cmd.data = &cmd_event;
+
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ cmd_event.path = fe->path;
+ cmd_event.path_size = fe->path_len + 1;
+ if (fe->oflags & OPT_EXPORTED) {
+ cmd_event.status = NFSE_CMD_EVENT_EXPORTED;
+ cmd_event.fsid = fe->fsid;
+ } else
+ cmd_event.status = 0;
+ rv = nfsserver_call(&hdr);
+ if (rv != EXPSPEC_RET_OK)
+ return (rv);
+ if (cmd_event.status & NFSE_CMD_EVENT_EXPORTED) {
+ if (!(fe->oflags & OPT_EXPORTED)) {
+ syslog(LOG_ERR, "expspec_event: file system "
+ "%s was exported by another process",
+ fe->path);
+ return (EXPSPEC_RET_FAILED);
+ }
+ if (cmd_event.status & NFSE_CMD_EVENT_WRONG_ID) {
+ syslog(LOG_ERR, "expspec_event: file system "
+ "ID mismatch for %s, it was re-exported "
+ "by another process", fe->path);
+ return (EXPSPEC_RET_FAILED);
+ }
+ } else if (cmd_event.status & NFSE_CMD_EVENT_MOUNTED) {
+ return (expspec_load_one(fe));
+ } else if (fe->oflags & OPT_EXPORTED) {
+ struct nfse_cmd_export cmd_export;
+
+ cmd_export.status = 0;
+ (void)check_cmd_export(fe, &cmd_export, 1);
+ }
+ }
+
+ return (EXPSPEC_RET_OK);
+}
+
+/*
+ * Call one of expspec_*() function. If called function had transaction
+ * timeout, then check whether there are pending SIGTERM or SIGINT signals.
+ * If these signals are pending, then set shutdown_flag and return
+ * immediately. Otherwise sleep for a while and retry function call.
+ */
+static int
+expspec_func(const u_int funcno)
+{
+ sigset_t sigset;
+
+ for (;;) {
+ switch (expspec_func_tbl[funcno].func()) {
+ case EXPSPEC_RET_OK:
+ return (0);
+ case EXPSPEC_RET_FAILED:
+ syslog(LOG_ERR, "%s: function failed",
+ expspec_func_tbl[funcno].name);
+ return (-1);
+ }
+ syslog(LOG_WARNING, "%s: retrying in %u seconds",
+ expspec_func_tbl[funcno].name, EXPSPEC_TO_SLEEP);
+ if (sigpending(&sigset) < 0) {
+ syslog(LOG_ERR, "expspec_func: sigpending: %m");
+ return (-1);
+ }
+ if (sigismember(&sigset, SIGTERM) ||
+ sigismember(&sigset, SIGINT)) {
+ shutdown_flag = 1;
+ return (-1);
+ }
+ sleep(EXPSPEC_TO_SLEEP);
+ }
+}
+
+/*
+ * Find the most specific address specification for address
+ * in sa of the given family in address specifications from fe.
+ * Return pointer to addr_exp structure or NULL if no match
+ * was found.
+ */
+static const struct addr_exp *
+lookup_addr_exp(const struct fs_exp *fe, const struct sockaddr *sa)
+{
+ const struct addr_exp *ae;
+
+ /*
+ * All addr_exp structures are sorted by maskbits value as follows:
+ * at the beginning all structures with zero maskbits (hosts), then
+ * all structures in descending order of maskbits value.
+ */
+ if (sa->sa_family == AF_INET) {
+ struct in_addr in_addr;
+ const struct addr4_spec *as4;
+
+ in_addr = ((const struct sockaddr_in *)sa)->sin_addr;
+ TAILQ_FOREACH(ae, &fe->ae4_list, link) {
+ as4 = ADDR4_SPEC_CPTR(ae->addr_spec);
+ if ((in_addr.s_addr & as4->mask.s_addr) ==
+ as4->addr.s_addr)
+ break;
+ }
+ } else {
+ const struct addr6_spec *as6;
+ const uint32_t *addr1, *addr2, *mask;
+ u_int i;
+
+ addr1 = (const uint32_t *)
+ &((const struct sockaddr_in6 *)sa)->sin6_addr.s6_addr;
+ TAILQ_FOREACH(ae, &fe->ae6_list, link) {
+ as6 = ADDR6_SPEC_CPTR(ae->addr_spec);
+ addr2 = (const uint32_t *)&as6->addr.s6_addr;
+ mask = (const uint32_t *)&as6->mask.s6_addr;
+ for (i = 0; i < IPV6_ADDR_LEN / 4; ++i)
+ if ((addr1[i] & mask[i]) != addr2[i])
+ goto next_a6;
+ break;
+next_a6: ;
+ }
+ }
+
+ return (ae != NULL ? ae : fe->ae_def);
+}
+
+/*
+ * Release memory used by one mnt_host.
+ */
+static void
+mntlist_free_mh(struct mnt_host *mh)
+{
+ const struct mnt_path_list *mp_list;
+ struct mnt_path *mp, *mp_next;
+
+ mp_list = &mh->mp_list;
+ LIST_FOREACH_SAFE(mp, mp_list, link, mp_next) {
+ if (mp->subpath != subpath_empty)
+ free(mp->subpath);
+ free(mp);
+ }
+ LIST_REMOVE(mh, link);
+ free(mh);
+}
+
+/*
+ * Release memory used by one mnt_host_list.
+ */
+void
+mntlist_free_mh_list(struct mnt_host_list *mh_list)
+{
+ struct mnt_host *mh, *mh_next;
+
+ LIST_FOREACH_SAFE(mh, mh_list, link, mh_next)
+ mntlist_free_mh(mh);
+}
+
+/*
+ * Check whether all hosts from the appropriate mnt_host_list
+ * still can mount paths from the file system.
+ */
+void
+mntlist_recheck(const u_int family, struct fs_exp *fe)
+{
+ const struct mnt_host_list *mh_list;
+ const struct addr_exp *ae;
+ struct mnt_host *mh, *mh_next;
+
+ if (fe->ae_def != NULL)
+ return;
+
+ if (family == AF_INET) {
+ struct sockaddr_in sa4;
+
+ sa4.sin_family = AF_INET;
+ mh_list = &fe->mh4_list;
+ LIST_FOREACH_SAFE(mh, mh_list, link, mh_next) {
+ sa4.sin_addr = *MNT_HOST_ADDR4(mh);
+ ae = lookup_addr_exp(fe, (struct sockaddr *)&sa4);
+ if (ae == NULL || (ae->oflags & OPT_DENY))
+ mntlist_free_mh(mh);
+ }
+ } else {
+ struct sockaddr_in6 sa6;
+
+ sa6.sin6_family = AF_INET6;
+ mh_list = &fe->mh6_list;
+ LIST_FOREACH_SAFE(mh, mh_list, link, mh_next) {
+ sa6.sin6_addr = *MNT_HOST_ADDR6(mh);
+ ae = lookup_addr_exp(fe, (struct sockaddr *)&sa6);
+ if (ae == NULL || (ae->oflags & OPT_DENY))
+ mntlist_free_mh(mh);
+ }
+ }
+}
+
+/*
+ * Move list from head1 to head2.
+ */
+#define LIST_MOVE(head1, head2, field) do { \
+ if (!LIST_EMPTY((head1))) { \
+ LIST_FIRST((head2)) = LIST_FIRST((head1)); \
+ LIST_FIRST((head2))->field.le_prev = &LIST_FIRST((head2));\
+ LIST_INIT((head1)); \
+ } \
+} while (0);
+
+/*
+ * Copy mount information from the previous configuration to
+ * the new configuration.
+ */
+static void
+mntlist_inherit(void)
+{
+ struct fs_exp_list *fe1_list, *fe2_list;
+ struct fs_exp *fe1, *fe2;
+
+ if (no_mntproc_dump)
+ return;
+ fe1_list = fs_exp_list_prev;
+ fe2_list = fs_exp_list;
+ TAILQ_FOREACH(fe1, fe1_list, link) {
+ if (!(fe1->oflags & OPT_EXPORTED) ||
+ (fe1->oflags & OPT_NO_MNT_DMP))
+ continue;
+ fe2 = fs_exp_by_path(fe2_list, fe1->path);
+ if (fe2 != NULL && (fe2->oflags & OPT_EXPORTED) &&
+ !(fe2->oflags & OPT_NO_MNT_DMP)) {
+ LIST_MOVE(&fe1->mh4_list, &fe2->mh4_list, link);
+ mntlist_recheck(AF_INET, fe2);
+ LIST_MOVE(&fe1->mh6_list, &fe2->mh6_list, link);
+ mntlist_recheck(AF_INET6, fe2);
+ }
+ }
+}
+
+/*
+ * Find mnt_host in fs_exp for the given address.
+ */
+static struct mnt_host *
+mntlist_find_mh(struct fs_exp *fe, const struct sockaddr *sa,
+ const int new_flag)
+{
+ struct mnt_host_list *mh_list;
+ struct mnt_host *mh;
+
+ if (sa->sa_family == AF_INET) {
+ struct in_addr in_addr;
+
+ in_addr = ((const struct sockaddr_in *)sa)->sin_addr;
+ mh_list = &fe->mh4_list;
+ LIST_FOREACH(mh, mh_list, link)
+ if (in_addr.s_addr == MNT_HOST_ADDR4(mh)->s_addr)
+ return (mh);
+ if (new_flag) {
+ mh = malloc(MNT_HOST_IPV4_SIZE);
+ if (mh == NULL) {
+ syslog(LOG_ERR, "mntlist_find_mh: malloc: %m");
+ return (NULL);
+ }
+ *MNT_HOST_ADDR4(mh) = in_addr;
+ LIST_INSERT_HEAD(mh_list, mh, link);
+ LIST_INIT(&mh->mp_list);
+ return (mh);
+ }
+ } else {
+ struct in6_addr in6_addr;
+
+ in6_addr = ((const struct sockaddr_in6 *)sa)->sin6_addr;
+ mh_list = &fe->mh6_list;
+ LIST_FOREACH(mh, mh_list, link)
+ if (addrcmp(in6_addr.s6_addr,
+ MNT_HOST_ADDR6(mh)->s6_addr, IPV6_ADDR_LEN) == 0)
+ return (mh);
+ if (new_flag) {
+ mh = malloc(MNT_HOST_IPV6_SIZE);
+ if (mh == NULL) {
+ syslog(LOG_ERR, "mntlist_find_mh: malloc: %m");
+ return (NULL);
+ }
+ *MNT_HOST_ADDR6(mh) = in6_addr;
+ LIST_INSERT_HEAD(mh_list, mh, link);
+ LIST_INIT(&mh->mp_list);
+ return (mh);
+ }
+ }
+ return (NULL);
+}
+
+/*
+ * Find mnt_path in mnt_host for the given subpath.
+ */
+static struct mnt_path *
+mntlist_find_mp(const struct mnt_host *mh, const char *subpath)
+{
+ const struct mnt_path_list *mp_list;
+ struct mnt_path *mp;
+
+ mp_list = &mh->mp_list;
+ LIST_FOREACH(mp, &mh->mp_list, link)
+ if (strcmp(mp->subpath, subpath) == 0)
+ return (mp);
+ return (NULL);
+}
+
+/*
+ * Dump one line with host and mounted path to MNTLISTFILE.
+ */
+static int
+mntlist_write_line(FILE *fp, const u_int family, const struct mnt_host *mh,
+ const char *path, const char *subpath)
+{
+ char host[INET6_ADDRSTRLEN];
+
+ if (inet_ntop(family, MNT_HOST_ADDR(mh), host, sizeof(host)) == NULL) {
+ syslog(LOG_ERR, "mntlist_write: inet_ntop: %m");
+ return (0);
+ }
+ if ((subpath == subpath_empty ? fprintf(fp, "%s %s\n", host, path) :
+ fprintf(fp, "%s %s//%s\n", host, path, subpath)) < 0) {
+ syslog(LOG_ERR, "mntlist_write: fprintf to %s failed: %m",
+ MNTLISTFILE);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Dump all lists of hosts and mounted paths to MNTLISTFILE.
+ */
+static int
+mntlist_write(void)
+{
+ const struct mnt_host_list *mh_list;
+ const struct fs_exp_list *fe_list;
+ const struct fs_exp *fe;
+ struct mnt_host *mh;
+ const struct mnt_path *mp;
+ FILE *fp;
+ u_int family;
+ int rv;
+
+ fp = fopen(MNTLISTFILE, "w");
+ if (fp == NULL) {
+ syslog(LOG_ERR, "mntlist_write: fopen(%s, \"w\"): %m",
+ MNTLISTFILE);
+ return (-1);
+ }
+
+ rv = 0;
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ for (family = AF_INET, mh_list = &fe->mh4_list;;) {
+ LIST_FOREACH(mh, mh_list, link)
+ LIST_FOREACH(mp, &mh->mp_list, link)
+ if (mntlist_write_line(fp, family,
+ mh, fe->path, mp->subpath) < 0) {
+ rv = -1;
+ goto done;
+ }
+ if (mh_list == &fe->mh6_list)
+ break;
+ family = AF_INET6;
+ mh_list = &fe->mh6_list;
+ }
+ }
+done:
+ if (fclose(fp) != 0) {
+ syslog(LOG_ERR, "mntlist_write: fclose(%s): %m", MNTLISTFILE);
+ rv = -1;
+ }
+ if (rv != 0)
+ if (unlink(MNTLISTFILE) < 0)
+ syslog(LOG_ERR, "mntlist_write: unlink(%s): %m",
+ MNTLISTFILE);
+ return (rv);
+}
+
+/*
+ * Parse one line from MNTLISTFILE. Mistakes like "not enough memory" are
+ * ignored, since information from this file is not important for mountd.
+ */
+#define MNTLIST_READ_OK 0 /* Line was successfully parsed. */
+#define MNTLIST_READ_FORMAT 1 /* Wrong format, ignore this line. */
+#define MNTLIST_READ_IGNORE 2 /* Ignore this line. */
+#define MNTLIST_READ_SKIP 3 /* Silently skip this line. */
+#define MNTLIST_READ_FINISH 4 /* Finish file parsing. */
+static int
+mntlist_read_line(char *str, size_t str_size)
+{
+ struct sockaddr_in6 sa6;
+ struct sockaddr_in sa4;
+ struct in6_addr in6_addr;
+ struct in_addr in_addr;
+ const struct addr_exp *ae;
+ struct mnt_host *mh;
+ struct mnt_path *mp;
+ struct fs_exp *fe;
+ void *addr;
+ char *host, *path, *subpath, *end;
+ ssize_t ssize;
+ u_int family;
+
+ /* Check if string is not too long. */
+ end = strchr(str, '\n');
+ if (end == NULL) {
+ syslog(LOG_WARNING, "mntlist_read: wrong format of %s: "
+ "too long line (> %zu bytes) or no newline at "
+ "the end, ignoring this \"%s\" line and rest of "
+ "lines", MNTLISTFILE, str_size - 1, str);
+ return (MNTLIST_READ_FINISH);
+ }
+ *end = '\0';
+
+ /* Validate format "host /path" */
+ host = str;
+ path = strchr(str, ' ');
+ if (path == NULL) {
+ syslog(LOG_WARNING, "mntlist_read: path name is absent");
+ return (MNTLIST_READ_FORMAT);
+ }
+ *path++ = '\0';
+ if (*path != '/') {
+ syslog(LOG_WARNING, "mntlist_read: no path with "
+ "absolute name");
+ return (MNTLIST_READ_FORMAT);
+ }
+ subpath = strstr(path, "//");
+ if (subpath != NULL) {
+ *subpath = '\0';
+ subpath += 2;
+ } else
+ subpath = subpath_empty;
+
+ /* (host path end) validate lengths. */
+ if (path - host > INET6_ADDRSTRLEN) {
+ syslog(LOG_WARNING, "mntlist_read: too long (> %u bytes) "
+ "host name", INET6_ADDRSTRLEN);
+ return (MNTLIST_READ_FORMAT);
+ }
+ ssize = subpath == subpath_empty ? (PATH_MAX - 1) : PATH_MAX;
+ if (end - path > ssize) {
+ syslog(LOG_WARNING, "mntlist_read: too long (> %zd bytes) "
+ "path name", ssize);
+ return (MNTLIST_READ_FORMAT);
+ }
+
+ fe = fs_exp_by_path(fs_exp_list, path);
+ if (fe == NULL || !(fe->oflags & OPT_EXPORTED) ||
+ (fe->oflags & OPT_NO_MNT_DMP))
+ return (MNTLIST_READ_SKIP);
+
+ /* Find or create mnt_host. */
+ if (strchr(host, '.') != NULL) {
+ family = AF_INET;
+ addr = &in_addr;
+ } else {
+ family = AF_INET6;
+ addr = &in6_addr;
+ }
+ switch (inet_pton(family, host, addr)) {
+ case 1:
+ break;
+ case 0:
+ syslog(LOG_WARNING, "mntlist_read: cannot interpret %s "
+ "address", host);
+ return (MNTLIST_READ_FORMAT);
+ default:
+ syslog(LOG_ERR, "mntlist_read: inet_pton: %m");
+ return (MNTLIST_READ_IGNORE);
+ }
+ if (family == AF_INET) {
+ if (!have_ipv4)
+ return (MNTLIST_READ_SKIP);
+ sa4.sin_family = AF_INET;
+ sa4.sin_addr.s_addr = in_addr.s_addr;
+ ae = lookup_addr_exp(fe, (struct sockaddr *)&sa4);
+ if (ae == NULL || (ae->oflags & (OPT_DENY|OPT_NO_MNT_DMP)))
+ return (MNTLIST_READ_SKIP);
+ mh = mntlist_find_mh(fe, (struct sockaddr *)&sa4, 1);
+ } else {
+ if (!have_ipv6)
+ return (MNTLIST_READ_SKIP);
+ sa6.sin6_family = AF_INET6;
+ sa6.sin6_addr = in6_addr;
+ ae = lookup_addr_exp(fe, (struct sockaddr *)&sa6);
+ if (ae == NULL || (ae->oflags & (OPT_DENY|OPT_NO_MNT_DMP)))
+ return (MNTLIST_READ_SKIP);
+ mh = mntlist_find_mh(fe, (struct sockaddr *)&sa6, 1);
+ }
+ if (mh == NULL)
+ return (MNTLIST_READ_SKIP);
+
+ /* Check duplicated directory. */
+ mp = mntlist_find_mp(mh, subpath);
+ if (mp != NULL) {
+ syslog(LOG_WARNING, "mntlist_read: found duplicated path for "
+ "host %s", host);
+ return (MNTLIST_READ_FORMAT);
+ }
+
+ /* New directory. */
+ if ((mp = malloc(sizeof(*mp))) == NULL || (subpath != subpath_empty &&
+ (mp->subpath = strdup(subpath)) == NULL)) {
+ syslog(LOG_ERR, "mntlist_read: malloc/strdup: %m");
+ free(mp);
+ if (LIST_EMPTY(&mh->mp_list)) {
+ LIST_REMOVE(mh, link);
+ free(mh);
+ }
+ return (MNTLIST_READ_IGNORE);
+ }
+ if (subpath == subpath_empty)
+ mp->subpath = subpath_empty;
+ LIST_INSERT_HEAD(&mh->mp_list, mp, link);
+
+ return (MNTLIST_READ_OK);
+}
+
+/*
+ * Read MNTLISTFILE and create lists of hosts and mounted paths.
+ * Maximum length of one line in MNTLISTFILE is: IPv6 address, one space,
+ * maximum path length plus one '/' as a subpath separator and
+ * new-line character (and NUL character at the end).
+ */
+static int
+mntlist_read(void)
+{
+ FILE *fp;
+ int error;
+ char str[INET6_ADDRSTRLEN + PATH_MAX + 2];
+
+ error = 0;
+ if (no_mntproc_dump)
+ goto done;
+
+ fp = fopen(MNTLISTFILE, "r");
+ if (fp == NULL) {
+ if (errno != ENOENT)
+ syslog(LOG_ERR, "mntlist_read: cannot open %s for "
+ "reading: %m", MNTLISTFILE);
+ return (0);
+ }
+
+ while (fgets(str, (int)sizeof(str), fp) != NULL)
+ switch (mntlist_read_line(str, sizeof(str))) {
+ case MNTLIST_READ_OK:
+ continue;
+ case MNTLIST_READ_FORMAT:
+ syslog(LOG_ERR, "mntlist_read: wrong format of %s: "
+ "ignoring line \"%s\"", MNTLISTFILE, str);
+ continue;
+ case MNTLIST_READ_IGNORE:
+ syslog(LOG_ERR, "mntlist_read: ignoring line \"%s\"",
+ str);
+ continue;
+ case MNTLIST_READ_SKIP:
+ continue;
+ }
+
+ if (ferror(fp)) {
+ syslog(LOG_ERR, "mntlist_read: read from %s failed, "
+ "ignoring rest of lines: %m", MNTLISTFILE);
+ error = -1;
+ }
+
+ if (fclose(fp) != 0) {
+ syslog(LOG_ERR, "mntlist_read: fclose(%s): %m", MNTLISTFILE);
+ error = -1;
+ }
+done:
+ if (unlink(MNTLISTFILE) < 0) {
+ if (errno != ENOENT)
+ error = -1;
+ syslog(LOG_ERR, "mntlist_read: unlink(%s): %m", MNTLISTFILE);
+ }
+
+ return (error);
+}
+
+/*
+ * Remove host and path from the appropriate mount list.
+ */
+static void
+mntlist_del(struct fs_exp *fe, const struct sockaddr *sa, const char *path)
+{
+ struct mnt_host *mh;
+ struct mnt_path *mp;
+
+ mh = mntlist_find_mh(fe, sa, 0);
+ if (mh == NULL)
+ return;
+
+ path += fe->path_len;
+ if (*path == '\0')
+ path = subpath_empty;
+ else
+ path++;
+
+ mp = mntlist_find_mp(mh, path);
+ if (mp != NULL) {
+ LIST_REMOVE(mp, link);
+ if (mp->subpath != subpath_empty)
+ free(mp->subpath);
+ free(mp);
+ if (LIST_EMPTY(&mh->mp_list)) {
+ LIST_REMOVE(mh, link);
+ free(mh);
+ }
+ }
+}
+
+/*
+ * Remove all mount entries with the given host.
+ */
+static void
+mntlist_del_host(const struct sockaddr *sa)
+{
+ const struct fs_exp_list *fe_list;
+ struct fs_exp *fe;
+ struct mnt_host *mh;
+
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ if ((fe->oflags & OPT_EXPORTED) ||
+ (fe->oflags & OPT_NO_MNT_DMP))
+ continue;
+ mh = mntlist_find_mh(fe, sa, 0);
+ if (mh != NULL)
+ mntlist_free_mh(mh);
+ }
+}
+
+/*
+ * Add host and path to the appropriate mount list.
+ */
+static void
+mntlist_add(struct fs_exp *fe, const struct sockaddr *sa, const char *path)
+{
+ struct mnt_host *mh;
+ struct mnt_path *mp;
+
+ mh = mntlist_find_mh(fe, sa, 1);
+ if (mh == NULL)
+ return;
+
+ path += fe->path_len;
+ if (*path == '\0')
+ path = subpath_empty;
+ else
+ path++;
+
+ mp = mntlist_find_mp(mh, path);
+ if (mp != NULL)
+ return;
+
+ if ((mp = malloc(sizeof(*mp))) == NULL ||
+ (path != subpath_empty && (mp->subpath = strdup(path)) == NULL)) {
+ syslog(LOG_ERR, "mntlist_add: malloc/strdup: %m");
+ free(mp);
+ if (LIST_EMPTY(&mh->mp_list)) {
+ LIST_REMOVE(mh, link);
+ free(mh);
+ }
+ } else {
+ if (path == subpath_empty)
+ mp->subpath = subpath_empty;
+ LIST_INSERT_HEAD(&mh->mp_list, mp, link);
+ }
+}
+
+/*
+ * Convert errno value to appropriate NFS error number.
+ * NFSv2 and NFSv3 define error code, both standards share the same
+ * enumeration for most of error codes.
+ */
+static u_int
+errno_to_nfserr(int error)
+{
+ switch (error) {
+ case EACCES:
+ error = NFSERR_ACCES;
+ break;
+ case ENOENT:
+ error = NFSERR_NOENT;
+ break;
+ case ENOTDIR:
+ error = NFSERR_NOTDIR;
+ break;
+ case EIO:
+ error = NFSERR_IO;
+ break;
+ case ENAMETOOLONG:
+ error = NFSERR_NAMETOL;
+ break;
+ default:
+ error = NFSERR_ACCES;
+ }
+ return (error);
+}
+
+#define MNTSRV_RET_OK 0 /* Procedure succeeded. */
+#define MNTSRV_RET_RETURN 1 /* Procedure finished, return. */
+#define MNTSRV_RET_CANTREPLY 2 /* Cannot send some reply. */
+#define MNTSRV_RET_CANTDECODE 3 /* Cannot decode request. */
+#define MNTSRV_RET_FAILED 4 /* Some error occurred. */
+
+/*
+ * NULLPROC.
+ */
+static int
+mntproc_nullproc(SVCXPRT *xprt)
+{
+ if (!svc_sendreply(xprt, (xdrproc_t)xdr_void, NULL))
+ return (MNTSRV_RET_CANTREPLY);
+ return (MNTSRV_RET_OK);
+}
+
+/*
+ * MOUNTPROC MNT.
+ */
+static int
+mntproc_mnt(SVCXPRT *xprt, struct svc_req *rqst, const struct sockaddr *sa,
+ const char *host)
+{
+ struct statfs statfsbuf;
+ struct stat statbuf;
+ struct fhreturn fhr;
+ const struct addr_exp *ae;
+ struct fs_exp *fe;
+ int error;
+ char rpcpath[RPCMNT_PATHLEN + 1];
+ char path[PATH_MAX];
+
+ if (!svc_getargs(xprt, (xdrproc_t)xdr_path, rpcpath))
+ return (MNTSRV_RET_CANTDECODE);
+
+ if (realpath(rpcpath, path) == NULL) {
+ error = EACCES;
+ syslog(LOG_NOTICE, "mntproc_mnt: request from %s for %s: "
+ "realpath failed at %s: %m", host, rpcpath, path);
+ } else if (statfs(path, &statfsbuf) < 0) {
+ error = errno;
+ syslog(LOG_NOTICE, "mntproc_mnt: request from %s for %s: "
+ "statfs(%s): %m", host, rpcpath, path);
+ if (crit_fs_err(error))
+ return (MNTSRV_RET_FAILED);
+ error = EACCES;
+ } else if (lstat(path, &statbuf) < 0) {
+ error = errno;
+ syslog(LOG_NOTICE, "mntproc_mnt: request from %s for %s: "
+ "lstat(%s): %m", host, rpcpath, path);
+ if (crit_fs_err(error))
+ return (MNTSRV_RET_FAILED);
+ } else if (!S_ISDIR(statbuf.st_mode) &&
+ (dir_only || !S_ISREG(statbuf.st_mode))) {
+ error = ENOTDIR;
+ syslog(LOG_NOTICE, "mntproc_mnt: request from %s for %s: "
+ "resolved path %s is %s", host, rpcpath, path,
+ dir_only ? "not a directory" :
+ "neither a directory nor a regular file");
+ } else
+ error = 0;
+
+ if (error == EACCES) {
+ /* Send reply immediately, since statfs() could fail. */
+ goto failed;
+ }
+
+ /* File system exists. */
+ fe = fs_exp_by_id(&statfsbuf.f_fsid);
+ if (fe == NULL) {
+ /* If file system is not exported, then deny. */
+ error = EACCES;
+ goto failed;
+ }
+ if (strncmp(fe->path, path, fe->path_len) != 0) {
+ /* Outdated fs_exp structure, it will be synchronized soon. */
+ error = EACCES;
+ goto failed;
+ }
+ ae = lookup_addr_exp(fe, sa);
+ if (ae == NULL || (ae->oflags & OPT_DENY)) {
+ /* File system is not exported to a client. */
+ error = EACCES;
+ syslog(LOG_NOTICE, "mntproc_mnt: deny request from %s for %s "
+ "(resolved %s)", host, rpcpath, path);
+ goto failed;
+ }
+
+ /* File system is exported to client. */
+ if (error != 0) {
+ /*
+ * Report a true error code to client, since it is
+ * allowed to mount this file system.
+ */
+ goto failed;
+ }
+
+ /* Can export requested path, return file handle. */
+ fhr.fhr_vers = rqst->rq_vers;
+ memset(&fhr.fhr_fh, 0, sizeof(fhr.fhr_fh));
+ if (getfh(path, (fhandle_t *)&fhr.fhr_fh) < 0) {
+ error = errno;
+ syslog(LOG_NOTICE, "mntproc_mnt: getfh(%s): %m", path);
+ if (crit_fs_err(error))
+ return (MNTSRV_RET_FAILED);
+ goto failed;
+ }
+ fhr.fhr_nsec = ae->secflavors->nsec;
+ fhr.fhr_sec = ae->secflavors->sec;
+ if (!svc_sendreply(xprt, (xdrproc_t)xdr_fhs, &fhr))
+ return (MNTSRV_RET_CANTREPLY);
+ if (!(ae->oflags & OPT_NO_MNT_DMP))
+ mntlist_add(fe, sa, path);
+ if (log_cli_reqs)
+ syslog(LOG_NOTICE, "mntproc_mnt: request <mnt> succeeded from "
+ "%s for %s (resolved path %s, matched %s address spec in "
+ "file system %s)",
+ host, rpcpath, path, ae->addr_spec != NULL ?
+ addr_spec_str(sa->sa_family, ae->addr_spec) : "default",
+ fe->path);
+ return (MNTSRV_RET_RETURN);
+
+failed:
+ error = errno_to_nfserr(error);
+ if (!svc_sendreply(xprt, (xdrproc_t)xdr_int, &error))
+ return (MNTSRV_RET_CANTREPLY);
+ return (MNTSRV_RET_RETURN);
+}
+
+/*
+ * MOUNTPROC DUMP.
+ */
+static int
+mntproc_dump(SVCXPRT *xprt)
+{
+ if (!svc_sendreply(xprt, (xdrproc_t)xdr_mntlist, NULL))
+ return (MNTSRV_RET_CANTREPLY);
+ return (MNTSRV_RET_OK);
+}
+
+/*
+ * MOUNTPROC UMNT.
+ */
+static int
+mntproc_umnt(SVCXPRT *xprt, const struct sockaddr *sa, const char *host)
+{
+ char rpcpath[RPCMNT_PATHLEN + 1];
+ char path[PATH_MAX];
+
+ if (!svc_getargs(xprt, (xdrproc_t)xdr_path, rpcpath))
+ return (MNTSRV_RET_CANTDECODE);
+ if (realpath(rpcpath, path) == NULL) {
+ syslog(LOG_NOTICE, "mntproc_umnt: request from %s for %s: "
+ "realpath failed at %s: %m", host, rpcpath, path);
+ path[0] = '\0';
+ } else if (!no_mntproc_dump) {
+ struct statfs statfsbuf;
+
+ if (statfs(path, &statfsbuf) < 0)
+ syslog(LOG_NOTICE, "mntproc_umnt: request from %s "
+ "for %s: statfs(%s): %m", host, rpcpath, path);
+ else {
+ struct fs_exp *fe;
+
+ fe = fs_exp_by_id(&statfsbuf.f_fsid);
+ if (fe != NULL && !(fe->oflags & OPT_NO_MNT_DMP) &&
+ strncmp(fe->path, path, fe->path_len) == 0)
+ mntlist_del(fe, sa, path);
+ }
+ }
+ if (!svc_sendreply(xprt, (xdrproc_t)xdr_void, NULL))
+ return (MNTSRV_RET_CANTREPLY);
+ if (log_cli_reqs)
+ syslog(LOG_NOTICE, "mntproc_umnt: request <umnt> succeeded "
+ "from %s for %s (resolved path %s)", host, rpcpath,
+ path[0] != '\0' ? path : "<not resolved>");
+ return (MNTSRV_RET_RETURN);
+}
+
+/*
+ * MOUNTPROC UMNTALL.
+ */
+static int
+mntproc_umntall(SVCXPRT *xprt, const struct sockaddr *sa)
+{
+ if (!svc_sendreply(xprt, (xdrproc_t)xdr_void, NULL))
+ return (MNTSRV_RET_CANTREPLY);
+ if (!no_mntproc_dump)
+ mntlist_del_host(sa);
+ return (MNTSRV_RET_OK);
+}
+
+/*
+ * MOUNTPROC EXPORT.
+ */
+static int
+mntproc_export(SVCXPRT *xprt)
+{
+ if (!svc_sendreply(xprt, (xdrproc_t)xdr_export_compl, NULL) &&
+ !svc_sendreply(xprt, (xdrproc_t)xdr_export_brief, NULL))
+ return (MNTSRV_RET_CANTREPLY);
+ return (MNTSRV_RET_OK);
+}
+
+/*
+ * The RPCPROG_MNT service dispatch procedure.
+ */
+static void
+mntsrv_dispatch(struct svc_req *rqst, SVCXPRT *xprt)
+{
+ const struct sockaddr *sa;
+ const void *addr;
+ u_int family;
+ uint32_t rq_proc;
+ in_port_t port;
+ char host[INET6_ADDRSTRLEN];
+
+ sa = svc_getrpccaller(xprt)->buf;
+
+ switch (family = sa->sa_family) {
+ case AF_INET:
+ port = ntohs(((const struct sockaddr_in *)sa)->sin_port);
+ addr = &((const struct sockaddr_in *)sa)->sin_addr;
+ break;
+ case AF_INET6:
+ port = ntohs(((const struct sockaddr_in6 *)sa)->sin6_port);
+ addr = &((const struct sockaddr_in6 *)sa)->sin6_addr;
+ break;
+ default:
+ syslog(LOG_ERR, "mntsrv_dispatch: request from unsupported "
+ "address family %u", family);
+ return;
+ }
+ if (inet_ntop(family, addr, host, sizeof(host)) == NULL) {
+ syslog(LOG_ERR, "mntsrv_dispatch: inet_ntop: %m");
+ host[0] = '?';
+ host[1] = '\0';
+ }
+
+ rq_proc = rqst->rq_proc;
+ if (rq_proc <= RPCMNT_MAX_NUMBER) {
+ const struct mntproc_req *mpreq;
+ int rv;
+
+ mpreq = &mntproc_req[rq_proc];
+ if (mpreq->priv && port >= IPPORT_RESERVED && resvport_only) {
+ syslog(LOG_NOTICE, "mntproc_%s: deny request from %s "
+ "from unprivileged port %"PRIu16, mpreq->name,
+ host, port);
+ svcerr_weakauth(xprt);
+ return;
+ }
+ switch (rq_proc) {
+ case NULLPROC:
+ rv = mntproc_nullproc(xprt);
+ break;
+ case RPCMNT_MOUNT:
+ rv = mntproc_mnt(xprt, rqst, sa, host);
+ break;
+ case RPCMNT_DUMP:
+ rv = mntproc_dump(xprt);
+ break;
+ case RPCMNT_UMOUNT:
+ rv = mntproc_umnt(xprt, sa, host);
+ break;
+ case RPCMNT_UMNTALL:
+ rv = mntproc_umntall(xprt, sa);
+ break;
+ case RPCMNT_EXPORT:
+ rv = mntproc_export(xprt);
+ break;
+ default:
+ syslog(LOG_ERR, "mntsrv_dispatch: unexpected "
+ "procedure number %"PRIu32, rq_proc);
+ return;
+ }
+ switch (rv) {
+ case MNTSRV_RET_OK:
+ if (log_cli_reqs)
+ syslog(LOG_NOTICE, "mntproc_%s: request <%s> "
+ "succeeded from %s", mpreq->name,
+ mpreq->name, host);
+ break;
+ case MNTSRV_RET_RETURN:
+ break;
+ case MNTSRV_RET_CANTREPLY:
+ syslog(LOG_ERR, "mntproc_%s: cannot send reply to %s",
+ mpreq->name, host);
+ break;
+ case MNTSRV_RET_CANTDECODE:
+ syslog(LOG_ERR, "mntproc_%s: cannot decode request "
+ "from %s", mpreq->name, host);
+ break;
+ case MNTSRV_RET_FAILED:
+ syslog(LOG_ERR, "mntproc_%s: some system call "
+ "unexpectedly failed", mpreq->name);
+ break;
+ }
+ } else {
+ syslog(LOG_ERR, "mntsrv_dispatch: unknown request number "
+ "%"PRIu32" from %s", rq_proc, host);
+ svcerr_noproc(xprt);
+ }
+}
+
+/*
+ * Establish a mapping between RPC mount, version number, network
+ * and address on the rpcbind service.
+ */
+static int
+mntsrv_set(const rpcvers_t versnum, const struct netconf *netconf,
+ struct sockaddr *sa)
+{
+ struct netbuf netbuf;
+ struct in6_addr in6_addr;
+ struct in_addr in_addr;
+
+ /*
+ * Have to set wildcard address here, since rpcbind does not allow
+ * multiple settings for the same network ID.
+ */
+ if (sa->sa_family == AF_INET) {
+ in_addr = ((struct sockaddr_in *)sa)->sin_addr;
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr =
+ htonl(INADDR_ANY);
+ } else {
+ in6_addr = ((struct sockaddr_in6 *)sa)->sin6_addr;
+ ((struct sockaddr_in6 *)sa)->sin6_addr = in6addr_any;
+ }
+
+ netbuf.maxlen = netbuf.len = sa->sa_len;
+ netbuf.buf = sa;
+ if (!rpcb_set(RPCPROG_MNT, versnum, netconf->nc, &netbuf)) {
+ syslog(LOG_ERR, "mntsrv_set: network %s: rpcb_set failed "
+ "for RPCMNT_VER%"PRIu32, netconf->netid, versnum);
+ return (-1);
+ }
+
+ /* Restore address. */
+ if (sa->sa_family == AF_INET)
+ ((struct sockaddr_in *)sa)->sin_addr = in_addr;
+ else
+ ((struct sockaddr_in6 *)sa)->sin6_addr = in6_addr;
+
+ return (0);
+}
+
+/*
+ * Create and register RPCPROG_MNT service for given socket address.
+ */
+static int
+mntsrv_reg(const u_int netidnum, struct sockaddr *sa, const char *host)
+{
+ const struct netconfig *nc;
+ const struct netconf *netconf;
+ const char *netid;
+ SVCXPRT *xprt;
+ in_port_t *port;
+ u_int bufsize, family;
+ int fd, val, socktype, sockproto;
+ char ver1_reg, ver3_reg;
+
+ netconf = &netconf_tbl[netidnum];
+ netid = netconf->netid;
+ nc = netconf->nc;
+ if (nc == NULL) {
+ syslog(LOG_WARNING, "mntsrv_reg: host %s, network %s: cannot "
+ "create service since corresponding netconfig is absent",
+ host, netid);
+ return (0);
+ }
+ if (!netconf->visible)
+ return (0);
+
+ if (netidnum == NETID_TCP || netidnum == NETID_TCP6) {
+ socktype = SOCK_STREAM;
+ sockproto = IPPROTO_TCP;
+ } else {
+ socktype = SOCK_DGRAM;
+ sockproto = IPPROTO_UDP;
+ }
+
+ family = sa->sa_family;
+
+ fd = socket(family, socktype, sockproto);
+ if (fd < 0) {
+ val = errno;
+ syslog(LOG_WARNING, "mntsrv_reg: host %s, network %s: "
+ "socket: %m", host, netid);
+ return (val == EPROTONOSUPPORT ? 0 : -1);
+ }
+
+ if (family == AF_INET)
+ port = &((struct sockaddr_in *)sa)->sin_port;
+ else {
+ val = 1;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val,
+ sizeof(val)) < 0) {
+ syslog(LOG_ERR, "mntsrv_reg: cannot set IPv6-only "
+ "binding socket: %m");
+ goto failed;
+ }
+ port = &((struct sockaddr_in6 *)sa)->sin6_port;
+ }
+ *port = svcport;
+
+ if (svcport == 0) {
+ if (bindresvport_sa(fd, sa) < 0) {
+ syslog(LOG_ERR, "mntsrv_reg: host %s, network %s: "
+ "bindresvport_sa: %m", host, netid);
+ goto failed;
+ }
+ } else if (bind(fd, sa, sa->sa_len) < 0) {
+ syslog(LOG_ERR, "mntsrv_reg: host %s, port %"PRIu16", "
+ "network %s: bind: %m", host, ntohs(svcport), netid);
+ goto failed;
+ }
+
+ if (netidnum == NETID_TCP || netidnum == NETID_TCP6) {
+ if (listen(fd, SOMAXCONN) < 0) {
+ syslog(LOG_ERR, "mntsrv_reg: listen: %m");
+ goto failed;
+ }
+ bufsize = RPC_MAXDATASIZE;
+ } else
+ bufsize = 0;
+
+ /*
+ * Create service and check correspondence between settings of
+ * bound socket and used netconfig.
+ */
+ xprt = svc_tli_create(fd, nc, (struct t_bind *)NULL, bufsize, bufsize);
+ if (xprt == NULL) {
+ syslog(LOG_WARNING, "mntsrv_reg: host %s, network %s: "
+ "svc_tli_create failed", host, netid);
+ goto failed;
+ }
+
+ if (!svc_reg(xprt, RPCPROG_MNT, RPCMNT_VER1, mntsrv_dispatch,
+ (struct netconfig *)NULL)) {
+ syslog(LOG_WARNING, "mntsrv_reg: host %s, network %s: "
+ "cannot register RPCMNT_VER1 service", host, netid);
+ ver1_reg = 0;
+ } else {
+ ver1_reg = 1;
+ if (rpcmnt_reg[0].net[netidnum] == 0) {
+ if (mntsrv_set(RPCMNT_VER1, netconf, sa) < 0)
+ return (-1);
+ rpcmnt_reg[0].net[netidnum] = 1;
+ }
+ }
+
+ ver3_reg = 0;
+ if (!force_nfsv2) {
+ if (!svc_reg(xprt, RPCPROG_MNT, RPCMNT_VER3, mntsrv_dispatch,
+ (struct netconfig *)NULL)) {
+ syslog(LOG_WARNING, "mntsrv_reg: host %s, "
+ "network %s: cannot register RPCMNT_VER3 service",
+ host, netid);
+ } else {
+ ver3_reg = 1;
+ if (rpcmnt_reg[1].net[netidnum] == 0) {
+ if (mntsrv_set(RPCMNT_VER3, netconf, sa) < 0)
+ return (-1);
+ rpcmnt_reg[1].net[netidnum] = 1;
+ }
+ }
+ }
+
+ if (!ver1_reg && !ver3_reg) {
+ svc_destroy(xprt);
+ return (-1);
+ }
+
+ if (family == AF_INET)
+ have_ipv4 = 1;
+ else
+ have_ipv6 = 1;
+
+ return (0);
+
+failed:
+ if (close(fd) < 0)
+ syslog(LOG_ERR, "mntsrv_reg: close: %m");
+ return (-1);
+}
+
+/*
+ * Create and register all RPCPROG_MNT services.
+ */
+static int
+mntsrv_setup(char *const *hosts, const u_int nhosts)
+{
+ static struct addrinfo hints = {
+ .ai_flags = AI_NUMERICHOST,
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = IPPROTO_UDP,
+ .ai_addrlen = 0,
+ .ai_addr = NULL,
+ .ai_canonname = NULL,
+ .ai_next = NULL
+ };
+
+ struct sockaddr_in6 sa6;
+ struct sockaddr_in sa4;
+ const struct netconfig *nc;
+ const char *host;
+ struct netconf *netconf;
+ struct sockaddr *sa;
+ struct addrinfo *ai;
+ void *nc_handle;
+ u_int i, n;
+ int error;
+
+ /* Destroy the mapping. */
+ (void)rpcb_unset(RPCPROG_MNT, RPCMNT_VER1, (struct netconfig *)NULL);
+ (void)rpcb_unset(RPCPROG_MNT, RPCMNT_VER3, (struct netconfig *)NULL);
+
+ /* Get network configuration from netconfig. */
+ nc_handle = setnetconfig();
+ if (nc_handle == NULL) {
+ syslog(LOG_ERR, "mntsrv_setup: setnetconfig failed");
+ return (-1);
+ }
+ n = 0;
+ while ((nc = getnetconfig(nc_handle)) != NULL)
+ for (i = 0; i < NC_TBL_SIZE; ++i) {
+ netconf = &netconf_tbl[i];
+ if (strcmp(netconf->netid, nc->nc_netid) == 0) {
+ netconf->nc = nc;
+ netconf->visible =
+ (nc->nc_flag & NC_VISIBLE) ? 1 : 0;
+ break;
+ }
+ }
+ n = 0;
+ for (i = 0; i < NC_TBL_SIZE; ++i) {
+ netconf = &netconf_tbl[i];
+ if (netconf->nc != NULL && netconf->visible)
+ ++n;
+ }
+ if (n == 0) {
+ syslog(LOG_ERR, "mntsrv_setup: could not find any suitable "
+ "netconfig");
+ return (-1);
+ }
+
+ /* Create and register transports. */
+ memset(&sa4, 0, sizeof(sa4));
+ sa4.sin_len = sizeof(sa4);
+ sa4.sin_family = AF_INET;
+ memset(&sa6, 0, sizeof(sa6));
+ sa6.sin6_len = sizeof(sa6);
+ sa6.sin6_family = AF_INET6;
+ if (nhosts == 0) {
+ /* Wild card addresses */
+ sa4.sin_addr.s_addr = htonl(INADDR_ANY);
+ sa = (struct sockaddr *)&sa4;
+ if (mntsrv_reg(NETID_TCP, sa, "0.0.0.0") < 0 ||
+ mntsrv_reg(NETID_UDP, sa, "0.0.0.0") < 0)
+ return (-1);
+ sa6.sin6_addr = in6addr_any;
+ sa = (struct sockaddr *)&sa6;
+ if (mntsrv_reg(NETID_TCP6, sa, "::") < 0 ||
+ mntsrv_reg(NETID_UDP6, sa, "::") < 0)
+ return (-1);
+ } else {
+ /* Given address plus loopbacks. */
+ for (i = 0; i < nhosts; ++i) {
+ host = hosts[i];
+ error = getaddrinfo(host, (char *)NULL, &hints, &ai);
+ if (error != 0) {
+ if (error == EAI_NONAME)
+ syslog(LOG_WARNING, "mntsrv_setup: "
+ "cannot interpreter %s address",
+ host);
+ else
+ syslog(LOG_WARNING, "mntsrv_setup: "
+ "getaddrinfo(%s): %s", host,
+ gai_strerror(error));
+ continue;
+ }
+ switch (ai->ai_family) {
+ case AF_INET:
+ sa4.sin_addr = ((struct sockaddr_in *)
+ ai->ai_addr)->sin_addr;
+ if (sa4.sin_addr.s_addr ==
+ htonl(INADDR_LOOPBACK))
+ break;
+ sa = (struct sockaddr *)&sa4;
+ if (mntsrv_reg(NETID_TCP, sa, host) < 0 ||
+ mntsrv_reg(NETID_UDP, sa, host) < 0)
+ return (-1);
+ break;
+ case AF_INET6:
+ sa6.sin6_addr = ((struct sockaddr_in6 *)
+ ai->ai_addr)->sin6_addr;
+ if (addrcmp(sa6.sin6_addr.s6_addr,
+ in6addr_loopback.s6_addr,
+ IPV6_ADDR_LEN) == 0)
+ break;
+ sa = (struct sockaddr *)&sa6;
+ if (mntsrv_reg(NETID_TCP6, sa, host) < 0 ||
+ mntsrv_reg(NETID_UDP6, sa, host) < 0)
+ return (-1);
+ break;
+ default:
+ syslog(LOG_WARNING, "mntsrv_setup: cannot "
+ "interpreter \"%s\" address", host);
+ }
+ freeaddrinfo(ai);
+ }
+ sa4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ sa = (struct sockaddr *)&sa4;
+ if (mntsrv_reg(NETID_TCP, sa, "127.0.0.1") < 0 ||
+ mntsrv_reg(NETID_UDP, sa, "127.0.0.1") < 0)
+ return (-1);
+ sa6.sin6_addr = in6addr_loopback;
+ sa = (struct sockaddr *)&sa6;
+ if (mntsrv_reg(NETID_TCP6, sa, "::1") < 0 ||
+ mntsrv_reg(NETID_UDP6, sa, "::1") < 0)
+ return (-1);
+ }
+
+ if (endnetconfig(nc_handle) < 0) {
+ syslog(LOG_ERR, "mntsrv_setup: endnetconfig failed");
+ return (-1);
+ }
+
+ if (rpcmnt_reg[0].any == 0 && rpcmnt_reg[1].any == 0) {
+ syslog(LOG_ERR, "mntsrv_setup: could not create any service");
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Destroy created mappings.
+ */
+static int
+mntsrv_unsetup(void)
+{
+ int rv;
+
+ rv = 0;
+ if (rpcmnt_reg[0].any != 0 &&
+ !rpcb_unset(RPCPROG_MNT, RPCMNT_VER1, (struct netconfig *)NULL)) {
+ syslog(LOG_ERR, "mntsrv_unsetup: rpcb_unset failed for "
+ "RPCMNT_VER1");
+ rv = -1;
+ }
+ if (rpcmnt_reg[1].any != 0 &&
+ !rpcb_unset(RPCPROG_MNT, RPCMNT_VER3, (struct netconfig *)NULL)) {
+ syslog(LOG_ERR, "mntstv_unsetup: rpcb_unset failed for "
+ "RPCMNT_VER3");
+ rv = -1;
+ }
+ return (rv);
+}
+
+/*
+ * Receive, unpack a control command. Perform actions and send the answer.
+ */
+static int
+ctl_server(void)
+{
+ static struct ctl_ans_hdr ctl_ans_hdr = {
+ .version = CTL_API_VERSION,
+ .size = 0
+ };
+
+ struct xucred xucred;
+ socklen_t slen;
+ ssize_t ssize;
+ u_int command;
+ int fd, rv, error;
+
+ /* Do not need timeout, since listen socket is non-blockable. */
+ fd = accept(ctl_listen_fd, (struct sockaddr *)NULL, (socklen_t *)NULL);
+ if (fd < 0) {
+ switch (errno) {
+ case EWOULDBLOCK:
+ case ECONNABORTED:
+ /* Ignore interrupted connection. */
+ return (0);
+ }
+ syslog(LOG_ERR, "ctl_server: accept: %m");
+ return (-1);
+ }
+
+ ctl_cmd_buf = NULL;
+ error = -1;
+
+ if (set_block(fd) < 0) {
+ syslog(LOG_ERR, "ctl_server: cannot make accepted socket "
+ "blockable");
+ goto done;
+ }
+
+ slen = sizeof(xucred);
+ if (getsockopt(fd, 0, LOCAL_PEERCRED, &xucred, &slen) < 0) {
+ syslog(LOG_ERR, "ctl_server: getsockopt(LOCAL_PEERCRED): %m");
+ goto done;
+ }
+ if (slen != sizeof(xucred) || xucred.cr_version != XUCRED_VERSION) {
+ syslog(LOG_WARNING, "ctl_server: getsockopt(LOCAL_PEERCRED) "
+ "returned wrong option");
+ goto done;
+ }
+
+ error = 0;
+
+ if (xucred.cr_uid != 0) {
+ syslog(LOG_WARNING, "ctl_server: request from not super-user");
+ goto done;
+ }
+
+ /* Receive the header. */
+ ssize = read(fd, &ctl_cmd_hdr, sizeof(ctl_cmd_hdr));
+ if (ssize < 0) {
+ syslog(LOG_WARNING, "ctl_server: read (command header): %m");
+ goto done;
+ }
+ if (ssize != sizeof(ctl_cmd_hdr)) {
+ syslog(LOG_WARNING, "ctl_server: read (command header): read "
+ "%zd of %zu bytes", ssize, sizeof(ctl_cmd_hdr));
+ goto done;
+ }
+
+ if (ctl_cmd_hdr.version != CTL_API_VERSION) {
+ syslog(LOG_WARNING, "ctl_server: wrong control command "
+ "protocol version %u, my version is %u",
+ ctl_cmd_hdr.version, CTL_API_VERSION);
+ goto done;
+ }
+ if (ctl_cmd_hdr.size > CTL_MSG_MAX_SIZE) {
+ syslog(LOG_WARNING, "ctl_server: message size %zu bigger "
+ "than allowed size %u", ctl_cmd_hdr.size,
+ CTL_MSG_MAX_SIZE);
+ goto done;
+ }
+
+ if (ctl_cmd_hdr.size > 0) {
+ /* Receive data. */
+ ctl_cmd_buf = malloc(ctl_cmd_hdr.size);
+ if (ctl_cmd_buf == NULL) {
+ syslog(LOG_ERR, "ctl_server: malloc(%zu): %m",
+ ctl_cmd_hdr.size);
+ goto done;
+ }
+ ssize = read(fd, ctl_cmd_buf, ctl_cmd_hdr.size);
+ if (ssize < 0) {
+ syslog(LOG_WARNING, "ctl_server: read (command "
+ "data): %m");
+ goto done;
+ }
+ if ((size_t)ssize != ctl_cmd_hdr.size) {
+ syslog(LOG_WARNING, "ctl_server: read (command "
+ "data): read %zd of %zu bytes", ssize,
+ ctl_cmd_hdr.size);
+ goto done;
+ }
+ }
+
+ switch (command = ctl_cmd_hdr.command) {
+ case CTL_CMD_EXPORT:
+ rv = ctl_export_unpack();
+ break;
+ case CTL_CMD_RELOAD:
+ rv = ctl_reload_unpack();
+ break;
+ case CTL_CMD_FLUSH:
+ rv = ctl_flush_unpack();
+ break;
+ default:
+ syslog(LOG_WARNING, "ctl_server: unknown command code %u",
+ ctl_cmd_hdr.command);
+ rv = -2;
+ }
+
+ if (rv < 0) {
+ ctl_ans_hdr.answer = rv == -1 ?
+ CTL_ANS_ERROR : CTL_ANS_FORMAT;
+ syslog(LOG_WARNING, "ctl_server: %s control command",
+ rv == -1 ? "cannot unpack" : "wrong format of");
+ } else {
+ ctl_ans_hdr.answer = CTL_ANS_OK;
+ switch (command) {
+ case CTL_CMD_EXPORT:
+ if (update_conf(1) < 0) {
+ ctl_ans_hdr.answer = CTL_ANS_REJECTED;
+ syslog(LOG_WARNING, "ctl_server: control "
+ "command was rejected, since updates "
+ "cannot be applied to the current "
+ "configuration");
+ } else if (expspec_func(EXPSPEC_FN_UPDATE) < 0) {
+ ctl_ans_hdr.answer = CTL_ANS_ERROR;
+ error = -1;
+ } else if (update_conf(0) < 0) {
+ syslog(LOG_ERR, "ctl_server: update_conf "
+ "unexpectedly failed");
+ return (-1);
+ }
+ free_fs_exp_list(&fs_exp_list_update);
+ free_unref_conf();
+ break;
+ case CTL_CMD_RELOAD:
+ if (configure(1) < 0)
+ if (syserr_flag) {
+ ctl_ans_hdr.answer = CTL_ANS_ERROR;
+ error = -1;
+ }
+ if (error == 0) {
+ if (expspec_func(EXPSPEC_FN_RELOAD) < 0) {
+ ctl_ans_hdr.answer = CTL_ANS_ERROR;
+ error = -1;
+ } else {
+ mntlist_inherit();
+ free_fs_exp_list(fs_exp_list_prev);
+ free_unref_conf();
+ }
+ }
+ break;
+ case CTL_CMD_FLUSH:
+ free_fs_exp_list(fs_exp_list);
+ if (expspec_func(EXPSPEC_FN_CLEAR) < 0) {
+ ctl_ans_hdr.answer = CTL_ANS_ERROR;
+ error = -1;
+ }
+ break;
+ }
+ }
+
+ /* Send the answer. */
+ ssize = write(fd, &ctl_ans_hdr, sizeof(ctl_ans_hdr));
+ if (ssize < 0) {
+ syslog(LOG_WARNING, "ctl_server: write (answer header): %m");
+ goto done;
+ }
+ if (ssize != sizeof(ctl_ans_hdr)) {
+ syslog(LOG_WARNING, "ctl_server: write (answer header): "
+ "wrote %zd of %zu bytes", ssize, sizeof(ctl_ans_hdr));
+ goto done;
+ }
+done:
+ free(ctl_cmd_buf);
+ if (close(fd) < 0) {
+ syslog(LOG_ERR, "ctl_server: close: %m");
+ error = -1;
+ }
+ return (error);
+}
+
+/*
+ * Pack, send a control command. Receive the answer and check it.
+ */
+static int
+ctl_client(void)
+{
+ static struct iovec ctl_cmd_iov[2] = {
+ [0] = {
+ .iov_base = &ctl_cmd_hdr,
+ .iov_len = sizeof(ctl_cmd_hdr)
+ }
+ };
+ static struct msghdr ctl_cmd_msg = {
+ .msg_name = NULL,
+ .msg_namelen = 0,
+ .msg_iov = ctl_cmd_iov,
+ .msg_iovlen = 2,
+ .msg_control = NULL,
+ .msg_controllen = 0,
+ .msg_flags = 0
+ };
+
+ struct sockaddr_un un;
+ struct sigaction sigact;
+ struct xucred xucred;
+ struct ctl_ans_hdr ctl_ans_hdr;
+ struct timeval tv;
+ socklen_t slen;
+ ssize_t ssize;
+ size_t size;
+ int fd, error;
+
+ switch (ctl_cmd_hdr.command) {
+ case CTL_CMD_EXPORT:
+ error = ctl_export_pack();
+ break;
+ case CTL_CMD_RELOAD:
+ error = ctl_reload_pack();
+ break;
+ case CTL_CMD_FLUSH:
+ error = ctl_flush_pack();
+ break;
+ default:
+ warnx("unexpected command code %u", ctl_cmd_hdr.command);
+ return (-1);
+ }
+ if (error < 0) {
+ warnx("cannot pack control message");
+ return (-1);
+ }
+ if (ctl_cmd_hdr.size > CTL_MSG_MAX_SIZE) {
+ warnx("message size %zu bigger than allowed size %u",
+ ctl_cmd_hdr.size, CTL_MSG_MAX_SIZE);
+ return (-1);
+ }
+
+ fd = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0) {
+ warn("socket");
+ return (-1);
+ }
+
+ error = -1;
+
+ tv.tv_sec = CTL_TIMEOUT;
+ tv.tv_usec = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 ||
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
+ warn("setsockopt(SO_RCVTIMEO/SO_SNDTIMEO)");
+ goto done;
+ }
+
+ /* Ignore SIGPIPE. */
+ sigact.sa_handler = SIG_IGN;
+ sigact.sa_flags = 0;
+ if (sigaction(SIGPIPE, &sigact, (struct sigaction *)NULL) < 0) {
+ warn("sigaction(SIGPIPE)");
+ goto done;
+ }
+
+ ctl_cmd_iov[1].iov_base = ctl_cmd_buf;
+ ctl_cmd_iov[1].iov_len = ctl_cmd_hdr.size;
+
+ memset(&un, 0, sizeof(un));
+ un.sun_family = PF_LOCAL;
+ strncpy(un.sun_path, _PATH_MOUNTD_CTLSOCKET, sizeof(un.sun_path) - 1);
+ un.sun_len = SUN_LEN(&un);
+
+ /*
+ * If PF_LOCAL listening socket's queue is full, then connect()
+ * returns ECONNREFUSED immediately, do not need timeout.
+ */
+ if (connect(fd, (struct sockaddr *)&un, un.sun_len) < 0) {
+ warn("connect(%s)", _PATH_MOUNTD_CTLSOCKET);
+ goto done;
+ }
+
+ slen = sizeof(xucred);
+ if (getsockopt(fd, 0, LOCAL_PEERCRED, &xucred, &slen) < 0) {
+ warn("getsockopt(LOCAL_PEERCRED)");
+ goto done;
+ }
+ if (slen != sizeof(xucred) || xucred.cr_version != XUCRED_VERSION) {
+ warnx("getsockopt(LOCAL_PEERCRED) returned wrong option");
+ goto done;
+ }
+ if (xucred.cr_uid != 0) {
+ warnx("peer is not run by super-user");
+ goto done;
+ }
+
+ setbuf(stdout, (char *)NULL);
+ printf("Sending control command (timeout %u seconds)... ",
+ CTL_TIMEOUT);
+ ssize = sendmsg(fd, &ctl_cmd_msg, 0);
+ if (ssize < 0) {
+ printf("failed\n");
+ warn("sendmsg (control command header and data)");
+ goto done;
+ }
+ size = sizeof(ctl_cmd_hdr) + ctl_cmd_hdr.size;
+ if ((size_t)ssize != size) {
+ printf("incomplete\n");
+ warnx("sendmsg: sent %zd of %zu bytes", ssize, size);
+ goto done;
+ }
+
+ printf("done\nReceiving answer (timeout %u seconds)... ", CTL_TIMEOUT);
+ ssize = read(fd, &ctl_ans_hdr, sizeof(ctl_ans_hdr));
+ if (ssize < 0) {
+ printf("failed\n");
+ warn("read (answer header)");
+ goto done;
+ }
+ if ((size_t)ssize != sizeof(ctl_ans_hdr)) {
+ printf("incomplete\n");
+ warnx("read (answer header): read %zd of %zu bytes", ssize,
+ sizeof(ctl_ans_hdr));
+ goto done;
+ }
+ printf("done\n");
+
+ if (ctl_ans_hdr.version != CTL_API_VERSION) {
+ warnx("wrong answer message protocol version %u, my version "
+ "is %u", ctl_ans_hdr.version, CTL_API_VERSION);
+ goto done;
+ }
+ if (ctl_ans_hdr.size != 0) {
+ warnx("answer has unexpected extra data");
+ goto done;
+ }
+ switch (ctl_ans_hdr.answer) {
+ case CTL_ANS_OK:
+ printf("Control command succeeded\n");
+ error = 0;
+ break;
+ case CTL_ANS_FORMAT:
+ warnx("answer: wrong format of command");
+ break;
+ case CTL_ANS_ERROR:
+ warnx("answer: some system error occurred");
+ break;
+ case CTL_ANS_REJECTED:
+ warnx("answer: control command was rejected");
+ break;
+ }
+done:
+ if (close(fd) < 0)
+ warn("close");
+ return (error);
+}
+
+/*
+ * Create and bind control socket.
+ */
+static int
+ctl_socket_init(void)
+{
+ struct sockaddr_un un;
+ struct timeval tv;
+ int fd;
+
+ if (sizeof(_PATH_MOUNTD_CTLSOCKET) > sizeof(un.sun_path)) {
+ syslog(LOG_ERR, "ctl_socket_init: path %s it too long for "
+ "local domain socket file name", _PATH_MOUNTD_CTLSOCKET);
+ return (-1);
+ }
+
+ fd = socket(PF_LOCAL, SOCK_STREAM, 0);
+ if (fd < 0) {
+ syslog(LOG_ERR, "ctl_socket_init: socket: %m");
+ return (-1);
+ }
+ ctl_listen_fd = fd;
+
+ if (unlink(_PATH_MOUNTD_CTLSOCKET) < 0)
+ if (errno != ENOENT) {
+ syslog(LOG_ERR, "ctl_socket_init: unlink(%s): %m",
+ _PATH_MOUNTD_CTLSOCKET);
+ return (-1);
+ }
+
+ memset(&un, 0, sizeof(un));
+ un.sun_family = PF_LOCAL;
+ strncpy(un.sun_path, _PATH_MOUNTD_CTLSOCKET, sizeof(un.sun_path) - 1);
+ un.sun_len = SUN_LEN(&un);
+ if (bind(fd, (struct sockaddr *)&un, un.sun_len) < 0) {
+ syslog(LOG_ERR, "ctl_socket_init: bind(%s): %m", un.sun_path);
+ return (-1);
+ }
+ ctl_socket_bound = 1;
+
+ if (chmod(un.sun_path, S_IWUSR) < 0) {
+ syslog(LOG_ERR, "ctl_socket_init: chmod(%s, 0%03o): %m",
+ un.sun_path, S_IWUSR);
+ return (-1);
+ }
+
+ if (set_nonblock(fd) < 0) {
+ syslog(LOG_ERR, "ctl_socket_init: cannot make listen socket "
+ "non-blockable");
+ return (-1);
+ }
+
+ tv.tv_sec = CTL_TIMEOUT;
+ tv.tv_usec = 0;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 ||
+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
+ syslog(LOG_ERR, "ctl_socket_init: "
+ "setsockopt(SO_RCVTIMEO/SO_SNDTIMEO): %m");
+ return (-1);
+ }
+
+ if (listen(fd, 5) < 0) {
+ syslog(LOG_ERR, "ctl_socket_init: listen: %m");
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Close and unlink control socket.
+ */
+static int
+ctl_socket_deinit(void)
+{
+ int rv;
+
+ rv = 0;
+ if (ctl_listen_fd >= 0)
+ if (close(ctl_listen_fd) < 0) {
+ syslog(LOG_ERR, "ctl_socket_deinit: close: %m");
+ rv = -1;
+ }
+ if (ctl_socket_bound)
+ if (unlink(_PATH_MOUNTD_CTLSOCKET) < 0) {
+ syslog(LOG_ERR, "ctl_socket_deinit: unlink(%s): %m",
+ _PATH_MOUNTD_CTLSOCKET);
+ rv = -1;
+ }
+ return (rv);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: %s [-2dlnrt] [-c command] [-h bindip] [-p port] "
+ "[exports_file ...]\n", getprogname());
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sigaction sigact;
+ struct kevent kev;
+ struct timespec ts;
+ fd_set rset;
+ sigset_t sigmask, zeromask;
+ const char *errstr;
+ struct pidfh *pfh;
+ char **hosts;
+ pid_t otherpid;
+ u_int nhosts;
+ int error, errno_save, kq, opt, rv, maxfd, maxfd0, nready;
+ char opt_c, opt_d;
+
+ hosts = NULL;
+ nhosts = 0;
+ opt_c = opt_d = 0;
+ while ((opt = getopt(argc, argv, "2c:dh:lnp:t")) != -1)
+ switch (opt) {
+ case '2':
+ force_nfsv2 = 1;
+ break;
+ case 'c':
+ if (add_exp_cmd(optarg) < 0)
+ return (EXIT_FAILURE);
+ opt_c = 1;
+ break;
+ case 'h':
+ ++nhosts;
+ hosts = realloc(hosts, nhosts * sizeof(*hosts));
+ if (hosts == NULL)
+ errx(EXIT_FAILURE, "main: realloc");
+ hosts[nhosts - 1] = optarg;
+ break;
+ case 'd':
+ opt_d = 1;
+ break;
+ case 'l':
+ log_cli_reqs = 1;
+ break;
+ case 'n':
+ resvport_only = 0;
+ break;
+ case 'p':
+ svcport = htons(strtonum(optarg, 0, UINT16_MAX,
+ &errstr));
+ if (errstr != NULL)
+ errx(EXIT_FAILURE, "option -p: wrong port "
+ "number: %s", errstr);
+ break;
+ case 't':
+ test_conf = 1;
+ break;
+ default:
+ usage();
+ return (EXIT_FAILURE);
+ }
+
+ if (set_exp_file(argc - optind, argv + optind) < 0)
+ return (EXIT_FAILURE);
+
+ TAILQ_INIT(&fs_exp_list1);
+ fs_exp_list = &fs_exp_list1;
+
+ if (test_conf) {
+ /* Just test configuration. */
+ if (configure(0) < 0)
+ return (EXIT_FAILURE);
+ show_conf();
+ return (EXIT_SUCCESS);
+ }
+
+ if (opt_c) {
+ /* Send control command. */
+ if (configure(0) < 0)
+ errx(EXIT_FAILURE, "wrong configuration: "
+ "no command was sent");
+ return (ctl_client() == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+ }
+
+ /* Verify that there is nfsserver KLD. */
+ if (modfind(KLD_NFSSERVER) < 0) {
+ if (errno != ENOENT)
+ err(EXIT_FAILURE, "modfind(%s)", KLD_NFSSERVER);
+ if (kldload(KLD_NFSSERVER) < 0 && errno != EEXIST)
+ err(EXIT_FAILURE, "kldload(%s)", KLD_NFSSERVER);
+ }
+
+ /* Check that another mountd is not already running. */
+ pfh = pidfile_open(_PATH_MOUNTDPID, S_IRUSR|S_IWUSR, &otherpid);
+ if (pfh == NULL) {
+ if (errno == EEXIST)
+ errx(EXIT_FAILURE, "mountd already running, PID %lu",
+ (u_long)otherpid);
+ err(EXIT_FAILURE, "cannot open or create PID-file %s",
+ _PATH_MOUNTDPID);
+ }
+
+ openlog("mountd", LOG_PID | opt_d ? LOG_PERROR : LOG_CONS, LOG_DAEMON);
+
+ if (!opt_d)
+ if (daemon(0, 0) < 0) {
+ syslog(LOG_ERR, "cannot run in the background: %m");
+ return (EXIT_FAILURE);
+ }
+
+ if (pidfile_write(pfh) < 0) {
+ syslog(LOG_ERR, "pidfile_write(%s): %m", _PATH_MOUNTDPID);
+ return (EXIT_FAILURE);
+ }
+
+ error = 1;
+ kq = -1;
+
+ if (expspec_func(EXPSPEC_FN_CLEAR) < 0)
+ goto failed;
+
+ /* Setup signals masks. */
+ sigemptyset(&sigmask);
+ sigemptyset(&zeromask);
+ if (opt_d)
+ sigaddset(&sigmask, SIGINT);
+ sigaddset(&sigmask, SIGHUP);
+ sigaddset(&sigmask, SIGTERM);
+
+ /* Mask expected signals. */
+ if (sigprocmask(SIG_SETMASK, &sigmask, (sigset_t *)NULL) < 0) {
+ syslog(LOG_ERR, "main: sigprocmask(SIG_SETMASK): %m");
+ goto failed;
+ }
+
+ /* Ignore SIGPIPE. */
+ sigact.sa_handler = SIG_IGN;
+ sigact.sa_flags = 0;
+ if (sigaction(SIGPIPE, &sigact, (struct sigaction *)NULL) < 0) {
+ syslog(LOG_ERR, "main: sigaction(SIGPIPE): %m");
+ goto failed;
+ }
+
+ /* Install SIGTERM (and SIGINT) handler. */
+ sigact.sa_handler = sig_term;
+ sigact.sa_mask = sigmask;
+ if (opt_d)
+ if (sigaction(SIGINT, &sigact, (struct sigaction *)NULL) < 0) {
+ syslog(LOG_ERR, "main: sigaction(SIGINT): %m");
+ goto failed;
+ }
+ if (sigaction(SIGTERM, &sigact, (struct sigaction *)NULL) < 0) {
+ syslog(LOG_ERR, "main: sigaction(SIGTERM): %m");
+ goto failed;
+ }
+
+ /* Install SIGHUP handler. */
+ sigact.sa_handler = sig_hup;
+ if (sigaction(SIGHUP, &sigact, (struct sigaction *)NULL) < 0) {
+ syslog(LOG_ERR, "main: sigaction(SIGHUP): %m");
+ goto failed;
+ }
+
+ /* Create pipe for IPC from signals handlers. */
+ if (pipe(sig_pipe) < 0) {
+ syslog(LOG_ERR, "main: pipe: %m");
+ goto failed;
+ }
+ /* Set write- and read-end of sig_pipe as non-blockable. */
+ if (set_nonblock(sig_pipe[0]) < 0 || set_nonblock(sig_pipe[1]) < 0)
+ goto failed;
+
+ opt = RPC_MAXDATASIZE;
+ if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &opt)) {
+ syslog(LOG_ERR, "rpc_control(RPC_SVC_CONNMAXREC_SET) failed");
+ goto failed;
+ }
+
+ if (!resvport_only && sysctlbyname("vfs.nfsrv.nfs_privport", NULL,
+ (size_t *)NULL, &resvport_only, sizeof(resvport_only)) < 0) {
+ syslog(LOG_ERR, "sysctl(vfs.nfsrv.nfs_privport): %m");
+ goto failed;
+ }
+
+ if (mntsrv_setup(hosts, nhosts) < 0)
+ goto failed;
+ free(hosts);
+
+ if (configure(0) < 0 && syserr_flag)
+ goto failed;
+
+ kq = kqueue();
+ if (kq < 0) {
+ syslog(LOG_ERR, "main: kqueue: %m");
+ goto failed;
+ }
+ EV_SET(&kev, 0, EVFILT_FS, EV_ADD, 0, 0, NULL);
+ if (kevent(kq, &kev, 1, (struct kevent *)NULL, 0,
+ (struct timespec *)NULL) < 0) {
+ syslog(LOG_ERR, "main: kevent: %m");
+ goto failed;
+ }
+ ts.tv_sec = (time_t)0;
+ ts.tv_nsec = 0;
+
+ if (expspec_func(EXPSPEC_FN_RELOAD) < 0)
+ goto done;
+
+ if (mntlist_read() < 0)
+ goto done;
+
+ if (ctl_socket_init() < 0)
+ goto done;
+
+ maxfd0 = sig_pipe[0];
+ if (maxfd0 < ctl_listen_fd)
+ maxfd0 = ctl_listen_fd;
+ if (maxfd0 < kq)
+ maxfd0 = kq;
+
+ for (;;) {
+ /* Unmask all signals. */
+ if (sigprocmask(SIG_SETMASK, &zeromask, (sigset_t *)NULL) < 0) {
+ syslog(LOG_ERR, "main: sigprocmask(SIG_SETMASK) "
+ "for zeromask: %m");
+ break;
+ }
+ /* svc_fdset could be changed, have to do this every time. */
+ rset = svc_fdset;
+ FD_SET(sig_pipe[0], &rset);
+ FD_SET(ctl_listen_fd, &rset);
+ FD_SET(kq, &rset);
+ maxfd = svc_maxfd;
+ if (maxfd < maxfd0)
+ maxfd = maxfd0;
+ nready = select(maxfd + 1, &rset, (fd_set *)NULL,
+ (fd_set *)NULL, (struct timeval *)NULL);
+ errno_save = errno;
+ /* Mask expected signals. */
+ if (sigprocmask(SIG_SETMASK, &sigmask, (sigset_t *)NULL) < 0) {
+ syslog(LOG_ERR, "main: sigprocmask(SIG_SETMASK) "
+ "for sigmask: %m");
+ break;
+ }
+ if (nready < 0) {
+ if (errno_save == EINTR)
+ continue;
+ errno = errno_save;
+ syslog(LOG_ERR, "main: select: %m");
+ break;
+ }
+ if (FD_ISSET(kq, &rset)) {
+ /* Handle VFS events. */
+ rv = kevent(kq, (struct kevent *)NULL, 0, &kev, 1,
+ &ts);
+ if (rv == 1) {
+ if (kev.filter == EVFILT_FS &&
+ (kev.fflags & (VQ_MOUNT|VQ_UNMOUNT)))
+ if (expspec_func(EXPSPEC_FN_EVENT) < 0)
+ break;
+ } else if (rv < 0) {
+ syslog(LOG_ERR, "main: kevent: %m");
+ break;
+ } else
+ syslog(LOG_WARNING, "main: kevent returned %d",
+ rv);
+ --nready;
+ }
+ if (!signal_flag) {
+ if (FD_ISSET(ctl_listen_fd, &rset)) {
+ /* Serve command. */
+ if (ctl_server() < 0)
+ break;
+ --nready;
+ }
+ if (nready > 0) {
+ /* Serve request. */
+ svc_getreqset(&rset);
+ if (syserr_flag)
+ break;
+ }
+ } else {
+ /* Check signals. */
+ if (shutdown_flag)
+ break;
+ signal_flag = 0;
+ while (read(sig_pipe[0], &opt, 1) > 0)
+ ;
+ if (reconf_flag) {
+ reconf_flag = 0;
+ if (configure(1) < 0 && syserr_flag)
+ break;
+ if (expspec_func(EXPSPEC_FN_RELOAD) < 0)
+ break;
+ mntlist_inherit();
+ free_fs_exp_list(fs_exp_list_prev);
+ free_unref_conf();
+ }
+ }
+ }
+done:
+ if (shutdown_flag) {
+ if (mntlist_write() < 0)
+ error = 1;
+ else
+ error = 0;
+ unconfigure();
+ }
+
+ if (expspec_func(EXPSPEC_FN_CLEAR) < 0)
+ error = 1;
+failed:
+ if (kq >= 0)
+ if (close(kq) < 0) {
+ syslog(LOG_ERR, "main: close: %m");
+ error = 1;
+ }
+
+ if (mntsrv_unsetup() < 0)
+ error = 1;
+
+ if (ctl_socket_deinit() < 0)
+ error = 1;
+
+ if (pidfile_remove(pfh) < 0) {
+ syslog(LOG_ERR, "pidfile_remove(%s): %m", _PATH_MOUNTDPID);
+ error = 1;
+ }
+
+ closelog();
+ return (error ? EXIT_FAILURE : EXIT_SUCCESS);
+}
diff -ruN empty/mountd.h mountd/mountd.h
--- empty/mountd.h 1970-01-01 03:00:00.000000000 +0300
+++ mountd/mountd.h 2009-06-26 14:12:39.000000000 +0300
@@ -0,0 +1,320 @@
+/*-
+ * Copyright (c) 2009 Andrey Simonenko
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD:$
+ */
+
+#ifndef MOUNTD_H
+#define MOUNTD_H
+
+#include <osreldate.h>
+
+#if __FreeBSD_version >= 800062
+# define WITH_SEC 1
+#else
+# define RPCSEC_GSS_KRB5 100
+# define RPCSEC_GSS_KRB5I 200
+# define RPCSEC_GSS_KRB5P 300
+#endif
+
+#define IPV4_ADDR_LEN 4 /* IPv4 address length in uint8_t. */
+#define IPV6_ADDR_LEN 16 /* IPv6 address length in uint8_t. */
+
+/*
+ * Information about mounted path.
+ */
+struct mnt_path {
+ LIST_ENTRY(mnt_path) link; /* For list building. */
+ char *subpath; /* Mounted subpath. */
+};
+
+/*
+ * List of all mounted paths by one host.
+ */
+LIST_HEAD(mnt_path_list, mnt_path);
+
+/*
+ * Information about mounted paths by one host.
+ */
+struct mnt_host {
+ LIST_ENTRY(mnt_host) link; /* For list building. */
+ struct mnt_path_list mp_list; /* Mounted paths list. */
+ uint32_t buf[1]; /* IPv4 or IPv6 address. */
+};
+
+#define MNT_HOST_ADDR(ptr) \
+ ((ptr)->buf)
+
+#define MNT_HOST_IPV4_SIZE \
+ (sizeof(struct in_addr) + offsetof(struct mnt_host, buf[0]))
+
+#define MNT_HOST_ADDR4(ptr) \
+ ((struct in_addr *)MNT_HOST_ADDR(ptr))
+
+#define MNT_HOST_IPV6_SIZE \
+ (sizeof(struct in6_addr) + offsetof(struct mnt_host, buf[0]))
+
+#define MNT_HOST_ADDR6(ptr) \
+ ((struct in6_addr *)MNT_HOST_ADDR(ptr))
+
+/*
+ * List of all hosts that mounted at least one path.
+ */
+LIST_HEAD(mnt_host_list, mnt_host);
+
+/*
+ * Credentials specification.
+ */
+struct cred_exp {
+ LIST_ENTRY(cred_exp) link; /* For list building. */
+ uid_t uid; /* Credentials UID. */
+ u_int ngids; /* Number of credentials GIDs. */
+ gid_t gids[NGROUPS]; /* Credentials GIDs. */
+ u_int ref_count; /* Reference counter. */
+};
+
+/*
+ * Security flavors specification.
+ */
+struct secflavors {
+ LIST_ENTRY(secflavors) link; /* For list building. */
+ u_int nsec; /* Number of security flavors. */
+ int sec[NFSE_NSECFLAV]; /* Security flavors. */
+ u_int ref_count; /* Reference counter. */
+};
+
+/*
+ * IPv4 address specification.
+ */
+struct addr4_spec {
+ struct in_addr addr; /* Network or host address. */
+ struct in_addr mask; /* Address mask. */
+};
+
+/*
+ * IPv6 address specification.
+ */
+struct addr6_spec {
+ struct in6_addr addr; /* Network or host address. */
+ struct in6_addr mask; /* Address mask. */
+};
+
+/*
+ * Address specification (variable-sized structure).
+ */
+struct addr_spec {
+ LIST_ENTRY(addr_spec) link; /* For list building. */
+ u_int maskbits; /* Number of bits in mask. */
+ u_int ref_count; /* Reference counter. */
+ uint32_t buf[1]; /* IPv4 or IPv6 address spec. */
+};
+
+LIST_HEAD(addr_spec_list, addr_spec);
+
+#define ADDR_SPEC_IPV4_SIZE \
+ (sizeof(struct addr4_spec) + offsetof(struct addr_spec, buf[0]))
+
+#define ADDR4_SPEC_PTR(ptr) \
+ ((struct addr4_spec *)(ptr)->buf)
+
+#define ADDR4_SPEC_CPTR(ptr) \
+ ((const struct addr4_spec *)(ptr)->buf)
+
+#define ADDR_SPEC_IPV6_SIZE \
+ (sizeof(struct addr6_spec) + offsetof(struct addr_spec, buf[0]))
+
+#define ADDR6_SPEC_PTR(ptr) \
+ ((struct addr6_spec *)(ptr)->buf)
+
+#define ADDR6_SPEC_CPTR(ptr) \
+ ((const struct addr6_spec *)(ptr)->buf)
+
+/*
+ * Export specification for one host or network.
+ */
+struct addr_exp {
+ TAILQ_ENTRY(addr_exp) link; /* For list building. */
+ struct addr_spec *addr_spec; /* Address specification. */
+ struct cred_exp *cred_exp; /* Export credentials. */
+ struct secflavors *secflavors; /* Security flavors. */
+ uint32_t oflags; /* Option flags. */
+};
+
+/*
+ * List of all host and network specifications for one file system.
+ */
+TAILQ_HEAD(addr_exp_list, addr_exp);
+
+/*
+ * All information about one exported file system.
+ */
+struct fs_exp {
+ TAILQ_ENTRY(fs_exp) link; /* For list building. */
+ fsid_t fsid; /* File system ID. */
+ char *path; /* Mount point. */
+ size_t path_len; /* Length of path_name. */
+ uint32_t oflags; /* File system option flags. */
+ struct addr_exp *ae_def; /* Default export spec. */
+ struct addr_exp_list ae4_list; /* Export spec. list for IPv4. */
+ struct addr_exp_list ae6_list; /* Export spec. list for IPv6. */
+ struct mnt_host_list mh4_list; /* Mounted paths by IPv4 hosts. */
+ struct mnt_host_list mh6_list; /* Mounted paths by IPv6 hosts. */
+ u_int nspec; /* Number of address spec. */
+ u_int no_mnt_exp; /* Number of OPT_NO_MNT_EXP. */
+};
+
+/*
+ * List of all exported file systems.
+ */
+TAILQ_HEAD(fs_exp_list, fs_exp);
+
+extern char test_conf;
+extern char syserr_flag;
+extern char have_ipv4;
+extern char have_ipv6;
+
+extern char no_mntproc_dump;
+extern char no_mntproc_export;
+
+extern char *index_webnfs;
+
+extern char subpath_empty[];
+
+extern struct fs_exp_list fs_exp_list1;
+extern struct fs_exp_list fs_exp_list2;
+extern struct fs_exp_list fs_exp_list_update;
+extern struct fs_exp_list *fs_exp_list;
+extern struct fs_exp_list *fs_exp_list_prev;
+
+extern struct fs_exp *fs_exp_by_path(const struct fs_exp_list *, const char *);
+
+extern void mntlist_recheck(const u_int, struct fs_exp *);
+extern void mntlist_free_mh_list(struct mnt_host_list *);
+
+#define CTL_API_VERSION 1 /* Control protocol version. */
+#define CTL_MSG_MAX_SIZE (1024 * 1024) /* Maximum allowed message size. */
+
+#define CTL_CMD_EXPORT 1 /* Change export specifications. */
+#define CTL_CMD_RELOAD 2 /* Reload configuration. */
+#define CTL_CMD_FLUSH 3 /* Flush all export specifications. */
+
+/*
+ * Control command header (do not change it).
+ */
+struct ctl_cmd_hdr {
+ u_int version; /* CTL_API_VERSION */
+ u_int command; /* Command code. */
+ size_t size; /* Size of data. */
+};
+
+/*
+ * IPv4 address specification control data.
+ */
+struct ctl_addr4_spec {
+ struct in_addr addr; /* Network or host address. */
+ u_int maskbits; /* Number of bits in mask. */
+};
+
+/*
+ * IPv6 address specification control data.
+ */
+struct ctl_addr6_spec {
+ struct in6_addr addr; /* Network or host address. */
+ u_int maskbits; /* Number of bits in mask. */
+};
+
+/*
+ * Union that represents IPv4 or IPv6 address specification.
+ */
+union ctl_addr_spec {
+ struct ctl_addr4_spec as4; /* IPv4 address spec. */
+ struct ctl_addr6_spec as6; /* IPv6 address spec. */
+};
+
+/*
+ * Export specification control data.
+ */
+struct ctl_addr_exp {
+ sa_family_t family; /* AF_INET or AF_INET6. */
+ union ctl_addr_spec addr_spec; /* Address specification. */
+ uid_t uid; /* Credentials UID. */
+ u_int ngids; /* Number of credentials GIDs. */
+ gid_t gids[NGROUPS]; /* Credentials GIDs. */
+ u_int nsec; /* Number of security flavors. */
+ int sec[NFSE_NSECFLAV]; /* Security flavors. */
+ uint32_t oflags; /* Option flags. */
+};
+
+/*
+ * File system control data.
+ */
+struct ctl_fs_exp {
+ size_t path_len; /* Length of path name. */
+ uint32_t oflags; /* Option flags. */
+ u_int nspec; /* Number of address spec. */
+};
+
+/*
+ * The format of a control command message:
+ * struct ctl_cmd_hdr -- header;
+ * data -- optional data.
+ *
+ * The format of CTL_CMD_EXPORT command:
+ * struct ctl_fs_exp -- file system control data;
+ * char [] -- file system path, without NUL character;
+ * struct ctl_addr_exp [] -- array of address specifications.
+ *
+ * The CTL_CMD_EXPORT command can have settings for several file systems.
+ *
+ * The format of CTL_CMD_RELOAD command:
+ * nothing -- nothing in data.
+ */
+
+#define CTL_ANS_OK 0 /* Control command succeeded. */
+#define CTL_ANS_FORMAT 1 /* Wrong format of a command. */
+#define CTL_ANS_ERROR 2 /* Some system error occurred. */
+#define CTL_ANS_REJECTED 3 /* Control command was rejected. */
+
+/*
+ * Answer on control command header (do not change it).
+ */
+struct ctl_ans_hdr {
+ u_int version; /* CTL_API_VERSION */
+ u_int answer; /* Answer code. */
+ size_t size; /* Size of data. */
+};
+
+/*
+ * The format of an answer message on control command:
+ * struct ctl_ans_hdr -- header;
+ * data -- optional data.
+ *
+ * All answers do not have any data.
+ */
+
+extern struct ctl_cmd_hdr ctl_cmd_hdr;
+extern void *ctl_cmd_buf;
+
+#endif /* !MOUNTD_H */
diff -ruN empty/mountd_conf.c mountd/mountd_conf.c
--- empty/mountd_conf.c 1970-01-01 03:00:00.000000000 +0300
+++ mountd/mountd_conf.c 2009-07-02 10:37:06.000000000 +0300
@@ -0,0 +1,3725 @@
+/*-
+ * Copyright (c) 2009 Andrey Simonenko
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD:$");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/limits.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <rpc/types.h>
+#include <rpc/auth.h>
+
+#include <nfs/rpcv2.h>
+#include <nfsserver/nfs_export.h>
+
+#include <err.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <grp.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "mountd.h"
+#include "mountd_conf.h"
+#include "pathnames.h"
+
+/*
+ * Option description.
+ */
+struct opt_descr {
+ const char *name; /* Option name. */
+ uint32_t oflags; /* Option flags. */
+ int (*parse)(char *); /* Parser or NULL. */
+};
+
+static int parse_opt_cred(char *);
+static int parse_opt_host(char *);
+static int parse_opt_index(char *);
+static int parse_opt_mask(char *);
+static int parse_opt_network(char *);
+static int parse_opt_nospec(char *);
+static int parse_opt_public(char *);
+static int parse_opt_ro_rw(char *);
+static int parse_opt_sec(char *);
+
+#define OPTS_ALLDIRS_C (OPT_ALLDIRS|OPT_COMPAT)
+#define OPTS_HOST (OPT_HOST|OPT_ARG)
+#define OPTS_INDEX (OPT_INDEX|OPT_ARG)
+#define OPTS_MAPALL (OPT_MAPALL|OPT_ARG)
+#define OPTS_MAPROOT (OPT_MAPROOT|OPT_ARG)
+#define OPTS_MAPROOT_C (OPTS_MAPROOT|OPT_COMPAT)
+#define OPTS_MASK (OPT_MASK|OPT_ARG)
+#define OPTS_MASK_C (OPTS_MASK|OPT_COMPAT)
+#define OPTS_NETWORK (OPT_NETWORK|OPT_ARG)
+#define OPTS_NETWORK_C (OPTS_NETWORK|OPT_COMPAT)
+#define OPTS_RO_C (OPT_RO|OPT_COMPAT)
+#define OPTS_SEC (OPT_SEC|OPT_ARG)
+#define OPTS_WEBNFS (OPT_WEBNFS|OPT_PUBLIC|OPT_RO|OPT_MAPALL)
+
+/* File system related options. */
+#define FE_OPTS_ALLFLAGS \
+ (OPT_PUBLIC|OPT_WEBNFS|OPT_INDEX|OPT_QUIET|OPT_NO_MNT_DMP| \
+ OPT_NO_MNT_EXP)
+
+/* File system related options that can be used with command. */
+#define FE_CMD_OPTS_ALLFLAGS \
+ (OPT_QUIET|OPT_NOSPEC|OPT_NO_MNT_DMP|OPT_NO_MNT_EXP)
+
+/* Address specification related options. */
+#define AE_OPTS_ALLFLAGS \
+ (OPT_RO|OPT_RW|OPT_MAPALL|OPT_MAPROOT|OPT_SEC|OPT_DENY)
+
+/* All global options. */
+#define GL_OPTS_ALLFLAGS \
+ (OPT_SEC|OPT_NO_MNT_DMP|OPT_NO_MNT_EXP)
+
+/* All export options. */
+#define EX_OPTS_ALLFLAGS \
+ (OPT_QUIET|OPT_RO|OPT_RW|OPT_ALLDIRS|OPT_PUBLIC|OPT_INDEX| \
+ OPT_MAPALL|OPT_MAPROOT|OPT_MASK|OPT_NETWORK|OPT_HOST|OPT_WEBNFS| \
+ OPT_NOSPEC|OPT_SEC|OPT_NO_MNT_DMP|OPT_NO_MNT_EXP)
+
+/*
+ * Options description table sorted by option name.
+ */
+static struct opt_descr opt_descr_tbl[] = {
+ { "alldirs", OPTS_ALLDIRS_C, NULL },
+ { "host", OPTS_HOST, parse_opt_host },
+ { "index", OPTS_INDEX, parse_opt_index },
+ { "m", OPTS_MASK_C, parse_opt_mask },
+ { "mapall", OPTS_MAPALL, parse_opt_cred },
+ { "maproot", OPTS_MAPROOT, parse_opt_cred },
+ { "mask", OPTS_MASK, parse_opt_mask },
+ { "n", OPTS_NETWORK_C, parse_opt_network },
+ { "network", OPTS_NETWORK, parse_opt_network },
+ { "no_mntproc_dump", OPT_NO_MNT_DMP, NULL },
+ { "no_mntproc_export", OPT_NO_MNT_EXP, NULL },
+ { "nospec", OPT_NOSPEC, parse_opt_nospec },
+ { "o", OPTS_RO_C, parse_opt_ro_rw },
+ { "public", OPT_PUBLIC, parse_opt_public },
+ { "quiet", OPT_QUIET, NULL },
+ { "r", OPTS_MAPROOT_C, parse_opt_cred },
+ { "ro", OPT_RO, parse_opt_ro_rw },
+ { "root", OPTS_MAPROOT_C, parse_opt_cred },
+ { "rw", OPT_RW, parse_opt_ro_rw },
+ { "sec", OPTS_SEC, parse_opt_sec },
+ { "webnfs", OPTS_WEBNFS, parse_opt_public }
+};
+
+#define OPT_DESCR_TBL_SIZE (sizeof(opt_descr_tbl) / sizeof(opt_descr_tbl[0]))
+
+/*
+ * One exports file or directory with exports files.
+ */
+struct exp_file {
+ const char *name; /* File name. */
+ int isfile; /* Non-zero if this is a file. */
+};
+
+static int exp_file_num; /* Number of exports files. */
+static struct exp_file *exp_file_arr; /* Array of exports files. */
+
+static u_int exp_cmd_num = 0; /* Number of -c options. */
+static char **exp_cmd_arr = NULL; /* -c options arguments. */
+
+/* Default exports file. */
+static struct exp_file exp_file_arr_def = {
+ .name = _PATH_EXPORTS,
+ .isfile = 1
+};
+
+static const char *exp_file_name; /* Current exports file name. */
+
+#define LINEBUF_SIZE 128 /* Initial size for linebuf (> 2). */
+#define LINEBUF_MAX 4096 /* Maximum size for linebuf. */
+
+static char *linebuf; /* Current line buffer. */
+static size_t linesize; /* Size of linebuf. */
+static u_int lineno; /* Current line number. */
+
+static char file_is_parsed; /* Non-zero if file is parsed. */
+static char log_to_console = 0; /* Write log messages to console. */
+
+/*
+ * If current option is -network, then this flag is set.
+ * It is cleared by any other option. It is used in parse_opts() for
+ * checking whether -mask is specified exactly after -network.
+ */
+static char opt_network;
+
+/*
+ * This flag is set if parse_opts() saw -network or -host option or
+ * if parse_hosts() saw at least one host. If there were these options,
+ * then address specification related flags are allowed to be changed.
+ * This flag is cleared in parse_opts() by any other option specified
+ * right after address specification.
+ */
+static char saw_addr_spec;
+
+/*
+ * This flag is set if there is -public or -webnfs option.
+ * Only one file system can be public.
+ */
+static char opt_public;
+
+/* This flag is set if there is '!' in address specification. */
+static char opt_deny;
+
+/*
+ * Current option flags are saved in oflags. If there is a new address
+ * specification in a line, then previous address specification related
+ * option flags are cleared in oflags. All current option flags are
+ * saved in oflagsall.
+ */
+static uint32_t oflags;
+static uint32_t oflagsall;
+
+/*
+ * Command option for all export specifications in the current file and
+ * flag that says that ocmd has a value. These variables are used for
+ * implementation of "-c add|delete -f file" options.
+ */
+static uint32_t ocmd;
+static char ocmd_set;
+
+/*
+ * Global option flags, local options in oflags and oflagsall overwrite
+ * global options.
+ */
+static uint32_t goflags;
+
+static u_int addr_spec_family; /* AF_INET or AF_INET6. */
+static struct addr_spec *addr_spec; /* Current address spec. */
+static struct fs_exp *fs_exp; /* Current fs_exp. */
+static struct cred_exp *cred_exp; /* Current credentials. */
+static struct secflavors *secflavors; /* Current -sec option. */
+static struct secflavors *gsecflavors; /* Global -sec option. */
+
+/* Global list of all credential specifications. */
+static LIST_HEAD(, cred_exp) cred_exp_list =
+ LIST_HEAD_INITIALIZER(&cred_exp_list);
+
+/* Global list of all addr_spec structure for IPv4. */
+static struct addr_spec_list addr4_spec_list =
+ LIST_HEAD_INITIALIZER(&addr4_spec_list);
+
+/* Global list of all addr_spec structures for IPv6. */
+static struct addr_spec_list addr6_spec_list =
+ LIST_HEAD_INITIALIZER(&addr6_spec_list);
+
+/* Global list of all security flavors. */
+static LIST_HEAD(, secflavors) secflavors_list =
+ LIST_HEAD_INITIALIZER(&secflavors_list);
+
+/* Default anonymous export credentials. */
+static struct cred_exp cred_exp_anon_def = {
+ .uid = (uid_t)-2,
+ .ngids = 1,
+ .gids = { (gid_t)-2 },
+ .ref_count = 1
+};
+
+/* Default security flavors. */
+static struct secflavors secflavors_def = {
+ .nsec = 1,
+ .sec = { AUTH_SYS },
+ .ref_count = 1
+};
+
+#define LOG_BUF_SIZE 256 /* Size of buffer in vlog*(). */
+#define LOGMSG_WARN (-EPERM) /* Anything non-zero and non-Error. */
+
+static void (*log_err)(const char *, ...) __printflike(1, 2);
+static void (*log_warn)(const char *, ...) __printflike(1, 2);
+
+static void logmsg(const char *, ...) __printflike(1, 2);
+static void logmsgx(const char *, ...) __printflike(1, 2);
+static void logmsgw(const char *, ...) __printflike(1, 2);
+static void logconfe(const char *, ...) __printflike(1, 2);
+static void logconfw(const char *, ...) __printflike(1, 2);
+static void logconfx(const char *, ...) __printflike(1, 2);
+
+static int Asprintf(char **, const char *, ...) __printflike(2, 3);
+
+/*
+ * These functions are wrappers for the corresponding real functions.
+ * If real functions fail, then syserr_flag is set.
+ */
+static void *
+Malloc(size_t size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (ptr == NULL)
+ syserr_flag = 1;
+ return (ptr);
+}
+
+static void *
+Realloc(void *ptr, size_t size)
+{
+ ptr = realloc(ptr, size);
+ if (ptr == NULL)
+ syserr_flag = 1;
+ return (ptr);
+}
+
+static char *
+Strdup(const char *str)
+{
+ char *ptr;
+
+ ptr = strdup(str);
+ if (ptr == NULL)
+ syserr_flag = 1;
+ return (ptr);
+}
+
+static int
+Asprintf(char **ret, const char *format, ...)
+{
+ va_list ap;
+ int rv;
+
+ va_start(ap, format);
+ rv = vasprintf(ret, format, ap);
+ va_end(ap);
+ if (rv < 0)
+ syserr_flag = 1;
+ return (rv);
+}
+
+static int
+Fclose(FILE *fp)
+{
+ int rv;
+
+ rv = fclose(fp);
+ if (rv != 0)
+ syserr_flag = 1;
+ return (rv);
+}
+
+static int
+Fstat(int fd, struct stat *statbuf)
+{
+ if (fstat(fd, statbuf) < 0) {
+ syserr_flag = 1;
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Log an error message. If code is not equal to zero,
+ * then output corresponding message for error code as well.
+ */
+static void
+vlogmsg_generic(const int config_flag, int code, const char *format,
+ va_list ap)
+{
+ const char *msg, *err_pre, *err_msg, *type_msg;
+ char *msg0;
+ int rv, errno_save, type_pri;
+ char buf[LOG_BUF_SIZE];
+
+ errno_save = errno;
+ msg0 = NULL;
+
+ rv = vsnprintf(buf, sizeof(buf), format, ap);
+ if (rv < 0)
+ msg = "(vlogmsg_generic: vsnprintf failed)";
+ else if (rv < LOG_BUF_SIZE)
+ msg = buf;
+ else {
+ msg0 = malloc(++rv);
+ if (msg0 == NULL)
+ msg = "(vlogmsg_generic: malloc failed)";
+ else {
+ if (vsnprintf(msg0, rv, format, ap) < 0) {
+ free(msg0);
+ msg0 = NULL;
+ msg = "(vlogmsg_generic: vsnprintf failed)";
+ } else
+ msg = msg0;
+ }
+ }
+
+ if (code == LOGMSG_WARN) {
+ code = 0;
+ type_pri = LOG_WARNING;
+ type_msg = "warning";
+ } else {
+ type_pri = LOG_ERR;
+ type_msg = "error";
+ }
+ if (code == 0)
+ err_pre = err_msg = "";
+ else {
+ err_pre = ": ";
+ err_msg = strerror(code);
+ }
+
+ if (config_flag) {
+ if (log_to_console)
+ fprintf(stderr, "parsing %s: %s:%u: %s%s%s\n",
+ type_msg, exp_file_name, lineno, msg,
+ err_pre, err_msg);
+ else
+ syslog(type_pri, "parsing %s: %s:%u: %s%s%s",
+ type_msg, exp_file_name, lineno, msg,
+ err_pre, err_msg);
+ } else {
+ if (log_to_console)
+ fprintf(stderr, "%s%s%s\n", msg, err_pre, err_msg);
+ else
+ syslog(type_pri, "%s%s%s", msg, err_pre, err_msg);
+ }
+
+ free(msg0);
+ errno = errno_save;
+}
+
+static void
+vlogmsg(const int code, const char *format, va_list ap)
+{
+ vlogmsg_generic(0, code, format, ap);
+}
+
+/*
+ * Log an error message with corresponding error message for errno.
+ */
+static void
+logmsg(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vlogmsg(errno, format, ap);
+ va_end(ap);
+}
+
+/*
+ * Log an error message.
+ */
+static void
+logmsgx(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vlogmsg(0, format, ap);
+ va_end(ap);
+}
+
+/*
+ * Log a warning message.
+ */
+static void
+logmsgw(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vlogmsg(LOGMSG_WARN, format, ap);
+ va_end(ap);
+}
+
+static void
+vlogconfe(const int code, const char *format, va_list ap)
+{
+ vlogmsg_generic(1, code, format, ap);
+}
+
+/*
+ * Log an error message during configuration with corresponding
+ * error message for errno.
+ */
+static void
+logconfe(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vlogconfe(errno, format, ap);
+ va_end(ap);
+}
+
+/*
+ * Log an error message during configuration.
+ */
+static void
+logconfx(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vlogconfe(0, format, ap);
+ va_end(ap);
+}
+
+/*
+ * Log a warning message.
+ */
+static void
+logconfw(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vlogconfe(LOGMSG_WARN, format, ap);
+ va_end(ap);
+}
+
+/*
+ * Remember list of exports files/directories and recognize what is
+ * a file and what is a directory from the configuration point of view.
+ */
+int
+set_exp_file(const int argc, char **argv)
+{
+ const char *ptr;
+ size_t len;
+ int i;
+
+ if (exp_cmd_arr != NULL) {
+ if (argc != 0) {
+ warnx("exports file(s) cannot be used together "
+ "with -c option(s)");
+ return (-1);
+ }
+ exp_file_num = 0;
+ return (0);
+ }
+
+ if (argc == 0) {
+ exp_file_arr = &exp_file_arr_def;
+ exp_file_num = 1;
+ return (0);
+ }
+
+ exp_file_arr = malloc(argc * sizeof(*exp_file_arr));
+ if (exp_file_arr == NULL) {
+ warn("set_exp_file: malloc");
+ return (-1);
+ }
+ for (i = 0; i < argc; ++i) {
+ exp_file_arr[i].name = ptr = argv[i];
+ len = strlen(ptr);
+ exp_file_arr[i].isfile =
+ (len > 0 && ptr[len - 1] == '/') ? 0 : 1;
+ }
+ exp_file_num = argc;
+ return (0);
+}
+
+/*
+ * Remember -c option's argument.
+ */
+int
+add_exp_cmd(char *str)
+{
+ exp_cmd_arr = realloc(exp_cmd_arr,
+ ++exp_cmd_num * sizeof(*exp_cmd_arr));
+ if (exp_cmd_arr == NULL) {
+ warn("add_exp_cmd: realloc");
+ return (-1);
+ }
+ exp_cmd_arr[exp_cmd_num - 1] = str;
+ return (0);
+}
+
+/*
+ * Pack data in a string, removing escape sequences.
+ * This function fails if some escape sequence is wrong.
+ */
+static int
+remove_escapes(char *s)
+{
+ const char *ptr;
+ char *mod;
+ u_int x1, x2, x3;
+
+ for (ptr = mod = s; *ptr != '\0'; ++mod)
+ if (*ptr != '\\') {
+ *mod = *ptr;
+ ++ptr;
+ } else {
+ ++ptr;
+ if (ptr[0] == '\0' || ptr[1] == '\0' ||
+ ptr[2] == '\0' || !isdigit(ptr[0]) ||
+ !isdigit(ptr[1]) || !isdigit(ptr[2])) {
+ logconfx("three octal digits are expected "
+ "after '\\'");
+ return (-1);
+ }
+ x1 = ptr[0] - '0';
+ x2 = ptr[1] - '0';
+ x3 = ptr[2] - '0';
+ if (x1 > 7 || x2 > 7 || x3 > 7) {
+ logconfx("non-octal digit after '\\'");
+ return (-1);
+ }
+ x3 += x1 * 64 + x2 * 8;
+ if (x3 > UCHAR_MAX) {
+ logconfx("too big value %u for one character "
+ "representation", x3);
+ return (-1);
+ }
+ *mod = (u_char)x3;
+ ptr += 3;
+ }
+ *mod = '\0';
+ return (0);
+}
+
+/*
+ * Build readable representation of address specification.
+ * x.x.x.x or x:x... for host address and x.x.x.x/y or x:x.../y for network.
+ * This function is non-reentrant, since works with one buffer.
+ */
+char *
+addr_spec_str(const u_int family, const struct addr_spec *as)
+{
+ static char repres[INET6_ADDRSTRLEN + 3];
+
+ const void *addr;
+
+ if (family == AF_INET)
+ addr = &ADDR4_SPEC_CPTR(as)->addr;
+ else
+ addr = &ADDR6_SPEC_CPTR(as)->addr;
+ if (inet_ntop(family, addr, repres, INET6_ADDRSTRLEN) != NULL) {
+ if (as->maskbits != 0) {
+ char *ptr;
+
+ ptr = strchr(repres, '\0');
+ sprintf(ptr, "/%u", as->maskbits);
+ }
+ } else {
+ repres[0] = '?';
+ repres[1] = '\0';
+ }
+ return (repres);
+}
+
+/*
+ * Compare two addresses.
+ */
+int
+addrcmp(const uint8_t *a1, const uint8_t *a2, const u_int len)
+{
+ u_int i;
+
+ for (i = 0; i < len; ++i)
+ if (a1[i] != a2[i])
+ return (1);
+ return (0);
+}
+
+/*
+ * Turn off bits in addr, which are not present in mask.
+ */
+static void
+correct_network(uint8_t *addr, const uint8_t *mask, const u_int len)
+{
+ u_int i;
+
+ for (i = 0; i < len; ++i)
+ *addr++ &= *mask++;
+}
+
+/*
+ * Check whether the error (the errno value) is equal to something
+ * that can be corrected in a file system.
+ */
+int
+crit_fs_err(const int error)
+{
+ switch (error) {
+ case ELOOP:
+ case ENOENT:
+ case ENOTDIR:
+ case ENAMETOOLONG:
+ return (0);
+ }
+ syserr_flag = 1;
+ return (1);
+}
+
+/*
+ * find_*() functions find similar structures in the appropriate list.
+ * If a similar structure does not exist, then create a new one and link
+ * it to the appropriate list. A new or existed structure does not
+ * get a new reference.
+ */
+static struct cred_exp *
+find_cred_exp(const uid_t uid, const u_int ngids, const gid_t *gids)
+{
+ struct cred_exp *ce;
+ u_int i;
+
+ /* Find duplicate. */
+ LIST_FOREACH(ce, &cred_exp_list, link)
+ if (ce->uid == uid && ce->ngids == ngids) {
+ for (i = 0; i < ngids; ++i)
+ if (ce->gids[i] != gids[i])
+ break;
+ if (i == ngids)
+ return (ce);
+ }
+
+ /* Allocate a new one. */
+ ce = Malloc(sizeof(*ce));
+ if (ce == NULL) {
+ log_err("find_cred_exp: malloc");
+ return (NULL);
+ }
+ LIST_INSERT_HEAD(&cred_exp_list, ce, link);
+ ce->uid = uid;
+ ce->ngids = ngids;
+ for (i = 0; i < ngids; ++i)
+ ce->gids[i] = gids[i];
+ ce->ref_count = 0;
+ return (ce);
+}
+
+static struct secflavors *
+find_secflavors(const u_int nsec, const int *sec)
+{
+ struct secflavors *sf;
+ u_int i;
+
+ /* Find duplicate. */
+ LIST_FOREACH(sf, &secflavors_list, link)
+ if (sf->nsec == nsec) {
+ for (i = 0; i < nsec; ++i)
+ if (sf->sec[i] != sec[i])
+ break;
+ if (i == nsec)
+ return (sf);
+ }
+
+ /* Allocate a new one. */
+ sf = Malloc(sizeof(*sf));
+ if (sf == NULL) {
+ log_err("find_secflavors: malloc");
+ return (NULL);
+ }
+ LIST_INSERT_HEAD(&secflavors_list, sf, link);
+ sf->nsec = nsec;
+ for (i = 0; i < nsec; ++i)
+ sf->sec[i] = sec[i];
+ sf->ref_count = 0;
+ return (sf);
+}
+
+static struct addr_spec *
+find_addr_spec(const u_int family, const int net_flag, struct addr_spec *as)
+{
+ struct addr_spec *as0;
+ u_int maskbits;
+
+ maskbits = as->maskbits;
+ as->ref_count = 0;
+
+ if (family == AF_INET) {
+ struct addr4_spec *as4;
+
+ /* IPv4 address spec. */
+ as4 = ADDR4_SPEC_PTR(as);
+ if (net_flag) {
+ /* Correct network according to mask spec. */
+ correct_network((uint8_t *)&as4->addr,
+ (uint8_t *)&as4->mask, IPV4_ADDR_LEN);
+ }
+
+ /* Try to find similar address spec. */
+ LIST_FOREACH(as0, &addr4_spec_list, link)
+ if (as4->addr.s_addr ==
+ ADDR4_SPEC_PTR(as0)->addr.s_addr &&
+ maskbits == as0->maskbits)
+ break;
+ if (as0 == NULL) {
+ /* New address spec. */
+ LIST_INSERT_HEAD(&addr4_spec_list, as, link);
+ }
+ } else {
+ struct addr6_spec *as6;
+
+ /* IPv6 address spec. */
+ as6 = ADDR6_SPEC_PTR(as);
+ if (net_flag) {
+ /* Correct network according to mask spec. */
+ correct_network((uint8_t *)&as6->addr,
+ (uint8_t *)&as6->mask, IPV6_ADDR_LEN);
+ }
+
+ /* Try to find similar address spec. */
+ LIST_FOREACH(as0, &addr6_spec_list, link)
+ if (maskbits == as0->maskbits &&
+ addrcmp((uint8_t *)&as6->addr,
+ (uint8_t *)&ADDR6_SPEC_PTR(as0)->addr,
+ IPV6_ADDR_LEN) == 0)
+ break;
+ if (as0 == NULL) {
+ /* New address spec. */
+ LIST_INSERT_HEAD(&addr6_spec_list, as, link);
+ }
+ }
+
+ if (as0 != NULL) {
+ /* Have found similar address spec. */
+ free(as);
+ as = as0;
+ }
+
+ return (as);
+}
+
+/*
+ * Insert fs_exp to the given fs_exp_list in alphabetical order.
+ */
+static void
+link_fs_exp(struct fs_exp_list *fe_list, struct fs_exp *fe)
+{
+ struct fs_exp *fe0;
+
+ TAILQ_FOREACH(fe0, fe_list, link)
+ if (strcmp(fe0->path, fe->path) > 0) {
+ TAILQ_INSERT_BEFORE(fe0, fe, link);
+ return;
+ }
+ TAILQ_INSERT_TAIL(fe_list, fe, link);
+}
+
+/*
+ * Increase no_mnt_exp value in fs_exp structure according to flags.
+ */
+static void
+inc_no_mnt_exp(struct fs_exp *fe, const uint32_t flags)
+{
+ if (flags & OPT_NO_MNT_EXP)
+ fe->no_mnt_exp++;
+}
+
+/*
+ * Decrease no_mnt_exp value in fs_exp structure according to flags.
+ */
+static void
+dec_no_mnt_exp(struct fs_exp *fe, const uint32_t flags)
+{
+ if (flags & OPT_NO_MNT_EXP)
+ fe->no_mnt_exp--;
+}
+
+/*
+ * Initialize fields in fs_exp (path field must be set before).
+ */
+static void
+init_fs_exp(struct fs_exp *fe)
+{
+ fe->path_len = strlen(fe->path);
+ fe->oflags = 0;
+ fe->ae_def = NULL;
+ TAILQ_INIT(&fe->ae4_list);
+ TAILQ_INIT(&fe->ae6_list);
+ fe->nspec = 0;
+ fe->no_mnt_exp = 0;
+ LIST_INIT(&fe->mh4_list);
+ LIST_INIT(&fe->mh6_list);
+}
+
+/*
+ * Release memory used by all addr_exp structures from the given list,
+ * dropping references for all referenced structures.
+ */
+static void
+free_addr_exp_list(struct addr_exp_list *ae_list)
+{
+ struct addr_exp *ae, *ae_next;
+
+ TAILQ_FOREACH_SAFE(ae, ae_list, link, ae_next) {
+ if (ae->addr_spec != NULL)
+ ae->addr_spec->ref_count--;
+ if (ae->cred_exp != NULL)
+ ae->cred_exp->ref_count--;
+ if (ae->secflavors != NULL)
+ ae->secflavors->ref_count--;
+ free(ae);
+ }
+}
+
+/*
+ * Release memory used by one fs_exp, also unlink it from the list.
+ */
+static void
+free_fs_exp(struct fs_exp_list *fe_list, struct fs_exp *fe)
+{
+ free(fe->path);
+ if (fe->ae_def != NULL) {
+ if (fe->ae_def->cred_exp != NULL)
+ fe->ae_def->cred_exp->ref_count--;
+ if (fe->ae_def->secflavors != NULL)
+ fe->ae_def->secflavors->ref_count--;
+ free(fe->ae_def);
+ }
+ free_addr_exp_list(&fe->ae4_list);
+ free_addr_exp_list(&fe->ae6_list);
+ mntlist_free_mh_list(&fe->mh4_list);
+ mntlist_free_mh_list(&fe->mh6_list);
+ TAILQ_REMOVE(fe_list, fe, link);
+ free(fe);
+}
+
+/*
+ * Release memory used by the given file systems export list.
+ */
+void
+free_fs_exp_list(struct fs_exp_list *fe_list)
+{
+ struct fs_exp *fe, *fe_next;
+
+ TAILQ_FOREACH_SAFE(fe, fe_list, link, fe_next)
+ free_fs_exp(fe_list, fe);
+}
+
+/*
+ * If some structure does not have any references, then remove it
+ * from the appropriate list and release its memory.
+ */
+void
+free_unref_conf(void)
+{
+ struct addr_spec_list *as_list;
+ struct cred_exp *ce, *ce_next;
+ struct addr_spec *as, *as_next;
+ struct secflavors *sf, *sf_next;
+
+ LIST_FOREACH_SAFE(ce, &cred_exp_list, link, ce_next)
+ if (ce->ref_count == 0) {
+ LIST_REMOVE(ce, link);
+ free(ce);
+ }
+
+ for (as_list = &addr4_spec_list;;) {
+ LIST_FOREACH_SAFE(as, as_list, link, as_next)
+ if (as->ref_count == 0) {
+ LIST_REMOVE(as, link);
+ free(as);
+ }
+ if (as_list == &addr6_spec_list)
+ break;
+ as_list = &addr6_spec_list;
+ }
+
+ LIST_FOREACH_SAFE(sf, &secflavors_list, link, sf_next)
+ if (sf->ref_count == 0) {
+ LIST_REMOVE(sf, link);
+ free(sf);
+ }
+}
+
+/*
+ * Some file systems can have wrong configuration, remove them.
+ */
+static void
+free_wrong_conf(void)
+{
+ struct fs_exp_list *fe_list;
+ struct fs_exp *fe, *fe_next;
+
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH_SAFE(fe, fe_list, link, fe_next)
+ if (fe->oflags & OPT_ERRCONFIG)
+ free_fs_exp(fe_list, fe);
+}
+
+/*
+ * Check that the given mask is a valid network mask. Returns 0 if
+ * the mask is acceptable (i.e. its binary form is 1...10...0).
+ * In *maskbitsp return number of bits in a mask.
+ */
+static int
+check_mask(const uint8_t *mask, const u_int len, u_int *maskbitsp)
+{
+ u_int i, maskbits;
+
+ /* First byte should not be 0. */
+ if (mask[0] == 0)
+ return (-1);
+
+ maskbits = 0;
+
+ /* Find first non 11..1 byte. */
+ for (i = 0; i < len; ++i) {
+ if (mask[i] != UINT8_MAX)
+ break;
+ maskbits += 8;
+ }
+
+ /* Check for 11..10..0 byte. */
+ if (i < len) {
+ if (mask[i] != 0) {
+ if ((uint8_t)~mask[i] & (uint8_t)(~mask[i] + 1))
+ return (-1);
+ maskbits += 8 + 1 - ffs(mask[i]);
+ }
+ ++i;
+ }
+
+ /* Rest of bytes should be equal to zero. */
+ for (; i < len; ++i)
+ if (mask[i] != 0)
+ return (-1);
+
+ *maskbitsp = maskbits;
+
+ return (0);
+}
+
+/*
+ * Make a network mask according to the specified prefix length,
+ * len is the length of mask in bytes, maskbits is the length
+ * of the prefix in bits.
+ */
+static int
+make_mask(uint8_t *mask, const u_int len, u_int maskbits)
+{
+ u_int i, n;
+
+ if (maskbits > len * 8 || maskbits == 0)
+ return (-1);
+
+ n = maskbits / 8;
+ for (i = 0; i < n; ++i)
+ mask[i] = UINT8_MAX;
+
+ maskbits %= 8;
+ if (maskbits != 0) {
+ mask[i] = ~((1 << (8 - maskbits)) - 1);
+ ++i;
+ }
+
+ for (; i < len; ++i)
+ mask[i] = 0;
+
+ return (0);
+}
+
+/*
+ * Read one line (honoring line continuation characters).
+ * Return:
+ * 1 -- if line was successfully read;
+ * -1 -- if some error occurred;
+ * 0 -- if EOF occurred.
+ */
+static int
+read_line(FILE *fp)
+{
+ char *ptr; /* Start in linebuf. */
+ size_t navail; /* Available space in linebuf. */
+ size_t nread; /* How many bytes have been read. */
+ size_t len; /* Length of read string. */
+ int is_cont; /* Non-zero if line is continued. */
+
+ ptr = linebuf;
+ navail = linesize;
+ nread = 0;
+ is_cont = 0;
+
+ for (;;) {
+ if (fgets(ptr, (int)navail, fp) == NULL) {
+ if (feof(fp)) {
+ ++lineno;
+ break;
+ }
+ logconfe("read_line: fgets(%s)", exp_file_name);
+ syserr_flag = 1;
+ return (-1);
+ }
+
+ len = strlen(ptr);
+ if (len == 0) {
+ /* Nothing was read, repeat again. */
+ continue;
+ }
+
+ nread += len;
+ if (linebuf[nread - 1] == '\n') {
+ ++lineno;
+ --nread;
+ if (!is_cont) {
+ /* Check for comment. */
+ if (linebuf[0] == '#')
+ return (1);
+ }
+ if (nread > 0 && linebuf[nread - 1] == '\\') {
+ /* Line is continued in next line. */
+ is_cont = 1;
+ --nread;
+ } else {
+ /* One non-last line was read. */
+ linebuf[nread] = '\0';
+ return (1);
+ }
+ }
+ navail = linesize - nread;
+ /* Can call fgets() if can read >= 2 chars. */
+ if (navail >= 2) {
+ ptr = linebuf + nread;
+ continue;
+ }
+
+ /* Increase linebuf. */
+ linesize *= 2;
+ if (linesize > LINEBUF_MAX) {
+ logconfx("read_line: too long configuration line "
+ "(more than %d)", LINEBUF_MAX);
+ return (-1);
+ }
+ ptr = Realloc(linebuf, linesize);
+ if (ptr == NULL) {
+ logconfe("read_line: realloc(%zu)", linesize);
+ return (-1);
+ }
+ linebuf = ptr;
+ ptr += nread;
+ navail = linesize - nread;
+ }
+
+ if (nread > 0 && linebuf[nread - 1] == '\\') {
+ /* EOF and ...\ like line. */
+ --nread;
+ linebuf[nread] = '\0';
+ }
+
+ return (nread > 0);
+}
+
+/*
+ * Return address of the first non white space character in the given string.
+ */
+static char *
+skip_spaces(char *str)
+{
+ for (;; ++str)
+ if (*str != ' ' && *str != '\t')
+ break;
+ return (str);
+}
+
+/*
+ * Select a new field from the configuration line.
+ * On enter *beg is start for search. On exit *beg is the next after
+ * the next character after a new field if EOL is not reached, else
+ * *beg will point to last '\0' character in a line. Return start of
+ * a new field, which is finished by '\0'.
+ */
+static char *
+select_field(char **beg)
+{
+ char *ptr, *start;
+
+ start = ptr = skip_spaces(*beg);
+
+ /* Find the end of a field and finish it with '\0'. */
+ for (;; ++ptr) {
+ if (*ptr == ' ' || *ptr == '\t') {
+ *ptr = '\0';
+ ++ptr;
+ break;
+ } else if (*ptr == '\0')
+ break;
+ }
+
+ *beg = ptr;
+
+ return (start);
+}
+
+/*
+ * In the beginning of a line find a path (path is a string that begins
+ * with '/' character) and find or create appropriate fs_exp structure.
+ * On enter *beg0 is the start of search, on exit *beg0 is the next
+ * character after current field if EOL is not reached, or it points
+ * to last '\0' character in a line.
+ */
+static int
+parse_dir(char **beg0, char **end0)
+{
+ struct fs_exp *fe;
+ const char *ptr;
+ char *beg, *end;
+ size_t len;
+
+ end = *beg0;
+ beg = select_field(&end);
+ if (remove_escapes(beg) < 0)
+ return (-1);
+
+ if (*beg != '/') {
+ logconfx("a directory name with absolute path is expected");
+ return (-1);
+ }
+
+ /* Validate length of directory. */
+ len = end - beg;
+ if (*end != '\0')
+ --len;
+ if (len > RPCMNT_PATHLEN) {
+ logconfx("too long directory name %s (%zu > %u)",
+ beg, len, RPCMNT_PATHLEN);
+ return (-1);
+ }
+
+ ptr = beg + len - 1;
+
+ /* Check for /.. /. /../ and /./ in directory path. */
+ if ((*ptr == '.' && (*(ptr - 1) == '/' || (*(ptr - 1) == '.' &&
+ *(ptr - 2) == '/'))) || strstr(beg, "/../") != NULL ||
+ strstr(beg, "/./") != NULL) {
+ logconfx("directory %s contains \"..\" or \".\" component, "
+ "this is not allowed", beg);
+ return (-1);
+ }
+
+ /* Check for // inside and / at the end of directory path. */
+ if ((*ptr == '/' && ptr != beg) || strstr(beg, "//") != NULL) {
+ logconfx("directory %s contains \"//\" inside or \"/\" "
+ "at the end, this is not allowed", beg);
+ return (-1);
+ }
+
+ /* Find fs_exp. */
+ fe = fs_exp_by_path(fs_exp_list, beg);
+ if (fe == NULL) {
+ /* New fs_exp. */
+ if ((fe = Malloc(sizeof(*fe))) == NULL ||
+ (fe->path = Strdup(beg)) == NULL) {
+ free(fe);
+ logconfe("parse_dir: malloc/strdup");
+ return (-1);
+ }
+ init_fs_exp(fe);
+ link_fs_exp(fs_exp_list, fe);
+ }
+
+ fs_exp = fe;
+ beg = select_field(&end);
+ *beg0 = beg;
+ *end0 = end;
+
+ return (0);
+}
+
+/*
+ * Parse an argument of -mapall and -maproot options.
+ */
+static int
+parse_opt_cred(char *arg)
+{
+ gid_t gids[NGROUPS + 1];
+ const struct group *grp;
+ const struct passwd *pwd;
+ const char *errstr;
+ char *ptr, *group_name;
+ uid_t uid;
+ int i, j, ngids;
+
+ if (oflags & (OPT_MAPALL|OPT_MAPROOT)) {
+ logconfx("mix of -mapall and -maproot options is not allowed");
+ return (-1);
+ }
+
+ /*
+ * Valid credential combinations (a user can be a string or UID,
+ * a group can be a string or GID):
+ * user -- user with its groups;
+ * user: -- user with no groups;
+ * user:group:... -- user with the given groups.
+ */
+
+ if (strcmp(arg, "-2:-2") == 0) {
+ cred_exp = &cred_exp_anon_def;
+ return (0);
+ }
+
+ group_name = strchr(arg, ':');
+ if (group_name != NULL)
+ *group_name++ = '\0';
+
+ errno = 0;
+ pwd = getpwnam(arg);
+ if (pwd == NULL) {
+ if (errno != 0) {
+ logconfe("parse_opt_cred: getpwnam(%s)", arg);
+ return (-1);
+ }
+ if (isdigit(*arg) == 0) {
+ logconfx("cannot find user \"%s\"", arg);
+ return (-1);
+ }
+ uid = (uid_t)strtonum(arg, 0, UID_MAX, &errstr);
+ if (errstr != NULL) {
+ logconfx("cannot convert \"%s\" to UID: %s", arg,
+ errstr);
+ return (-1);
+ }
+ if (group_name == NULL) {
+ errno = 0;
+ pwd = getpwuid(uid);
+ if (pwd == NULL) {
+ if (errno != 0)
+ logconfe("parse_opt_cred: "
+ "getpwuid(%lu)", (u_long)uid);
+ else
+ logconfx("unknown user with UID %lu",
+ (u_long)uid);
+ return (-1);
+ }
+ }
+ uid = uid;
+ } else
+ uid = pwd->pw_uid;
+
+ if (group_name == NULL) {
+ /* Groups are GIDs of the given user. */
+ ngids = NGROUPS + 1;
+ if (getgrouplist(pwd->pw_name, pwd->pw_gid, gids, &ngids) < 0) {
+ logconfx("parse_opt_cred: not enough space for "
+ "getgrouplist");
+ return (-1);
+ }
+ /* First GID is duplicated somewhere in gids[]. */
+ --ngids;
+ for (i = 0; i < ngids; ++i)
+ gids[i] = gids[i + 1];
+ goto done;
+ }
+
+ if (*group_name == '\0') {
+ /* User with no groups. */
+ ngids = 0;
+ goto done;
+ }
+
+ /* Groups are specified as group1:group2:group3 */
+ ngids = 0;
+ for (;;) {
+ ptr = strchr(group_name, ':');
+ if (ptr != NULL)
+ *ptr++ = '\0';
+
+ errno = 0;
+ grp = getgrnam(group_name);
+ if (grp == NULL) {
+ if (errno != 0) {
+ logconfe("parse_opt_cred: getgrnam(%s)",
+ group_name);
+ return (-1);
+ }
+ if (isdigit(*group_name) == 0) {
+ logconfx("cannot find group \"%s\"",
+ group_name);
+ return (-1);
+ }
+ gids[ngids] =
+ (gid_t)strtonum(group_name, 0, GID_MAX, &errstr);
+ if (errstr != NULL) {
+ logconfx("cannot convert \"%s\" to GID: %s",
+ group_name, errstr);
+ return (-1);
+ }
+ } else
+ gids[ngids] = grp->gr_gid;
+
+ group_name = ptr;
+ if (group_name == NULL)
+ break;
+
+ if (++ngids == NGROUPS) {
+ logconfx("too many groups (more than %u) in "
+ "credential specification", NGROUPS);
+ return (-1);
+ }
+ }
+ ++ngids;
+done:
+ /* Check for duplicates in gids[]. */
+ for (i = 0; i < ngids; ++i)
+ for (j = i + 1; j < ngids; ++j)
+ if (gids[i] == gids[j]) {
+ logconfx("duplicated GID %lu in credentials",
+ (u_long)gids[i]);
+ return (-1);
+ }
+
+ cred_exp = find_cred_exp(uid, ngids, gids);
+ if (cred_exp == NULL)
+ return (-1);
+ return (0);
+}
+
+/*
+ * Parse an argument of the -index option.
+ */
+static int
+parse_opt_index(char *arg)
+{
+ int error;
+
+ if (!(oflags & OPT_PUBLIC)) {
+ logconfx("-index option should be used only after -public or "
+ "-webnfs option");
+ return (-1);
+ }
+
+ /* File name should not contain '/' and cannot be "." or "..". */
+ error = strchr(arg, '/') != NULL;
+ if (error == 0 && arg[0] == '.')
+ switch (arg[1]) {
+ case '\0':
+ error = 1;
+ break;
+ case '.':
+ if (arg[2] == '\0')
+ error = 1;
+ break;
+ }
+
+ if (error) {
+ logconfx("argument of -index option should be a file name, "
+ "not file pathname or directory");
+ return (-1);
+ }
+
+ index_webnfs = Strdup(arg);
+ if (index_webnfs == NULL) {
+ logconfe("parse_opt_index: strdup");
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Similar to inet_network(3), but makes more check, can parse
+ * 255.255.255.255 and returns in nbytes number of specified
+ * bytes in IPv4 dot-notation address. Return -1 if cannot
+ * parse the given address, otherwise return 0.
+ */
+static int
+inet_network3(const char *cp, in_addr_t *resp, u_int *nbytes)
+{
+ in_addr_t res, val;
+ u_int base, dots;
+ u_char c;
+ char got_data;
+
+ res = 0;
+ dots = 0;
+ for (;;) {
+ val = 0;
+ got_data = 0;
+ if (*cp == '0') {
+ cp++;
+ if (*cp == 'x' || *cp == 'X') {
+ cp++;
+ base = 16;
+ } else {
+ base = 8;
+ got_data = 1;
+ }
+ } else
+ base = 10;
+ while ((c = *cp) != '\0') {
+ if (isdigit(c)) {
+ if (base == 8 && c > '7')
+ return (-1);
+ val = val * base + c - '0';
+ } else if (base == 16 && isxdigit(c))
+ val = (val << 4) + 10 -
+ (islower(c) ? 'a' : 'A');
+ else
+ break;
+ if (val > 0xff)
+ return (-1);
+ cp++;
+ got_data = 1;
+ }
+ if (!got_data)
+ return (-1);
+ if (dots != 0)
+ res <<= 8;
+ res |= val;
+ if (c != '.')
+ break;
+ if (++dots == 4)
+ return (-1);
+ cp++;
+ }
+ if (c != '\0')
+ return (-1);
+ *resp = res;
+ *nbytes = dots + 1;
+ return (0);
+}
+
+/*
+ * Apply options to the given export specification.
+ */
+static void
+setopts_addr_exp(struct addr_exp *ae, const int net_flag)
+{
+ struct cred_exp *ce;
+ struct secflavors *sf;
+ uint32_t flags;
+
+ ce = cred_exp == NULL ? &cred_exp_anon_def : cred_exp;
+ ae->cred_exp = ce;
+ ce->ref_count++;
+ sf = secflavors;
+ if (sf == NULL)
+ sf = gsecflavors != NULL ? gsecflavors : &secflavors_def;
+ ae->secflavors = sf;
+ sf->ref_count++;
+ if (ae->addr_spec != NULL)
+ flags = net_flag ? OPT_NETWORK : OPT_HOST;
+ else
+ flags = 0;
+ if (oflags & (OPT_RO|OPT_RW))
+ flags |= oflags & (OPT_RO|OPT_RW);
+ else if (oflagsall & (OPT_RO|OPT_RW))
+ flags |= oflagsall & (OPT_RO|OPT_RW);
+ else
+ flags |= OPT_RW;
+ if (oflags & (OPT_MAPALL|OPT_MAPROOT))
+ flags |= oflags & (OPT_MAPALL|OPT_MAPROOT);
+ else if (oflagsall & (OPT_MAPALL|OPT_MAPROOT))
+ flags |= oflagsall & (OPT_MAPALL|OPT_MAPROOT);
+ else
+ flags |= OPT_MAPROOT;
+ if (opt_deny)
+ flags |= OPT_DENY;
+ if (exp_cmd_num > 0) {
+ if (_CMD_OPT(oflagsall) == CMD_OPT_DELETE ||
+ (ocmd_set && ocmd == CMD_OPT_DELETE)) {
+ flags &= OPT_HOST | OPT_NETWORK;
+ flags |= CMD_OPT_DELETE;
+ } else
+ flags |= _CMD_OPT(oflagsall) | ocmd;
+ } else
+ flags |= CMD_OPT_ADD;
+ flags |= oflagsall & (OPT_NO_MNT_DMP|OPT_NO_MNT_EXP);
+ if (!test_conf)
+ flags |= (goflags | fs_exp->oflags) & OPT_NO_MNT_DMP;
+ inc_no_mnt_exp(fs_exp, flags);
+ ae->oflags = flags;
+}
+
+/*
+ * All addr_exp structures are sorted by maskbits value as follows:
+ * at the beginning all structures with zero maskbits (hosts), then
+ * all structures in descending order of maskbits value.
+ */
+static void
+link_addr_exp(struct addr_exp_list *ae_list, struct addr_exp *ae)
+{
+ u_int maskbits;
+
+ maskbits = ae->addr_spec->maskbits;
+ if (maskbits == 0)
+ TAILQ_INSERT_HEAD(ae_list, ae, link);
+ else {
+ struct addr_exp *ae0;
+
+ TAILQ_FOREACH(ae0, ae_list, link)
+ if (ae0->addr_spec->maskbits != 0 &&
+ ae0->addr_spec->maskbits <= maskbits) {
+ TAILQ_INSERT_BEFORE(ae0, ae, link);
+ return;
+ }
+ TAILQ_INSERT_TAIL(ae_list, ae, link);
+ }
+}
+
+/*
+ * In general this routine creates addr_exp and links current addr_spec to
+ * the appropriate list. But it also makes final test for -network option
+ * and finds similar address specification in the corresponding list.
+ * If this function fails, then addr_spec can be freed (it was not linked
+ * to any list or is NULL).
+ */
+static int
+add_addr_exp(const int net_flag)
+{
+ struct fs_exp *fe;
+ struct addr_exp_list *ae_list;
+ struct addr_spec *as;
+ struct addr_exp *ae;
+ u_int maskbits, family;
+
+ as = addr_spec;
+ if (as == NULL)
+ return (0);
+ fe = fs_exp;
+ family = addr_spec_family;
+ maskbits = as->maskbits;
+
+ if (net_flag) {
+ /* Finish work with previous -network option. */
+ if (family == AF_INET) {
+ if (maskbits == 0) {
+ logconfx("could not build mask for the given "
+ "IPv4 network");
+ return (-1);
+ }
+ } else {
+ if (maskbits == 0) {
+ logconfx("-network option requires -mask "
+ "option for IPv6 address");
+ return (-1);
+ }
+ }
+ }
+
+ addr_spec = NULL;
+ as = find_addr_spec(family, net_flag, as);
+
+ /* Try to find duplicated address specifications. */
+ ae_list = family == AF_INET ? &fe->ae4_list: &fe->ae6_list;
+ TAILQ_FOREACH(ae, ae_list, link)
+ if (ae->addr_spec == as) {
+ logconfx("duplicated address specification %s "
+ "for %s was found in this line",
+ addr_spec_str(family, as), fe->path);
+ return (-1);
+ }
+
+ /* Add new addr_exp to the current fs_exp. */
+ ae = Malloc(sizeof(*ae));
+ if (ae == NULL) {
+ logconfe("add_addr_exp: malloc");
+ return (-1);
+ }
+ ae->addr_spec = as;
+ as->ref_count++;
+ setopts_addr_exp(ae, net_flag);
+ fe->nspec++;
+ if (exp_cmd_num == 0) {
+ /* Sort addr_exp structures. */
+ link_addr_exp(ae_list, ae);
+ } else {
+ /* For commands the order is not important. */
+ TAILQ_INSERT_TAIL(ae_list, ae, link);
+ }
+
+ return (0);
+}
+
+/*
+ * Called from parse_opt_network() and parse_opt_mask().
+ */
+static int
+parse_opt_net_mask(char *arg, const int mask_flag)
+{
+ struct in6_addr in6_addr;
+ const char *errstr;
+ const struct netent *ne;
+ struct addr4_spec *as4;
+ struct addr6_spec *as6;
+ struct addr_spec *as;
+ char *maskstr;
+ in_addr_t in_addr;
+ u_int family, nbytes, maskbits;
+
+ if (!mask_flag) {
+ maskstr = strchr(arg, '/');
+ if (maskstr != NULL)
+ *maskstr++ = '\0';
+ if (*arg == '!') {
+ ++arg;
+ opt_deny = 1;
+ } else
+ opt_deny = 0;
+ } else
+ maskstr = NULL;
+
+ family = AF_UNSPEC;
+
+ if (isdigit(*arg) != 0) {
+ /* Try to interpret argument as IPv4 numeric address. */
+ if (inet_network3(arg, &in_addr, &nbytes) == 0)
+ family = AF_INET;
+ }
+
+ if (family == AF_UNSPEC && (isxdigit(*arg) != 0 || *arg == ':')) {
+ /* Try to interpret argument as IPv6 numeric address. */
+ switch (inet_pton(AF_INET6, arg, &in6_addr)) {
+ case 0:
+ /* Cannot interpret as numeric IPv6 address. */
+ break;
+ case 1:
+ family = AF_INET6;
+ break;
+ default: /* -1 */
+ logconfe("parse_opt_net_mask: inet_pton");
+ return (-1);
+ }
+ }
+
+ if (family == AF_UNSPEC) {
+ /* Try to interpret as network name. */
+ ne = getnetbyname(arg);
+ if (ne == NULL) {
+ logconfx("cannot interpret \"%s\" address", arg);
+ return (-1);
+ }
+ if (ne->n_addrtype != AF_INET) {
+ logconfx("unsupported family type %d from "
+ "getnetbyname(%s)", ne->n_addrtype, arg);
+ return (-1);
+ }
+ in_addr = ne->n_net;
+ if (in_addr & 0xff000000)
+ nbytes = 4;
+ else if (in_addr & 0xff0000)
+ nbytes = 3;
+ else if (in_addr & 0xff00)
+ nbytes = 2;
+ else
+ nbytes = 1;
+ family = AF_INET;
+ }
+
+ maskbits = 0;
+ if (family == AF_INET) {
+ /* IPv4 address. */
+ if (nbytes != IPV4_ADDR_LEN)
+ in_addr <<= 8 * (IPV4_ADDR_LEN - nbytes);
+ if (maskstr == NULL) {
+ if (IN_CLASSA(in_addr))
+ maskbits = 8;
+ else if (IN_CLASSB(in_addr))
+ maskbits = 16;
+ else if (IN_CLASSC(in_addr))
+ maskbits = 24;
+ else {
+ /* Does not belong to class A, B or C. */
+ }
+ }
+ in_addr = htonl(in_addr);
+ }
+
+ if (maskstr != NULL) {
+ /* Extract a mask from /masklen suffix. */
+ maskbits = strtonum(maskstr, 1, UINT_MAX, &errstr);
+ if (errstr != NULL) {
+ logconfx("cannot interpret mask prefix length "
+ "\"%s\": %s", maskstr, errstr);
+ return (-1);
+ }
+ oflags |= OPT_MASK;
+ }
+
+ if (mask_flag) {
+ const uint8_t *mask;
+ u_int masklen;
+
+ /* The specified address is a mask. */
+ if (family != addr_spec_family) {
+ logconfx("mask in -mask should belong to the same "
+ "family as previously specified network in "
+ "-network");
+ return (-1);
+ }
+ as = addr_spec;
+ if (addr_spec_family == AF_INET) {
+ as4 = ADDR4_SPEC_PTR(as);
+ as4->mask.s_addr = in_addr;
+ mask = (uint8_t *)&as4->mask.s_addr;
+ masklen = IPV4_ADDR_LEN;
+ } else {
+ as6 = ADDR6_SPEC_PTR(as);
+ as6->mask = in6_addr;
+ mask = as6->mask.s6_addr;
+ masklen = IPV6_ADDR_LEN;
+ }
+ if (check_mask(mask, masklen, &maskbits) < 0) {
+ logconfx("wrong mask (in binary form it should be "
+ "1...10...0)");
+ return (-1);
+ }
+ as->maskbits = maskbits;
+ } else {
+ int error;
+
+ /* The specified address is a network address. */
+ error = 0;
+ if (family == AF_INET) {
+ as = Malloc(ADDR_SPEC_IPV4_SIZE);
+ if (as == NULL) {
+ logconfe("parse_opt_net_mask: malloc");
+ return (-1);
+ }
+ as4 = ADDR4_SPEC_PTR(as);
+ as4->addr.s_addr = in_addr;
+ if (maskbits != 0) {
+ error = make_mask((uint8_t *)&as4->mask.s_addr,
+ IPV4_ADDR_LEN, maskbits);
+ as->maskbits = maskbits;
+ }
+ } else {
+ as = Malloc(ADDR_SPEC_IPV6_SIZE);
+ if (as == NULL) {
+ logconfe("parse_opt_net_mask: malloc");
+ return (-1);
+ }
+ as6 = ADDR6_SPEC_PTR(as);
+ as6->addr = in6_addr;
+ if (maskbits != 0) {
+ error = make_mask(as6->mask.s6_addr,
+ IPV6_ADDR_LEN, maskbits);
+ as->maskbits = maskbits;
+ }
+ }
+ if (error) {
+ logconfx("wrong mask length %u", maskbits);
+ return (-1);
+ }
+ addr_spec = as;
+ addr_spec_family = family;
+ }
+
+ /* Remember that this line has at least one network. */
+ oflagsall |= OPT_NETWORK;
+ return (0);
+}
+
+/*
+ * Parse an argument of the -mask option.
+ */
+static int
+parse_opt_mask(char *arg)
+{
+ if (parse_opt_net_mask(arg, 1) < 0)
+ return (-1);
+ return (add_addr_exp(1));
+}
+
+/*
+ * This function must be called before any address specification
+ * parsing, since -nospec disallow address specifications.
+ */
+static int
+nospec_check(void)
+{
+ if (oflagsall & OPT_NOSPEC) {
+ logconfx("this line has -nospec option, "
+ "hosts or network addresses are disallowed");
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Parse an argument of the -network option.
+ */
+static int
+parse_opt_network(char *arg)
+{
+ if (nospec_check() < 0)
+ return (-1);
+
+ /* Forget about previous -mask option. */
+ oflags &= ~OPT_MASK;
+
+ return (parse_opt_net_mask(arg, 0));
+}
+
+/*
+ * Parse -nospec option.
+ */
+/* ARGSUSED */
+static int
+parse_opt_nospec(char *arg __unused)
+{
+ if (oflagsall & (OPT_HOST|OPT_NETWORK)) {
+ logconfx("-nospec option cannot be used, since this line "
+ "already has address specifications");
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Parse -public or -webnfs option.
+ */
+/* ARGSUSED */
+static int
+parse_opt_public(char *arg __unused)
+{
+ if (opt_public) {
+ logconfx("-public or -webnfs option can be specified only "
+ "once and for one file system");
+ return (-1);
+ }
+ opt_public = 1;
+ return (0);
+}
+
+/*
+ * Parse -ro and -rw options.
+ */
+/* ARGSUSED */
+static int
+parse_opt_ro_rw(char *arg __unused)
+{
+ if (oflags & (OPT_RO|OPT_RW)) {
+ logconfx("mix of -ro and -rw options is not allowed");
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Parse an argument of the -sec option.
+ */
+static int
+parse_opt_sec(char *arg)
+{
+ int sec[NFSE_NSECFLAV];
+ char *ptr;
+ u_int i, j;
+ int secflav;
+
+#ifndef WITH_SEC
+ logconfw("-sec option does not have any effect on this system");
+#endif
+
+ /* Valid format is flavor1:flavor2... */
+ for (i = 0;;) {
+ ptr = strchr(arg, ':');
+ if (ptr != NULL)
+ *ptr++ = '\0';
+ if (!strcmp(arg, "sys"))
+ secflav = AUTH_SYS;
+ else if (!strcmp(arg, "krb5"))
+ secflav = RPCSEC_GSS_KRB5;
+ else if (!strcmp(arg, "krb5i"))
+ secflav = RPCSEC_GSS_KRB5I;
+ else if (!strcmp(arg, "krb5p"))
+ secflav = RPCSEC_GSS_KRB5P;
+ else {
+ logconfx("unknown security flavor \"%s\"", arg);
+ return (-1);
+ }
+ for (j = 0; j < i; ++j)
+ if (sec[j] == secflav) {
+ logconfx("duplicated security flavor \"%s\"",
+ arg);
+ return (-1);
+ }
+ sec[i] = secflav;
+ ++i;
+ if (ptr == NULL)
+ break;
+ if (i == NFSE_NSECFLAV) {
+ logconfx("too many security flavors (more than %u)",
+ NFSE_NSECFLAV);
+ return (-1);
+ }
+ arg = ptr;
+ }
+
+ secflavors = find_secflavors(i, sec);
+ if (secflavors == NULL)
+ return (-1);
+ return (0);
+}
+
+/*
+ * Used as a comparison function in bsearch(3) for option descriptions.
+ */
+static int
+cmp_opt_descr(const void *key, const void *item)
+{
+ return (strcmp(key, ((const struct opt_descr *)item)->name));
+}
+
+/*
+ * Parse current field with option(s).
+ * On enter *beg0 is the start for search, *end0 is the next
+ * character after a current field if EOL is not reached, or
+ * it points to ther last '\0' character in a line. On return
+ * *beg0 and *end0 are the same, but for the next field.
+ * Return:
+ * 0 -- current field is not a option,
+ * -1 -- some error occurred;
+ * 1 -- current field is a correct option.
+ */
+static int
+parse_opts(const int global_flag, char **beg0, char **end0)
+{
+ const struct opt_descr *od;
+ char *arg, *opt, *end;
+ int comma;
+
+ /*
+ * Valid options combinations:
+ * -opt
+ * -opt=arg
+ * -opt arg
+ * -opt1,opt2
+ * -opt1=arg,opt2
+ * -opt1,opt2 arg
+ */
+
+ opt = *beg0;
+
+ /* Check for EOL and not option field. */
+ if (*opt == '\0' || *opt != '-')
+ return (0);
+
+ /* -... -> ... */
+ ++opt;
+
+ if (*opt == '\0') {
+ logconfx("no option after '-'");
+ return (-1);
+ }
+
+ for (;;) {
+ end = strchr(opt, ',');
+ if (end != NULL) {
+ if (end == opt) {
+ /* ,... */
+ logconfx("wrong usage of ',' in options: "
+ "no option before it");
+ return (-1);
+ }
+ if (*(end + 1) == '\0') {
+ /* ..., */
+ logconfx("wrong usage of ',' in options: "
+ "no option after it");
+ return (-1);
+ }
+ /* ...,... */
+ *end = '\0';
+ comma = 1;
+ } else {
+ /* ... */
+ comma = 0;
+ }
+
+ arg = strchr(opt, '=');
+ if (arg != NULL) {
+ if (arg == opt) {
+ /* =... */
+ logconfx("wrong usage of '=' in options: "
+ "no option before it");
+ return (-1);
+ }
+ if (*(arg + 1) == '\0') {
+ /* ...= */
+ logconfx("wrong usage of '=' in options: "
+ "no argument after it");
+ return (-1);
+ }
+ /* ...=... */
+ *arg++ = '\0';
+ } else {
+ /* ... */
+ }
+
+ od = bsearch(opt, opt_descr_tbl, OPT_DESCR_TBL_SIZE,
+ sizeof(*od), cmp_opt_descr);
+ if (od == NULL) {
+ logconfx("unknown option -%s", opt);
+ return (-1);
+ }
+
+ if (global_flag) {
+ if (!(od->oflags & GL_OPTS_ALLFLAGS)) {
+ logconfx("option -%s cannot be used as a "
+ "global option", opt);
+ return (-1);
+ }
+ } else {
+ if (!(od->oflags & EX_OPTS_ALLFLAGS)) {
+ logconfx("option -%s cannot be used for "
+ "export line", opt);
+ return (-1);
+ }
+ }
+ if (exp_cmd_num > 0)
+ if (od->oflags & ~FE_CMD_OPTS_ALLFLAGS &
+ FE_OPTS_ALLFLAGS) {
+ logconfx("option -%s cannot be used with a "
+ "command", opt);
+ return (-1);
+ }
+
+ if (od->oflags & OPT_MASK) {
+ if (!opt_network) {
+ logconfx("-network option should be used "
+ "before -mask option");
+ return (-1);
+ }
+ } else {
+ /* Finish work with previous -network option. */
+ if (add_addr_exp(1) < 0)
+ return (-1);
+ }
+
+ opt_network = (od->oflags & OPT_NETWORK) ? 1 : 0;
+
+ if (od->oflags & (OPT_NETWORK|OPT_HOST))
+ saw_addr_spec = 1;
+ else if (saw_addr_spec && !(od->oflags & OPT_MASK)) {
+ /*
+ * New option (not -mask) right after address
+ * specification, forget previous address
+ * specification related options.
+ */
+ oflags &= ~AE_OPTS_ALLFLAGS;
+ saw_addr_spec = 0;
+ }
+
+ if ((global_flag && (od->oflags & goflags)) ||
+ (od->oflags & oflags)) {
+ logconfx("option -%s is duplicated in this line or "
+ "it is mutually exclusive with already specified "
+ "options", opt);
+ return (-1);
+ }
+
+ if (!(od->oflags & OPT_ARG))
+ arg = NULL;
+ else if (arg == NULL) {
+ if (!comma) {
+ *beg0 = select_field(end0);
+ arg = *beg0;
+ if (*arg == '\0' || *arg == '-')
+ arg = NULL;
+ }
+ if (arg == NULL) {
+ logconfx("option -%s requires an argument",
+ opt);
+ return (-1);
+ }
+ }
+
+ if (arg != NULL)
+ if (remove_escapes(arg) < 0)
+ return (-1);
+
+ if (od->oflags & OPT_COMPAT)
+ logconfw("option -%s is obsolete", opt);
+
+ if (od->parse != NULL)
+ if (od->parse(arg) < 0) {
+ logconfx("-%s option%s parsing failed", opt,
+ arg != NULL ? "'s argument" : "");
+ return (-1);
+ }
+
+ /*
+ * Since -host, -network and options with arguments can be
+ * used several times, forget about corresponding flags.
+ */
+ oflags |= od->oflags &
+ ~(OPT_ARG|OPT_COMPAT|OPT_HOST|OPT_NETWORK);
+
+ /*
+ * Update oflagsall, special case is mutual exclusive
+ * options -maproot and -mapall, -ro and -rw.
+ */
+ if (oflags & OPT_MAPROOT)
+ oflagsall &= ~OPT_MAPALL;
+ else if (oflags & OPT_MAPALL)
+ oflagsall &= ~OPT_MAPROOT;
+ else if (oflags & OPT_RO)
+ oflagsall &= ~OPT_RW;
+ else if (oflags & OPT_RW)
+ oflagsall &= ~OPT_RO;
+ oflagsall |= oflags;
+
+ if (!comma) {
+ /* Last option in ...,...,... or single option. */
+ *beg0 = select_field(end0);
+ break;
+ }
+
+ /* ...,... */
+ opt = end + 1;
+ }
+
+ return (1);
+}
+
+/*
+ * Parse a host.
+ */
+static int
+parse_host(const char *host)
+{
+ static struct addrinfo hints = {
+ .ai_flags = 0,
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = IPPROTO_UDP,
+ .ai_addrlen = 0,
+ .ai_addr = NULL,
+ .ai_canonname = NULL,
+ .ai_next = NULL
+ };
+
+ const struct addrinfo *ai;
+ struct addrinfo *ai0;
+ struct addr_spec *as;
+ struct addr4_spec *as4;
+ struct addr6_spec *as6;
+ int error;
+
+ if (nospec_check() < 0)
+ return (-1);
+
+ error = getaddrinfo(host, (char *)NULL, &hints, &ai0);
+ if (error != 0) {
+ if (error == EAI_NONAME)
+ logconfx("unknown host or cannot interpret \"%s\" "
+ "hostname", host);
+ else
+ logconfx("parse_host: getaddrinfo(%s): %s", host,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ error = -1;
+
+ for (ai = ai0; ai != NULL; ai = ai->ai_next) {
+ switch (ai->ai_family) {
+ case AF_INET:
+ addr_spec_family = AF_INET;
+ as = Malloc(ADDR_SPEC_IPV4_SIZE);
+ if (as == NULL) {
+ logconfe("parse_opt_host: malloc");
+ goto done;
+ }
+ as4 = ADDR4_SPEC_PTR(as);
+ as4->addr.s_addr = ((const struct sockaddr_in *)
+ ai->ai_addr)->sin_addr.s_addr;
+ as4->mask.s_addr = UINT32_MAX;
+ break;
+ case AF_INET6:
+ addr_spec_family = AF_INET6;
+ as = Malloc(ADDR_SPEC_IPV6_SIZE);
+ if (as == NULL) {
+ logconfe("parse_opt_host: malloc");
+ goto done;
+ }
+ as6 = ADDR6_SPEC_PTR(as);
+ as6->addr = ((const struct sockaddr_in6 *)
+ ai->ai_addr)->sin6_addr;
+ make_mask(as6->mask.s6_addr, IPV6_ADDR_LEN,
+ IPV6_ADDR_LEN * 8);
+ break;
+ default:
+ /* Unsupported address family, skip it. */
+ continue;
+ }
+ as->maskbits = 0;
+ addr_spec = as;
+ if (add_addr_exp(0) < 0)
+ goto done;
+ }
+
+ /* Remember that this line has at least one host. */
+ oflagsall |= OPT_HOST;
+ error = 0;
+done:
+ freeaddrinfo(ai0);
+ return (error);
+}
+
+/*
+ * Parse an argument of the -host option.
+ */
+static int
+parse_opt_host(char *arg)
+{
+ if (*arg == '!') {
+ ++arg;
+ opt_deny = 1;
+ } else
+ opt_deny = 0;
+ return (parse_host(arg));
+}
+
+/*
+ * Parse all hosts specifications. At first step try to interpret
+ * each host specification as netgroup(5) name, then try to interpret
+ * it as a host domain name or host address.
+ */
+static int
+parse_hosts(char **beg0, char **end0)
+{
+ char *beg, *end, *host, *user, *domain;
+ int parsed;
+
+ end = *beg0;
+
+ for (;;) {
+ /* Get new hostname. */
+ beg = select_field(&end);
+
+ /* Check for EOL. */
+ if (*beg == '\0')
+ break;
+
+ /* Check for option. */
+ if (*beg == '-')
+ break;
+
+ if (*beg == '!') {
+ ++beg;
+ opt_deny = 1;
+ } else
+ opt_deny = 0;
+
+ /* Try to interpret as netgroup(5). */
+ setnetgrent(beg);
+ for (parsed = 0;;) {
+ if (getnetgrent(&host, &user, &domain) == 0)
+ break;
+ if (host != NULL) {
+ if (parse_host(host) < 0)
+ return (-1);
+ parsed = 1;
+ }
+ }
+ endnetgrent();
+
+ if (!parsed) {
+ /* Try to interpret as a host. */
+ if (parse_host(beg) < 0)
+ return (-1);
+ }
+ saw_addr_spec = 1;
+ }
+
+ *beg0 = beg;
+ *end0 = end;
+
+ return (0);
+}
+
+/*
+ * Parse the beginning of a command from the -c option's argument.
+ */
+static int
+parse_cmd(char **beg0, char *end)
+{
+ char *beg, *ptr;
+ uint32_t flag;
+
+ beg = *beg0;
+ if (*beg == '\0') {
+ logconfx("no command at the beginning of a string");
+ return (-1);
+ }
+ ptr = strchr(beg, ' ');
+ if (ptr == NULL)
+ ptr = strchr(beg, '\t');
+ if (ptr != NULL) {
+ *ptr = '\0';
+ ptr = skip_spaces(ptr + 1);
+ }
+ if (ptr == NULL)
+ ptr = end;
+ if (strcmp(beg, "add") == 0)
+ flag = CMD_OPT_ADD;
+ else if (strcmp(beg, "update") == 0)
+ flag = CMD_OPT_UPDATE;
+ else if (strcmp(beg, "delete") == 0)
+ flag = CMD_OPT_DELETE;
+ else if (strcmp(beg, "flush") == 0)
+ flag = CMD_OPT_FLUSH;
+ else if (strcmp(beg, "file") == 0) {
+ logconfx("nested command \"file\" is not allowed");
+ return (-1);
+ } else if (strcmp(beg, "reload") == 0) {
+ logconfx("command \"reload\" cannot be used with other "
+ "commands");
+ return (-1);
+ } else {
+ logconfx("unknown command \"%s\"", beg);
+ return (-1);
+ }
+ if (*ptr == '\0') {
+ logconfx("command \"%s\" requires an argument", beg);
+ return (-1);
+ }
+ switch (flag) {
+ case CMD_OPT_ADD:
+ case CMD_OPT_UPDATE:
+ case CMD_OPT_DELETE:
+ if (strncmp(ptr, "-f", sizeof("-f") - 1) == 0) {
+ if (file_is_parsed) {
+ logconfx("command \"%s -f\" cannot be used "
+ "in file with commands", beg);
+ return (-1);
+ }
+ exp_file_name = skip_spaces(ptr + sizeof("-f") - 1);
+ ocmd = flag;
+ ocmd_set = 1;
+ return (0);
+ }
+ break;
+ }
+ oflagsall = flag;
+ *beg0 = ptr;
+ return (0);
+}
+
+/*
+ * Parse one line.
+ */
+static int
+parse_line(void)
+{
+ struct fs_exp *fe;
+ char *beg, *end;
+ int rv;
+
+ beg = skip_spaces(linebuf);
+
+ /* Check for comment. */
+ if (*beg == '#')
+ return (0);
+
+ /* Check for empty line. */
+ if (*beg == '\0')
+ return (0);
+
+ /* Remove a comment from this line. */
+ for (end = beg; *end != '\0'; ++end)
+ if (*end == '#') {
+ *end = '\0';
+ break;
+ }
+
+ /* Initialize settings for a new line. */
+ oflags = oflagsall = 0;
+ opt_network = saw_addr_spec = 0;
+ fs_exp = NULL;
+ addr_spec = NULL;
+ cred_exp = NULL;
+ secflavors = NULL;
+
+ /*
+ * If -c commands are parsed and we do not parse a file with
+ * export specifications, then parse a command name first.
+ */
+ if (exp_cmd_num > 0 && !ocmd_set) {
+ if (parse_cmd(&beg, end) < 0)
+ return (-1);
+ if (ocmd_set)
+ return (0);
+ }
+
+ if (strncmp(beg, "options:", sizeof("options:") - 1) == 0) {
+ /* Global options line. */
+ if (exp_cmd_num > 0) {
+ logconfx("global options cannot be used with "
+ "commands");
+ return (-1);
+ }
+ if (!TAILQ_EMPTY(fs_exp_list)) {
+ logconfx("global options can be used before all "
+ "other settings only");
+ return (-1);
+ }
+ beg = skip_spaces(beg + sizeof("options:") - 1);
+ end = beg;
+ select_field(&end);
+ /* Parse options. */
+ for (; *beg != '\0';) {
+ do {
+ rv = parse_opts(1, &beg, &end);
+ if (rv < 0)
+ return (-1);
+ } while (rv != 0);
+ }
+ goflags = oflagsall;
+ if (secflavors != NULL)
+ gsecflavors = secflavors;
+ return (0);
+ }
+
+ /* Parse a directory. */
+ if (parse_dir(&beg, &end) < 0)
+ return (-1);
+ fe = fs_exp;
+
+ if (exp_cmd_num > 0) {
+ switch (_CMD_GET(oflagsall)) {
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ if (_CMD_OPT(fe->oflags) == CMD_OPT_FLUSH) {
+ logconfx("commands \"delete\" and \"update\" "
+ "cannot be used after command \"flush\"");
+ return (-1);
+ }
+ break;
+ case CMD_FLUSH:
+ if (*beg != '\0') {
+ logconfx("command \"flush\" requires only "
+ "a directory name");
+ return (-1);
+ }
+ free_addr_exp_list(&fe->ae4_list);
+ TAILQ_INIT(&fe->ae4_list);
+ free_addr_exp_list(&fe->ae6_list);
+ TAILQ_INIT(&fe->ae6_list);
+ if (fe->ae_def != NULL) {
+ fe->ae_def->cred_exp->ref_count--;
+ fe->ae_def->secflavors->ref_count--;
+ fe->ae_def = NULL;
+ }
+ fe->oflags |= CMD_OPT_FLUSH;
+ fe->nspec = 0;
+ return (0);
+ }
+ }
+
+ for (; *beg != '\0';) {
+ /* Parse options. */
+ do {
+ rv = parse_opts(0, &beg, &end);
+ if (rv < 0)
+ goto failed;
+ } while (rv != 0);
+
+ /* Finish work with -network option. */
+ if (add_addr_exp(1) < 0)
+ goto failed;
+
+ /*
+ * After parse_dir() or parse_opts() beg points to
+ * nul-terminated string, if *end is not equal to '\0',
+ * then a string beg points to is not a last field.
+ */
+ if (*end != '\0')
+ *(end - 1) = ' ';
+
+ /* Parse hosts specifications. */
+ if (parse_hosts(&beg, &end) < 0)
+ goto failed;
+ }
+
+ if (!(oflagsall & (OPT_HOST|OPT_NETWORK|OPT_NOSPEC))) {
+ struct addr_exp *ae;
+
+ /* Check for duplicated default export specification. */
+ if (fe->ae_def != NULL) {
+ logconfx("default export specification already was "
+ "specified for %s", fe->path);
+ goto failed;
+ }
+ ae = Malloc(sizeof(*ae));
+ if (ae == NULL) {
+ logconfe("parse_line: malloc");
+ goto failed;
+ }
+ ae->addr_spec = NULL;
+ setopts_addr_exp(ae, 0);
+ fe->ae_def = ae;
+ fe->nspec++;
+ } else if (!saw_addr_spec) {
+ if (((oflags & AE_OPTS_ALLFLAGS) ||
+ (oflags & (OPT_NO_MNT_DMP|OPT_NO_MNT_EXP))) &&
+ !(oflags & OPT_NOSPEC))
+ logconfw("some options specified at the end of this "
+ "line do not have effect on configuration");
+ }
+
+ fe->oflags |= oflagsall &
+ (FE_OPTS_ALLFLAGS & ~(OPT_NO_MNT_DMP|OPT_NO_MNT_EXP));
+ if (oflagsall & OPT_NOSPEC)
+ fe->oflags |= oflagsall & (OPT_NO_MNT_DMP|OPT_NO_MNT_EXP);
+ return (0);
+
+failed:
+ free(addr_spec);
+ if (syserr_flag || exp_cmd_num > 0)
+ return (-1);
+ if (fs_exp != NULL)
+ fs_exp->oflags |= OPT_ERRCONFIG;
+ return (0);
+}
+
+/*
+ * Forget about configuration for the given address family.
+ */
+static void
+forget_conf(const u_int family)
+{
+ const struct fs_exp_list *fe_list;
+ struct addr_exp_list *ae_list;
+ struct fs_exp *fe;
+
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ ae_list = family == AF_INET ? &fe->ae4_list : &fe->ae6_list;
+ free_addr_exp_list(ae_list);
+ TAILQ_INIT(ae_list);
+ }
+}
+
+/*
+ * Read file's content line-by-line and parse each line.
+ */
+static int
+parse_file(FILE *fp, const char *name)
+{
+ int rv;
+
+ if (test_conf)
+ logmsgx("configure: reading file %s", name);
+ lineno = 0;
+ exp_file_name = name;
+ file_is_parsed = 1;
+ for (;;) {
+ rv = read_line(fp);
+ if (rv == 1) {
+ if (parse_line() < 0)
+ return (-1);
+ } else
+ break;
+ }
+ return (rv);
+}
+
+/*
+ * This function parse single commands that cannot be used with another
+ * commands.
+ */
+static int
+parse_single_cmd(const char *str)
+{
+ u_int cmd;
+
+ if (strcmp(str, "reload") == 0)
+ cmd = CTL_CMD_RELOAD;
+ else if (strcmp(str, "flush") == 0)
+ cmd = CTL_CMD_FLUSH;
+ else
+ return (0);
+ if (exp_cmd_num != 1) {
+ exp_file_name = "-c";
+ log_warn("command \"%s\" cannot be used with other commands",
+ str);
+ return (-1);
+ }
+ ctl_cmd_hdr.command = cmd;
+ return (1);
+}
+
+/*
+ * Parse -c options commands.
+ * Return:
+ * 0 -- all commands were parsed successfully;
+ * -1 -- some error occurred (syserr_flag can be set);
+ * -2 -- some system error occurred (the caller must set syserr_flag).
+ */
+static int
+parse_exp_cmds(void)
+{
+ struct stat statbuf;
+ const char *fname;
+ FILE *fp;
+ char *ptr, *linebuf_bak;
+ u_int i;
+ int rv;
+
+ if (test_conf)
+ printf("configure: parsing -c commands\n");
+
+ ctl_cmd_hdr.version = CTL_API_VERSION;
+
+ /* Check single command. */
+ switch (parse_single_cmd(skip_spaces(exp_cmd_arr[0]))) {
+ case 0:
+ break;
+ case 1:
+ return (0);
+ default:
+ return (-1);
+ }
+
+ /* Parse other commands. */
+ for (i = 0; i < exp_cmd_num; ++i) {
+ ptr = skip_spaces(exp_cmd_arr[i]);
+ ocmd = 0;
+ ocmd_set = 0;
+ if (strncmp(ptr, "file", sizeof("file") - 1) == 0)
+ fname = skip_spaces(ptr + sizeof("file") - 1);
+ else {
+ exp_file_name = "-c";
+ lineno = i + 1;
+ linebuf_bak = linebuf;
+ linebuf = ptr;
+ file_is_parsed = 0;
+ rv = parse_line();
+ linebuf = linebuf_bak;
+ if (rv < 0)
+ return (-1);
+ if (!ocmd_set)
+ continue;
+ /* Apply command to export specifications from file. */
+ fname = exp_file_name;
+ }
+ fp = fopen(fname, "r");
+ if (fp == NULL) {
+ (void)crit_fs_err(errno);
+ logmsg("configure: fopen(%s, \"r\")", fname);
+ return (-1);
+ }
+ if (Fstat(fileno(fp), &statbuf) < 0) {
+ logmsg("configure: stat(%s)", fname);
+ goto failed;
+ }
+ if (!S_ISREG(statbuf.st_mode)) {
+ logmsgx("configure: file %s is not a regular "
+ "file", fname);
+ goto failed;
+ }
+ if (parse_file(fp, fname) < 0)
+ goto failed;
+ if (Fclose(fp) != 0) {
+ logmsg("configure: fclose(%s)", fname);
+ return (-1);
+ }
+ }
+ ctl_cmd_hdr.command = CTL_CMD_EXPORT;
+ return (0);
+
+failed:
+ if (Fclose(fp) != 0)
+ logmsg("configure: fclose(%s)", fname);
+ return (-1);
+}
+
+/*
+ * Parse all configuration files.
+ * Return:
+ * 0 -- all files were parsed successfully;
+ * -1 -- some error occurred (syserr_flag can be set);
+ * -2 -- some system error occurred (the caller must set syserr_flag).
+ */
+static int
+parse_exp_files(void)
+{
+ struct stat statbuf;
+ const struct dirent *dp;
+ const char *fname, *dname;
+ FILE *fp;
+ DIR *dirp;
+ char *name;
+ int i, rv;
+
+ dirp = NULL;
+ for (i = 0; i < exp_file_num;) {
+ if (dirp == NULL) {
+ /* Not reading a directory. */
+ if (exp_file_arr[i].isfile) {
+ /* A new file. */
+ fname = exp_file_arr[i].name;
+ } else {
+ /* A new directory. */
+ dname = exp_file_arr[i].name;
+ dirp = opendir(dname);
+ if (dirp == NULL) {
+ if (crit_fs_err(errno)) {
+ logmsg("configure: "
+ "opendir(%s)", dname);
+ return (-1);
+ }
+ logmsgw("configure: skipping "
+ "directory %s: %s", dname,
+ strerror(errno));
+ ++i;
+ continue;
+ }
+ }
+ }
+ if (dirp != NULL) {
+ /* Reading a directory. */
+ errno = 0;
+ dp = readdir(dirp);
+ if (dp == NULL) {
+ if (errno != 0) {
+ logmsg("configure: readdir(%s)",
+ dname);
+ return (-2);
+ }
+ if (closedir(dirp) < 0) {
+ logmsg("configure: closedir(%s)",
+ dname);
+ return (-2);
+ }
+ dirp = NULL;
+ ++i;
+ continue;
+ }
+ /* Ignore "." and ".." directories. */
+ if (dp->d_name[0] == '.')
+ switch (dp->d_name[1]) {
+ case '\0':
+ continue;
+ case '.':
+ if (dp->d_name[2] == '\0')
+ continue;
+ break;
+ }
+ if (Asprintf(&name, "%s%s", dname, dp->d_name) < 0) {
+ logmsg("configure: asprintf");
+ return (-1);
+ }
+ fname = name;
+ }
+ fp = fopen(fname, "r");
+ if (fp == NULL) {
+ rv = crit_fs_err(errno);
+ if (dirp != NULL && !rv) {
+ logmsgw("configure: skipping file %s: %s",
+ fname, strerror(errno));
+ free(name);
+ continue;
+ }
+ logmsg("configure: fopen(%s, \"r\")", fname);
+ return (-1);
+ }
+ if (Fstat(fileno(fp), &statbuf) < 0) {
+ logmsg("configure: stat(%s)", fname);
+ goto failed;
+ }
+ if (!S_ISREG(statbuf.st_mode)) {
+ if (dirp != NULL)
+ goto next;
+ logmsgx("configure: %s is not a regular file", fname);
+ goto failed;
+ }
+ if (parse_file(fp, fname) < 0)
+ goto failed;
+next: if (Fclose(fp) != 0) {
+ logmsg("configure: fclose(%s)", fname);
+ return (-1);
+ }
+ if (dirp != NULL)
+ free(name);
+ else
+ ++i;
+ }
+ return (0);
+
+failed:
+ if (Fclose(fp) != 0)
+ logmsg("configure: fclose(%s)", fname);
+ return (-1);
+}
+
+/*
+ * Parse configuration.
+ */
+int
+configure(const int reconf)
+{
+ int rv;
+
+ /* Allocate initial line buffer. */
+ linebuf = Malloc(LINEBUF_SIZE);
+ if (linebuf == NULL) {
+ logmsg("configure: malloc(%u)", LINEBUF_SIZE);
+ return (-1);
+ }
+ linesize = LINEBUF_SIZE;
+
+ if (!reconf) {
+ /* First call. */
+ LIST_INSERT_HEAD(&cred_exp_list, &cred_exp_anon_def, link);
+ LIST_INSERT_HEAD(&secflavors_list, &secflavors_def, link);
+ }
+
+ /* Save old configuration of fs_exp_list. */
+ fs_exp_list_prev = fs_exp_list;
+ fs_exp_list = fs_exp_list == &fs_exp_list1 ?
+ &fs_exp_list2 : &fs_exp_list1;
+ TAILQ_INIT(fs_exp_list);
+
+ opt_public = 0;
+ goflags = 0;
+ free(index_webnfs);
+ index_webnfs = NULL;
+ setnetent(1);
+
+ log_err = logconfe;
+ log_warn = logconfw;
+ if (exp_cmd_num == 0) {
+ if (test_conf)
+ log_to_console = 1;
+ rv = parse_exp_files();
+ } else {
+ log_to_console = 1;
+ rv = parse_exp_cmds();
+ }
+
+ endnetent();
+ free(linebuf);
+
+ if (rv < 0) {
+ if (rv == -2)
+ syserr_flag = 1;
+ logmsgx("parsing failed: %s", syserr_flag ?
+ "some system error occurred" : "wrong configuration");
+ free_fs_exp_list(fs_exp_list);
+ return (-1);
+ }
+
+ if (exp_cmd_num == 0) {
+ no_mntproc_dump = (goflags & OPT_NO_MNT_DMP) ? 1 : 0;
+ no_mntproc_export = (goflags & OPT_NO_MNT_EXP) ? 1 : 0;
+ if (!test_conf) {
+ if (!have_ipv4)
+ forget_conf(AF_INET);
+ if (!have_ipv6)
+ forget_conf(AF_INET6);
+ free_wrong_conf();
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Release memory used by the current fs_exp_list and check
+ * that structures with reference counters were freed.
+ */
+void
+unconfigure(void)
+{
+#ifdef DEBUG_MEMORY_LEAK
+ free_fs_exp_list(fs_exp_list);
+ free_unref_conf();
+ if (!LIST_EMPTY(&addr4_spec_list))
+ syslog(LOG_ERR, "addr4_spec_list is not empty");
+ if (!LIST_EMPTY(&addr6_spec_list))
+ syslog(LOG_ERR, "addr6_spec_list is not empty");
+ if (LIST_NEXT(&secflavors_def, link) != NULL)
+ syslog(LOG_ERR, "secflavors_list is not empty");
+ if (LIST_NEXT(&cred_exp_anon_def, link) != NULL)
+ syslog(LOG_ERR, "cred_exp_list is not empty");
+#endif
+}
+
+/*
+ * Output a string, representing non-printable characters,
+ * white spaces and backslashes as octal numbers.
+ */
+static void
+print_string(const char *str)
+{
+ for (; *str != '\0'; ++str)
+ if (!isprint((u_char)*str) || *str == ' ' || *str == '\\')
+ printf("\\%03o", (u_char)*str);
+ else
+ printf("%c", *str);
+}
+
+/*
+ * Output security flavors.
+ */
+static void
+print_secflavors(const struct secflavors *sf)
+{
+ const char *str;
+ u_int i, n;
+
+ printf(" -sec ");
+ n = sf->nsec;
+ for (i = 0; i < n; ++i) {
+ switch (sf->sec[i]) {
+ case AUTH_SYS:
+ str = "sys";
+ break;
+ case RPCSEC_GSS_KRB5:
+ str = "krb5";
+ break;
+ case RPCSEC_GSS_KRB5I:
+ str = "krb5i";
+ break;
+ default: /* RPCSEC_GSS_KRB5P */
+ str = "krb5p";
+ }
+ printf("%s", str);
+ if (i < n - 1)
+ printf(":");
+ }
+}
+
+/*
+ * Output credentials specified in ce.
+ * Special case is -2:-2 credentials.
+ */
+static void
+print_cred(const uint32_t flags, const struct cred_exp *ce)
+{
+ if (flags & OPT_MAPALL)
+ printf(" -mapall");
+ if (flags & OPT_MAPROOT)
+ printf(" -maproot");
+ if (ce == &cred_exp_anon_def)
+ printf("=-2:-2");
+ else {
+ printf(" %lu", (u_long)ce->uid);
+ if (ce->ngids > 0) {
+ u_int i, ngids;
+
+ ngids = ce->ngids;
+ for (i = 0; i < ngids; ++i)
+ printf(":%lu", (u_long)ce->gids[i]);
+ } else
+ printf(":");
+ }
+}
+
+/*
+ * Output everything related to the given addr_exp.
+ */
+static void
+print_addr_exp(const u_int family, const struct addr_exp *ae)
+{
+ int show_opts;
+ uint32_t flags;
+
+ printf(" ");
+ flags = ae->oflags;
+ show_opts = 1;
+ if (exp_cmd_num > 0) {
+ const char *str;
+
+ switch (_CMD_GET(flags)) {
+ case CMD_ADD:
+ str = " add";
+ break;
+ case CMD_UPDATE:
+ str = "update";
+ break;
+ default: /* CMD_DELETE */
+ str = "delete";
+ show_opts = 0;
+ }
+ printf("-c %s ", str);
+ }
+ if (show_opts) {
+ printf("-r%c", (flags & OPT_RO) ? 'o' : 'w');
+ print_secflavors(ae->secflavors);
+ print_cred(flags, ae->cred_exp);
+ if (flags & OPT_NO_MNT_DMP)
+ printf(" -no_mntproc_dump");
+ if (flags & OPT_NO_MNT_EXP)
+ printf(" -no_mntproc_export");
+ printf(" ");
+ }
+ if (family != AF_UNSPEC)
+ printf("-%s %s%s",
+ (flags & OPT_NETWORK) ? "network" : "host",
+ (flags & OPT_DENY) ? "!" : "",
+ addr_spec_str(family, ae->addr_spec));
+}
+
+/*
+ * Output all addr_exp structures from the given list.
+ */
+static void
+print_addr_exp_list(const u_int family, const struct addr_exp_list *ae_list)
+{
+ const struct addr_exp *ae;
+
+ TAILQ_FOREACH(ae, ae_list, link) {
+ print_addr_exp(family, ae);
+ printf("\n");
+ }
+}
+
+/*
+ * Output all configuration in readable form.
+ */
+void
+show_conf(void)
+{
+ const struct fs_exp_list *fe_list;
+ struct statfs statfsbuf;
+ const struct fs_exp *fe;
+ uint32_t flags;
+
+ flags = goflags;
+ if ((flags & ~OPT_SEC) != 0) {
+ printf("Global options:\n");
+ if (flags & OPT_NO_MNT_DMP)
+ printf(" -no_mntproc_dump\n");
+ if (flags & OPT_NO_MNT_EXP)
+ printf(" -no_mntproc_export\n");
+ }
+
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ printf("\nDirectory ");
+ print_string(fe->path);
+ if (statfs(fe->path, &statfsbuf) == 0)
+ if (strcmp(statfsbuf.f_mntonname, fe->path) == 0)
+ printf(" (mount point)");
+ printf("\n");
+ flags = fe->oflags;
+ if (flags & OPT_ERRCONFIG) {
+ printf(" Wrong configuration\n");
+ continue;
+ }
+ if (flags & FE_OPTS_ALLFLAGS) {
+ printf(" File system options:\n");
+ if (flags & OPT_QUIET)
+ printf(" -quiet\n");
+ if (flags & OPT_NO_MNT_DMP)
+ printf(" -no_mntproc_dump\n");
+ if (flags & OPT_NO_MNT_EXP)
+ printf(" -no_mntproc_export\n");
+ if (flags & OPT_PUBLIC) {
+ printf(" -%s", (flags & OPT_WEBNFS) ?
+ "webnfs" : "public");
+ if (flags & OPT_INDEX) {
+ printf(" -index ");
+ print_string(index_webnfs);
+ }
+ printf("\n");
+ }
+ }
+ if (exp_cmd_num == 0)
+ printf(" Export specifications:\n");
+ else
+ printf(" Commands:\n");
+ if (exp_cmd_num > 0)
+ if (_CMD_OPT(flags) == CMD_OPT_FLUSH)
+ printf(" -c flush\n");
+ print_addr_exp_list(AF_INET, &fe->ae4_list);
+ print_addr_exp_list(AF_INET6, &fe->ae6_list);
+ if (fe->ae_def != NULL) {
+ print_addr_exp(AF_UNSPEC, fe->ae_def);
+ printf("(default)\n");
+ }
+ }
+}
+
+/*
+ * Convert one addr_exp structure to ctl_addr_exp structure and
+ * put it to the buffer.
+ */
+static void
+ctl_addr_exp_pack(char **ptr0, const u_int family, const struct addr_exp *ae)
+{
+ struct ctl_addr_exp cae;
+ u_int i;
+
+ cae.family = family;
+ if (ae->addr_spec != NULL) {
+ if (family == AF_INET) {
+ cae.addr_spec.as4.addr =
+ ADDR4_SPEC_PTR(ae->addr_spec)->addr;
+ cae.addr_spec.as4.maskbits= ae->addr_spec->maskbits;
+ } else {
+ cae.addr_spec.as6.addr =
+ ADDR6_SPEC_PTR(ae->addr_spec)->addr;
+ cae.addr_spec.as6.maskbits = ae->addr_spec->maskbits;
+ }
+ }
+ cae.uid = ae->cred_exp->uid;
+ cae.ngids = ae->cred_exp->ngids;
+ for (i = 0; i < cae.ngids; ++i)
+ cae.gids[i] = ae->cred_exp->gids[i];
+ cae.nsec = ae->secflavors->nsec;
+ for (i = 0; i < cae.nsec; ++i)
+ cae.sec[i] = ae->secflavors->sec[i];
+ cae.oflags = ae->oflags;
+ memcpy(*ptr0, &cae, sizeof(cae));
+ *ptr0 += sizeof(cae);
+}
+
+/*
+ * Convert all addr_exp structures from the given list to ctl_addr_exp
+ * structures and put them to the buffer.
+ */
+static void
+ctl_addr_exp_list_pack(char **ptr0, const u_int family,
+ const struct addr_exp_list *ae_list)
+{
+ const struct addr_exp *ae;
+
+ TAILQ_FOREACH(ae, ae_list, link)
+ ctl_addr_exp_pack(ptr0, family, ae);
+}
+
+/*
+ * Pack CTL_CMD_EXPORT command data, fs_exp_list contains all commands.
+ */
+int
+ctl_export_pack(void)
+{
+ struct ctl_fs_exp cfe;
+ const struct fs_exp_list *fe_list;
+ const struct fs_exp *fe;
+ char *ptr;
+ size_t size;
+ u_int nspec;
+
+ size = 0;
+ nspec = 0;
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ size += sizeof(cfe) + fe->path_len;
+ nspec += fe->nspec;
+ }
+ ctl_cmd_hdr.size = size += nspec * sizeof(struct ctl_addr_exp);
+ ctl_cmd_buf = ptr = malloc(size);
+ if (ptr == NULL) {
+ warn("ctl_export_pack: malloc");
+ return (-1);
+ }
+ TAILQ_FOREACH(fe, fe_list, link) {
+ cfe.path_len = fe->path_len;
+ cfe.oflags = fe->oflags;
+ cfe.nspec = fe->nspec;
+ memcpy(ptr, &cfe, sizeof(cfe));
+ ptr += sizeof(cfe);
+ memcpy(ptr, fe->path, fe->path_len);
+ ptr += fe->path_len;
+ if (fe->ae_def != NULL)
+ ctl_addr_exp_pack(&ptr, AF_UNSPEC, fe->ae_def);
+ ctl_addr_exp_list_pack(&ptr, AF_INET, &fe->ae4_list);
+ ctl_addr_exp_list_pack(&ptr, AF_INET6, &fe->ae6_list);
+ }
+ if ((char *)ctl_cmd_buf + size != ptr) {
+ warnx("ctl_export_pack: size mismatch: %p vs %p",
+ (char *)ctl_cmd_buf + size, ptr);
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Convert one ctl_addr_exp structure to addr_exp structure and
+ * add it to fs_exp structure.
+ */
+static int
+ctl_addr_exp_unpack(const char *ptr, struct fs_exp *fe)
+{
+ struct ctl_addr_exp cae;
+ struct addr_spec *as;
+ struct addr_exp *ae;
+ uint32_t cmd, flags;
+
+ memcpy(&cae, ptr, sizeof(cae));
+
+ flags = cae.oflags;
+ if (flags & (OPT_HOST|OPT_NETWORK)) {
+ /* -host or -network, should have correct family. */
+ if ((flags & (OPT_HOST|OPT_NETWORK)) == (OPT_HOST|OPT_NETWORK))
+ return (-2);
+ if (cae.family != AF_INET && cae.family != AF_INET6)
+ return (-2);
+ } else {
+ /* Check duplicated update for the default export. */
+ if (fe->ae_def != NULL)
+ return (-2);
+ }
+
+ cmd = _CMD_GET(flags);
+
+ /* Flushed file system cannot have "delete" or "update" commands. */
+ if ((cmd == CMD_DELETE || cmd == CMD_UPDATE) &&
+ (_CMD_OPT(fe->oflags) == CMD_OPT_FLUSH))
+ return (-2);
+
+ if (cmd == CMD_DELETE) {
+ /* "delete" command can have only -host or -network flag. */
+ if (flags & ~(CMD_OPT_DELETE|OPT_HOST|OPT_NETWORK))
+ return (-2);
+ } else if (cmd == CMD_FLUSH) {
+ /* "flush" command cannot be used for address specification. */
+ return (-2);
+ } else {
+ u_int i, j;
+
+ /* Should have -mapall or -maproot. */
+ if (!(flags & (OPT_MAPALL|OPT_MAPROOT)) ||
+ (flags & (OPT_MAPALL|OPT_MAPROOT)) ==
+ (OPT_MAPALL|OPT_MAPROOT))
+ return (-2);
+ /* Should have -rw or -ro. */
+ if (!(flags & (OPT_RO|OPT_RW)) ||
+ (flags & (OPT_RO|OPT_RW)) == (OPT_RO|OPT_RW))
+ return (-2);
+ /* Validate credentials and security flavors. */
+ if (cae.ngids > NGROUPS || cae.nsec > NFSE_NSECFLAV ||
+ cae.nsec == 0)
+ return (-2);
+ for (i = 0; i < cae.ngids; ++i)
+ for (j = i + 1; j < cae.ngids; ++j)
+ if (cae.gids[i] == cae.gids[j])
+ return (-2);
+ for (i = 0; i < cae.nsec; ++i)
+ switch (cae.sec[i]) {
+ case AUTH_SYS:
+ case RPCSEC_GSS_KRB5:
+ case RPCSEC_GSS_KRB5I:
+ case RPCSEC_GSS_KRB5P:
+ for (j = i + 1; j < cae.nsec; ++j)
+ if (cae.sec[i] == cae.sec[j])
+ return (-2);
+ break;
+ default:
+ return (-2);
+ }
+ }
+
+ ae = malloc(sizeof(*ae));
+ if (ae == NULL) {
+ syslog(LOG_ERR, "ctl_addr_exp_unpack: malloc: %m");
+ return (-1);
+ }
+
+ if (flags & (OPT_HOST|OPT_NETWORK)) {
+ const struct addr_exp *ae0;
+ struct addr_exp_list *ae_list;
+ uint8_t *addr, *mask;
+ u_int len, maskbits;
+
+ /* Command for -host or -network. */
+ as = malloc(cae.family == AF_INET ?
+ ADDR_SPEC_IPV4_SIZE : ADDR_SPEC_IPV6_SIZE);
+ if (as == NULL) {
+ syslog(LOG_ERR, "ctl_addr_exp_unpack: malloc");
+ free(ae);
+ return (-1);
+ }
+ if (cae.family == AF_INET) {
+ struct addr4_spec *as4;
+
+ len = IPV4_ADDR_LEN;
+ as4 = ADDR4_SPEC_PTR(as);
+ as4->addr = cae.addr_spec.as4.addr;
+ addr = (uint8_t *)&as4->addr.s_addr;
+ mask = (uint8_t *)&as4->mask.s_addr;
+ maskbits = cae.addr_spec.as4.maskbits;
+ ae_list = &fe->ae4_list;
+ } else {
+ struct addr6_spec *as6;
+
+ len = IPV6_ADDR_LEN;
+ as6 = ADDR6_SPEC_PTR(as);
+ as6->addr = cae.addr_spec.as6.addr;
+ addr = as6->addr.s6_addr;
+ mask = as6->mask.s6_addr;
+ maskbits = cae.addr_spec.as6.maskbits;
+ ae_list = &fe->ae6_list;
+ }
+ if (!(flags & OPT_NETWORK)) {
+ if (maskbits != 0)
+ goto failed;
+ maskbits = cae.family == AF_INET ?
+ (IPV4_ADDR_LEN * 8) : (IPV6_ADDR_LEN * 8);
+ as->maskbits = 0;
+ } else
+ as->maskbits = maskbits;
+ if (make_mask(mask, len, maskbits) < 0)
+ goto failed;
+ as = find_addr_spec(cae.family, (flags & OPT_NETWORK), as);
+ TAILQ_FOREACH(ae0, ae_list, link)
+ if (ae0->addr_spec == as)
+ goto failed;
+ TAILQ_INSERT_TAIL(ae_list, ae, link);
+ ae->addr_spec = as;
+ as->ref_count++;
+ } else {
+ /* Command for default export. */
+ ae->addr_spec = NULL;
+ fe->ae_def = ae;
+ }
+
+ ae->cred_exp = NULL;
+ ae->secflavors = NULL;
+ ae->oflags = flags;
+ if (cmd != CMD_DELETE) {
+ /* "add" or "update" commands. */
+ ae->cred_exp = find_cred_exp(cae.uid, cae.ngids, cae.gids);
+ if (ae->cred_exp == NULL)
+ return (-1);
+ ae->cred_exp->ref_count++;
+ ae->secflavors = find_secflavors(cae.nsec, cae.sec);
+ if (ae->secflavors == NULL)
+ return (-1);
+ ae->secflavors->ref_count++;
+ }
+
+ return (0);
+
+failed:
+ free(as);
+ free(ae);
+ return (-2);
+}
+
+/*
+ * Unpack CTL_CMD_EXPORT command data and create fs_exp_list.
+ */
+int
+ctl_export_unpack(void)
+{
+ struct ctl_fs_exp cfe;
+ struct fs_exp_list *fe_list;
+ struct fs_exp *fe;
+ char *ptr;
+ size_t size;
+ int rv;
+
+ fe_list = &fs_exp_list_update;
+ TAILQ_INIT(fe_list);
+ size = ctl_cmd_hdr.size;
+ ptr = ctl_cmd_buf;
+ for (; size > 0;) {
+ if (size < sizeof(cfe))
+ return (-2);
+ memcpy(&cfe, ptr, sizeof(cfe));
+ if (cfe.oflags & ~(FE_CMD_OPTS_ALLFLAGS|CMD_OPT_FLUSH))
+ return (-2);
+ if (cfe.path_len == 0 || cfe.path_len > RPCMNT_PATHLEN)
+ return (-2);
+ if ((fe = malloc(sizeof(*fe))) == NULL ||
+ (fe->path = malloc(cfe.path_len + 1)) == NULL) {
+ free(fe);
+ syslog(LOG_ERR, "ctl_export_unpack: malloc: %m");
+ return (-1);
+ }
+ size -= sizeof(cfe);
+ if (size < cfe.path_len)
+ return (-2);
+ ptr += sizeof(cfe);
+ memcpy(fe->path, ptr, cfe.path_len);
+ fe->path[cfe.path_len] = '\0';
+ if (fs_exp_by_path(fe_list, fe->path) != NULL) {
+ free(fe->path);
+ free(fe);
+ return (-2);
+ }
+ init_fs_exp(fe);
+ TAILQ_INSERT_TAIL(fe_list, fe, link);
+ fe->oflags = cfe.oflags;
+ if (strchr(fe->path, '\0') != fe->path + cfe.path_len)
+ return (-2);
+ ptr += cfe.path_len;
+ size -= cfe.path_len;
+ if (size < cfe.nspec * sizeof(struct ctl_addr_exp))
+ return (-2);
+ size -= cfe.nspec * sizeof(struct ctl_addr_exp);
+ fe->nspec = cfe.nspec;
+ for (; cfe.nspec != 0; --cfe.nspec) {
+ rv = ctl_addr_exp_unpack(ptr, fe);
+ if (rv < 0)
+ return (rv);
+ ptr += sizeof(struct ctl_addr_exp);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Pack a command with no data.
+ */
+int
+ctl_nodata_pack(void)
+{
+ ctl_cmd_hdr.size = 0;
+ return (0);
+}
+
+/*
+ * Unpack a command that does not require data.
+ */
+int
+ctl_nodata_unpack(void)
+{
+ if (ctl_cmd_hdr.size != 0)
+ return (-2);
+ return (0);
+}
+
+/*
+ * Apply settings from ae1_list to fe2. This function does not allocate
+ * memory, it can fail only if new settings cannot be applied to existent
+ * configuration.
+ */
+static int
+update_addr_exp_list(const int check, const u_int family,
+ struct addr_exp_list *ae1_list, struct fs_exp *fe2)
+{
+ struct addr_exp_list *ae2_list;
+ struct addr_exp *ae1, *ae2, *ae1_next;
+ uint32_t cmd, flags;
+
+ ae2_list = family == AF_INET ? &fe2->ae4_list : &fe2->ae6_list;
+ flags = (goflags | fe2->oflags) & OPT_NO_MNT_DMP;
+ TAILQ_FOREACH_SAFE(ae1, ae1_list, link, ae1_next) {
+ TAILQ_FOREACH(ae2, ae2_list, link)
+ if (ae1->addr_spec == ae2->addr_spec)
+ break;
+ cmd = _CMD_GET(ae1->oflags);
+ if (!check) {
+ ae1->oflags &= ~_CMD_ALLBITS;
+ ae1->oflags |= flags | CMD_OPT_ADD;
+ }
+ switch (cmd) {
+ case CMD_ADD:
+ if (ae2 != NULL)
+ return (-1);
+ if (!check) {
+ TAILQ_REMOVE(ae1_list, ae1, link);
+ link_addr_exp(ae2_list, ae1);
+ fe2->nspec++;
+ inc_no_mnt_exp(fe2, ae1->oflags);
+ }
+ break;
+ case CMD_UPDATE:
+ if (ae2 == NULL)
+ return (-1);
+ if (!check) {
+ ae2->cred_exp->ref_count--;
+ ae2->cred_exp = ae1->cred_exp;
+ ae1->cred_exp = NULL;
+ ae2->secflavors->ref_count--;
+ ae2->secflavors = ae1->secflavors;
+ ae1->secflavors = NULL;
+ if (!(ae2->oflags & OPT_DENY) &&
+ (ae1->oflags & OPT_DENY))
+ mntlist_recheck(family, fe2);
+ dec_no_mnt_exp(fe2, ae2->oflags);
+ inc_no_mnt_exp(fe2, ae1->oflags);
+ ae2->oflags = ae1->oflags;
+ }
+ break;
+ case CMD_DELETE:
+ if (ae2 == NULL)
+ return (-1);
+ if (!check) {
+ ae2->addr_spec->ref_count--;
+ ae2->cred_exp->ref_count--;
+ ae2->secflavors->ref_count--;
+ TAILQ_REMOVE(ae2_list, ae2, link);
+ dec_no_mnt_exp(fe2, ae2->oflags);
+ free(ae2);
+ fe2->nspec--;
+ mntlist_recheck(family, fe2);
+ }
+ break;
+ }
+ }
+ return (0);
+}
+
+/*
+ * Get data from fe1_list and apply it to the current configuration
+ * (that is given in fs_exp_list). If the check flag is on, then just
+ * try to apply. This function does not allocate memory, it can fail
+ * only if new settings cannot be applied to existent configuration.
+ * Always check new settings first, if everything is correct then load
+ * updates to nfsserver and only then apply the same new settings to
+ * the current configuration.
+ */
+int
+update_conf(const int check)
+{
+ struct addr_exp_list ae4_list, ae6_list;
+ struct fs_exp_list *fe1_list, *fe2_list;
+ struct fs_exp *fe1, *fe2, *fe1_next;
+ struct addr_exp *ae1, *ae2;
+ int rv;
+ uint32_t cmd;
+
+ fe1_list = &fs_exp_list_update;
+ fe2_list = fs_exp_list;
+ TAILQ_FOREACH_SAFE(fe1, fe1_list, link, fe1_next) {
+ fe2 = fs_exp_by_path(fe2_list, fe1->path);
+ if (fe2 != NULL) {
+ /* Update existent file system. */
+ if (fe2->oflags & OPT_EXPORTED) {
+ fe1->oflags |= OPT_EXPORTED;
+ fe1->fsid = fe2->fsid;
+ }
+ /* Inherit existent -quiet option. */
+ fe1->oflags |= fe2->oflags & OPT_QUIET;
+ } else {
+ /* New file system. */
+ fe1->oflags |= OPT_NEWEXPORT;
+ }
+ if (_CMD_OPT(fe1->oflags) == CMD_OPT_FLUSH) {
+ /* Commands were check in ctl_addr_exp_unpack(). */
+ if (fe2 == NULL)
+ return (-2);
+ if (check)
+ continue;
+ if (fe2 != NULL) {
+ /* Flush configuration. */
+ free_fs_exp(fe2_list, fe2);
+ fe2 = NULL;
+ }
+ }
+ if (fe2 == NULL) {
+ /* Flushed or new file system. */
+ if (fe1->nspec == 0 &&
+ !(fe1->oflags & FE_CMD_OPTS_ALLFLAGS))
+ continue;
+ if (!check) {
+ /* Use already allocated fs_exp structure. */
+ TAILQ_REMOVE(fe1_list, fe1, link);
+ link_fs_exp(fe2_list, fe1);
+ fe1->nspec = 0;
+ }
+ fe2 = fe1;
+ }
+ /*
+ * Apply settings (have to use separate lists since
+ * fe1 and fe2 can point to the same data).
+ */
+ if (!check) {
+ /* Inherit new file system options. */
+ fe2->oflags |= fe1->oflags & FE_CMD_OPTS_ALLFLAGS;
+ }
+ TAILQ_INIT(&ae4_list);
+ TAILQ_CONCAT(&ae4_list, &fe1->ae4_list, link);
+ rv = update_addr_exp_list(check, AF_INET, &ae4_list, fe2);
+ TAILQ_CONCAT(&fe1->ae4_list, &ae4_list, link);
+ if (rv < 0)
+ return (-1);
+ TAILQ_INIT(&ae6_list);
+ TAILQ_CONCAT(&ae6_list, &fe1->ae6_list, link);
+ rv = update_addr_exp_list(check, AF_INET6, &ae6_list, fe2);
+ TAILQ_CONCAT(&fe1->ae6_list, &ae6_list, link);
+ if (rv < 0)
+ return (-1);
+ if (fe1->ae_def != NULL) {
+ cmd = _CMD_GET(fe1->ae_def->oflags);
+ if (!check) {
+ fe1->ae_def->oflags &= ~_CMD_ALLBITS;
+ fe1->ae_def->oflags |= CMD_OPT_ADD;
+ }
+ switch (cmd) {
+ case CMD_ADD:
+ if (fe1 != fe2) {
+ if (fe2->ae_def != NULL)
+ return (-1);
+ if (!check) {
+ fe2->ae_def = fe1->ae_def;
+ fe1->ae_def = NULL;
+ }
+ }
+ if (!check) {
+ fe2->nspec++;
+ inc_no_mnt_exp(fe2,
+ fe2->ae_def->oflags);
+ }
+ break;
+ case CMD_UPDATE:
+ if (fe1 == fe2 || fe2->ae_def == NULL)
+ return (-1);
+ if (!check) {
+ ae1 = fe1->ae_def;
+ ae2 = fe2->ae_def;
+ ae2->cred_exp->ref_count--;
+ ae2->cred_exp = ae1->cred_exp;
+ ae1->cred_exp = NULL;
+ ae2->secflavors->ref_count--;
+ ae2->secflavors = ae1->secflavors;
+ ae1->secflavors = NULL;
+ dec_no_mnt_exp(fe2, ae2->oflags);
+ inc_no_mnt_exp(fe2, ae1->oflags);
+ ae2->oflags = ae1->oflags;
+ }
+ break;
+ case CMD_DELETE:
+ if (fe1 == fe2 || fe2->ae_def == NULL)
+ return (-1);
+ if (!check) {
+ ae2 = fe2->ae_def;
+ ae2->cred_exp->ref_count--;
+ ae2->secflavors->ref_count--;
+ dec_no_mnt_exp(fe2, ae2->oflags);
+ free(ae2);
+ fe2->ae_def = NULL;
+ fe2->nspec--;
+ mntlist_recheck(AF_INET, fe2);
+ mntlist_recheck(AF_INET6, fe2);
+ }
+ break;
+ }
+ }
+ if (!check) {
+ /* Is a directory still exported? */
+ if (!(fe2->oflags & OPT_EXPORTED))
+ fe1->oflags &= ~OPT_EXPORTED;
+ }
+ }
+ return (0);
+}
diff -ruN empty/mountd_conf.h mountd/mountd_conf.h
--- empty/mountd_conf.h 1970-01-01 03:00:00.000000000 +0300
+++ mountd/mountd_conf.h 2009-06-26 14:12:41.000000000 +0300
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (c) 2009 Andrey Simonenko
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD:$
+ */
+
+#ifndef MOUNTD_CONF_H
+#define MOUNTD_CONF_H
+
+/*
+ * Options flags, not all of them should be visible outside of
+ * mountd_conf.c, but it looks better to keep them all in one place.
+ */
+#define OPT_QUIET 0x00000001 /* -quiet */
+#define OPT_RO 0x00000002 /* -ro */
+#define OPT_RW 0x00000004 /* -rw */
+#define OPT_ALLDIRS 0x00000008 /* -alldirs */
+#define OPT_PUBLIC 0x00000010 /* -public */
+#define OPT_INDEX 0x00000020 /* -index */
+#define OPT_MAPALL 0x00000040 /* -mapall */
+#define OPT_MAPROOT 0x00000080 /* -maproot */
+#define OPT_MASK 0x00000100 /* -mask or -network=.../yy */
+#define OPT_NETWORK 0x00000200 /* -network */
+#define OPT_HOST 0x00000400 /* -host */
+#define OPT_WEBNFS 0x00000800 /* -webnfs */
+#define OPT_SEC 0x00001000 /* -sec */
+#define OPT_NOSPEC 0x00002000 /* -nospec */
+#define OPT_NO_MNT_DMP 0x00004000 /* -no_mntproc_dump */
+#define OPT_NO_MNT_EXP 0x00008000 /* -no_mntproc_export */
+#define OPT_DENY 0x00010000 /* '!' flag in address spec. */
+#define OPT_ARG 0x01000000 /* Option requires an argument. */
+#define OPT_COMPAT 0x02000000 /* Obsolete option. */
+#define OPT_ERRCONFIG 0x04000000 /* Wrong configuration. */
+#define OPT_EXPORTED 0x10000000 /* File system is exported. */
+#define OPT_NEWEXPORT 0x20000000 /* New file system export. */
+
+/*
+ * Commands are encoded in last two bits of options value.
+ */
+#define _CMD_SHIFT (30)
+#define _CMD_GET(x) ((x) >> _CMD_SHIFT)
+#define _CMD_ALLBITS (~((UINT32_C(1) << _CMD_SHIFT) - 1))
+#define _CMD_OPT(x) ((x) & _CMD_ALLBITS)
+#define CMD_ADD (UINT32_C(0))
+#define CMD_UPDATE (UINT32_C(1))
+#define CMD_DELETE (UINT32_C(2))
+#define CMD_FLUSH (UINT32_C(3))
+#define CMD_OPT_ADD (CMD_ADD << _CMD_SHIFT)
+#define CMD_OPT_UPDATE (CMD_UPDATE << _CMD_SHIFT)
+#define CMD_OPT_DELETE (CMD_DELETE << _CMD_SHIFT)
+#define CMD_OPT_FLUSH (CMD_FLUSH << _CMD_SHIFT)
+
+extern int set_exp_file(int, char **);
+extern int add_exp_cmd(char *);
+
+extern int configure(int);
+extern void unconfigure(void);
+extern void show_conf(void);
+extern int update_conf(const int);
+extern void free_unref_conf(void);
+
+extern void free_fs_exp_list(struct fs_exp_list *);
+
+extern char *addr_spec_str(const u_int, const struct addr_spec *);
+extern int addrcmp(const uint8_t *, const uint8_t *, const u_int);
+
+extern int crit_fs_err(const int);
+
+extern int ctl_export_pack(void);
+extern int ctl_export_unpack(void);
+extern int ctl_nodata_pack(void);
+extern int ctl_nodata_unpack(void);
+
+#define ctl_reload_pack ctl_nodata_pack
+#define ctl_reload_unpack ctl_nodata_unpack
+#define ctl_flush_pack ctl_nodata_pack
+#define ctl_flush_unpack ctl_nodata_unpack
+
+#endif /* !MOUNTD_CONF_H */
diff -ruN empty/mountd_xdr.c mountd/mountd_xdr.c
--- empty/mountd_xdr.c 1970-01-01 03:00:00.000000000 +0300
+++ mountd/mountd_xdr.c 2009-06-26 14:12:42.000000000 +0300
@@ -0,0 +1,308 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California.
+ * Copyright (c) 2009 Andrey Simonenko
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Herb Hasler and Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD:$");
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <rpc/types.h>
+#include <rpc/xdr.h>
+
+#include <nfs/nfsproto.h>
+#include <nfs/rpcv2.h>
+#include <nfsserver/nfs_export.h>
+
+#include <string.h>
+#include <syslog.h>
+
+#include "mountd.h"
+#include "mountd_conf.h"
+#include "mountd_xdr.h"
+
+/*
+ * NFSv2 and NFSv3 define formats for RPC mount procedures.
+ * Both standards use the same XDR data structures (except file handle).
+ */
+
+/*
+ * XDR routine for generating file handle reply.
+ */
+int
+xdr_fhs(XDR *xdrs, char *cp)
+{
+ struct fhreturn *fhrp;
+ u_int val;
+#ifdef WITH_SEC
+ u_int i;
+#else
+ int secflav;
+#endif
+
+ fhrp = (struct fhreturn *)cp;
+
+ val = NFS_OK;
+ if (!xdr_u_int(xdrs, &val))
+ goto xdr_int_failed;
+
+ switch (fhrp->fhr_vers) {
+ case 1:
+ if (!xdr_opaque(xdrs, (char *)&fhrp->fhr_fh, NFSX_V2FH))
+ goto xdr_opaque_failed;
+ return (TRUE);
+ case 3:
+ val = NFSX_V3FH;
+ if (!xdr_u_int(xdrs, &val))
+ goto xdr_int_failed;
+ if (!xdr_opaque(xdrs, (char *)&fhrp->fhr_fh, val))
+ goto xdr_opaque_failed;
+#ifdef WITH_SEC
+ if (!xdr_u_int(xdrs, &fhrp->fhr_nsec))
+ goto xdr_int_failed;
+ for (i = 0; i < fhrp->fhr_nsec; ++i)
+ if (!xdr_int(xdrs, &fhrp->fhr_sec[i]))
+ goto xdr_int_failed;
+#else
+ val = 1;
+ if (!xdr_u_int(xdrs, &val))
+ goto xdr_int_failed;
+ secflav = RPCAUTH_UNIX;
+ if (!xdr_int(xdrs, &secflav))
+ goto xdr_int_failed;
+#endif
+ return (TRUE);
+ }
+ return (FALSE);
+
+xdr_opaque_failed:
+ syslog(LOG_WARNING, "xdr_fhs: xdr_opaque failed");
+ return (FALSE);
+
+xdr_int_failed:
+ syslog(LOG_WARNING, "xdr_fhs: xdr_int failed");
+ return (FALSE);
+}
+
+/*
+ * XDR routine for generating list of hosts with mounted paths.
+ */
+/* ARGSUSED1 */
+int
+xdr_mntlist(XDR *xdrs, char *cp __unused)
+{
+ const struct mnt_host_list *mh_list;
+ const struct mnt_host *mh;
+ const struct fs_exp_list *fe_list;
+ const struct fs_exp *fe;
+ struct mnt_path *mp;
+ char *pathp, *hostp;
+ size_t path_len;
+ int family;
+ bool_t bool;
+ char path[PATH_MAX];
+ char host[INET6_ADDRSTRLEN];
+
+ if (no_mntproc_dump)
+ goto done;
+ bool = TRUE;
+ pathp = path;
+ hostp = host;
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ if ((fe->oflags & OPT_NO_MNT_DMP) ||
+ !(fe->oflags & OPT_EXPORTED))
+ continue;
+ path_len = fe->path_len;
+ memcpy(path, fe->path, fe->path_len);
+ for (family = AF_INET, mh_list = &fe->mh4_list;;) {
+ LIST_FOREACH(mh, mh_list, link) {
+ if (inet_ntop(family, MNT_HOST_ADDR(mh),
+ host, sizeof(host)) == NULL) {
+ syslog(LOG_ERR, "xdr_mntlist: "
+ "inet_ntop: %m");
+ return (FALSE);
+ }
+ LIST_FOREACH(mp, &mh->mp_list, link) {
+ if (!xdr_bool(xdrs, &bool))
+ goto xdr_bool_failed;
+ if (!xdr_string(xdrs, &hostp,
+ RPCMNT_NAMELEN))
+ goto xdr_string_failed;
+ if (mp->subpath == subpath_empty)
+ path[path_len] = '\0';
+ else {
+ path[path_len] = '/';
+ strncpy(path + path_len + 1,
+ mp->subpath,
+ PATH_MAX - path_len - 2);
+ path[PATH_MAX - 1] = '\0';
+ }
+ if (!xdr_string(xdrs, &pathp,
+ RPCMNT_PATHLEN))
+ goto xdr_string_failed;
+ }
+ }
+ if (mh_list == &fe->mh6_list)
+ break;
+ family = AF_INET6;
+ mh_list = &fe->mh6_list;
+ }
+ }
+done:
+ bool = FALSE;
+ if (xdr_bool(xdrs, &bool))
+ return (TRUE);
+
+xdr_bool_failed:
+ syslog(LOG_WARNING, "xdr_mntlist: xdr_bool failed");
+ return (FALSE);
+
+xdr_string_failed:
+ syslog(LOG_WARNING, "xdr_mntlist: xdr_string failed");
+ return (FALSE);
+}
+
+/*
+ * XDR conversion for a file system path.
+ */
+int
+xdr_path(XDR *xdrs, char *path)
+{
+ if (xdr_string(xdrs, &path, RPCMNT_PATHLEN))
+ return (TRUE);
+ syslog(LOG_WARNING, "xdr_path: xdr_string failed");
+ return (FALSE);
+}
+
+/*
+ * XDR routine for generating brief or complete information
+ * about exported file systems.
+ */
+static int
+xdr_export_common(XDR *xdrs, const int brief)
+{
+ static char addr_brief[] = "(...)";
+
+ const struct fs_exp_list *fe_list;
+ const struct addr_exp_list *ae_list;
+ struct addr_exp *ae;
+ struct fs_exp *fe;
+ char *str;
+ u_int family;
+ bool_t true, false;
+
+ false = FALSE;
+ if (no_mntproc_export)
+ goto done;
+ true = TRUE;
+ fe_list = fs_exp_list;
+ TAILQ_FOREACH(fe, fe_list, link) {
+ if (!(fe->oflags & OPT_EXPORTED) ||
+ (fe->oflags & OPT_NO_MNT_EXP) ||
+ fe->no_mnt_exp == fe->nspec)
+ continue;
+ if (!xdr_bool(xdrs, &true))
+ goto xdr_bool_failed;
+ if (!xdr_string(xdrs, &fe->path, RPCMNT_PATHLEN))
+ goto xdr_string_failed;
+ if (brief) {
+ if (!xdr_bool(xdrs, &true))
+ goto xdr_bool_failed;
+ str = addr_brief;
+ if (!xdr_string(xdrs, &str, RPCMNT_PATHLEN))
+ goto xdr_string_failed;
+ } else if (fe->ae_def == NULL) {
+ for (family = AF_INET, ae_list = &fe->ae4_list;;) {
+ TAILQ_FOREACH_REVERSE(ae, ae_list,
+ addr_exp_list, link) {
+ if (ae->oflags &
+ (OPT_DENY|OPT_NO_MNT_EXP))
+ continue;
+ if (!xdr_bool(xdrs, &true))
+ goto xdr_bool_failed;
+ str = addr_spec_str(family,
+ ae->addr_spec);
+ if (!xdr_string(xdrs, &str,
+ RPCMNT_NAMELEN))
+ goto xdr_string_failed;
+ }
+ if (ae_list == &fe->ae6_list)
+ break;
+ family = AF_INET6;
+ ae_list = &fe->ae6_list;
+ }
+ }
+ if (!xdr_bool(xdrs, &false))
+ goto xdr_bool_failed;
+ }
+done:
+ if (xdr_bool(xdrs, &false))
+ return (TRUE);
+
+xdr_bool_failed:
+ syslog(LOG_WARNING, "xdr_export_common: xdr_bool failed");
+ return (FALSE);
+
+xdr_string_failed:
+ syslog(LOG_WARNING, "xdr_export_common: xdr_string failed");
+ return (FALSE);
+}
+
+/*
+ * XDR routine for generating complete information
+ * about exported file systems.
+ */
+/* ARGSUSED1 */
+int
+xdr_export_compl(XDR *xdrs, char *cp __unused)
+{
+ return (xdr_export_common(xdrs, 0));
+}
+
+/*
+ * XDR routine for generating brief information
+ * about exported file systems.
+ */
+/* ARGSUSED1 */
+int
+xdr_export_brief(XDR *xdrs, char *cp __unused)
+{
+ return (xdr_export_common(xdrs, 1));
+}
diff -ruN empty/mountd_xdr.h mountd/mountd_xdr.h
--- empty/mountd_xdr.h 1970-01-01 03:00:00.000000000 +0300
+++ mountd/mountd_xdr.h 2009-06-26 14:12:42.000000000 +0300
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Herb Hasler and Rick Macklem at The University of Guelph.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD:$
+ */
+
+#ifndef MOUNTD_XDR_H
+#define MOUNTD_XDR_H
+
+/*
+ * An argument for xdr_fhs().
+ */
+struct fhreturn {
+ u_int fhr_vers; /* Service protocol version. */
+ nfsfh_t fhr_fh; /* File handle from getfh(). */
+ u_int fhr_nsec; /* Number of security flavors. */
+ int *fhr_sec; /* Security flavors. */
+};
+
+extern int xdr_fhs(XDR *, char *);
+extern int xdr_mntlist(XDR *, char *);
+extern int xdr_path(XDR *, char *);
+extern int xdr_export_compl(XDR *, char *);
+extern int xdr_export_brief(XDR *, char *);
+
+#endif /* !MOUNTD_XDR_H */
diff -ruN empty/netgroup.5 mountd/netgroup.5
--- empty/netgroup.5 1970-01-01 03:00:00.000000000 +0300
+++ mountd/netgroup.5 2009-06-01 12:12:57.000000000 +0300
@@ -0,0 +1,199 @@
+.\" Copyright (c) 1992, 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.
+.\" 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.
+.\"
+.\" @(#)netgroup.5 8.2 (Berkeley) 12/11/93
+.\" $FreeBSD: src/usr.sbin/mountd/netgroup.5,v 1.14 2005/01/18 20:02:38 ru Exp $
+.\"
+.Dd December 11, 1993
+.Dt NETGROUP 5
+.Os
+.Sh NAME
+.Nm netgroup
+.Nd defines network groups
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+The
+.Nm
+file
+specifies
+.Dq netgroups ,
+which are sets of
+.Sy (host, user, domain)
+tuples that are to be given similar network access.
+.Pp
+Each line in the file
+consists of a netgroup name followed by a list of the members of the
+netgroup.
+Each member can be either the name of another netgroup or a specification
+of a tuple as follows:
+.Bd -literal -offset indent
+(host, user, domain)
+.Ed
+.Pp
+where the
+.Sy host ,
+.Sy user ,
+and
+.Sy domain
+are character string names for the corresponding component.
+Any of the comma separated fields may be empty to specify a
+.Dq wildcard
+value or may consist of the string
+.Ql -
+to specify
+.Dq no valid value .
+The members of the list may be separated by whitespace and/or commas;
+the
+.Ql \e
+character may be used at the end of a line to specify
+line continuation.
+Lines are limited to 1024 characters.
+The functions specified in
+.Xr getnetgrent 3
+should normally be used to access the
+.Nm
+database.
+.Pp
+Lines that begin with a
+.Ql #
+are treated as comments.
+.Sh NIS/YP INTERACTION
+On most other platforms,
+.Nm Ns s
+are only used in conjunction with
+.Tn NIS
+and local
+.Pa /etc/netgroup
+files are ignored.
+With
+.Fx ,
+.Nm Ns s
+can be used with either
+.Tn NIS
+or local files, but there are certain
+caveats to consider.
+The existing
+.Nm
+system is extremely inefficient where
+.Fn innetgr 3
+lookups are concerned since
+.Nm
+memberships are computed on the fly.
+By contrast, the
+.Tn NIS
+.Nm
+database consists of three separate maps (netgroup, netgroup.byuser
+and netgroup.byhost) that are keyed to allow
+.Fn innetgr 3
+lookups to be done quickly.
+The
+.Fx
+.Nm
+system can interact with the
+.Tn NIS
+.Nm
+maps in the following ways:
+.Bl -bullet -offset indent
+.It
+If the
+.Pa /etc/netgroup
+file does not exist, or it exists and is empty, or
+it exists and contains only a
+.Sq + ,
+and
+.Tn NIS
+is running,
+.Nm
+lookups will be done exclusively through
+.Tn NIS ,
+with
+.Fn innetgr 3
+taking advantage of the netgroup.byuser and
+netgroup.byhost maps to speed up searches.
+(This
+is more or less compatible with the behavior of SunOS and
+similar platforms.)
+.It
+If the
+.Pa /etc/netgroup
+exists and contains only local
+.Nm
+information (with no
+.Tn NIS
+.Sq +
+token), then only the local
+.Nm
+information will be processed (and
+.Tn NIS
+will be ignored).
+.It
+If
+.Pa /etc/netgroup
+exists and contains both local netgroup data
+.Pa and
+the
+.Tn NIS
+.Sq +
+token, the local data and the
+.Tn NIS
+netgroup
+map will be processed as a single combined
+.Nm
+database.
+While this configuration is the most flexible, it
+is also the least efficient: in particular,
+.Fn innetgr 3
+lookups will be especially slow if the
+database is large.
+.El
+.Sh FILES
+.Bl -tag -width /etc/netgroup -compact
+.It Pa /etc/netgroup
+the netgroup database
+.El
+.Sh COMPATIBILITY
+The file format is compatible with that of various vendors, however it
+appears that not all vendors use an identical format.
+.Sh SEE ALSO
+.Xr getnetgrent 3
+.Sh BUGS
+The interpretation of access restrictions based on the member tuples of a
+netgroup is left up to the various network applications.
+Also, it is not obvious how the domain specification
+applies to the
+.Bx
+environment.
+.Pp
+The
+.Nm
+database should be stored in the form of a
+hashed
+.Xr db 3
+database just like the
+.Xr passwd 5
+database to speed up reverse lookups.
diff -ruN empty/pathnames.h mountd/pathnames.h
--- empty/pathnames.h 1970-01-01 03:00:00.000000000 +0300
+++ mountd/pathnames.h 2009-06-26 14:12:43.000000000 +0300
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ * 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.
+ *
+ * @(#)pathnames.h 8.1 (Berkeley) 6/5/93
+ * $FreeBSD: src/usr.sbin/mountd/pathnames.h,v 1.2 2004/08/07 04:27:51 imp Exp $
+ */
+
+#ifndef MOUNTD_PATHNAMES_H
+#define MOUNTD_PATHNAMES_H
+
+#define _PATH_EXPORTS "/etc/exports"
+#define _PATH_RMOUNTLIST "/var/db/mountdtab"
+#define _PATH_MOUNTDPID "/var/run/mountd.pid"
+#define _PATH_MOUNTD_CTLSOCKET "/var/run/mountd.socket"
+
+#endif /* !MOUNTD_PATHNAMES_H */
#########################################################################
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list