diff --git a/usr.sbin/nfsd/Makefile b/usr.sbin/nfsd/Makefile index d7ca8c380c48..b6bd9a28e651 100644 --- a/usr.sbin/nfsd/Makefile +++ b/usr.sbin/nfsd/Makefile @@ -1,6 +1,8 @@ PACKAGE= nfs PROG= nfsd MAN= nfsd.8 nfsv4.4 stablerestart.5 pnfs.4 pnfsserver.4 +LIBADD= util + .include diff --git a/usr.sbin/nfsd/Makefile.depend b/usr.sbin/nfsd/Makefile.depend index 732a025c9552..7e5c47e39608 100644 --- a/usr.sbin/nfsd/Makefile.depend +++ b/usr.sbin/nfsd/Makefile.depend @@ -1,18 +1,19 @@ # Autogenerated - do NOT edit! DIRDEPS = \ include \ include/arpa \ include/rpc \ include/rpcsvc \ include/xlocale \ lib/${CSU_DIR} \ lib/libc \ lib/libcompiler_rt \ + lib/libutil \ .include .if ${DEP_RELDIR} == ${_DEP_RELDIR} # local dependencies - needed for -jN in clean tree .endif diff --git a/usr.sbin/nfsd/nfsd.8 b/usr.sbin/nfsd/nfsd.8 index 992228fba752..2e5724dbce33 100644 --- a/usr.sbin/nfsd/nfsd.8 +++ b/usr.sbin/nfsd/nfsd.8 @@ -1,391 +1,396 @@ .\" Copyright (c) 1989, 1991, 1993 .\" The Regents of the University of California. All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" 3. Neither the name of the University nor the names of its contributors .\" may be used to endorse or promote products derived from this software .\" without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 21, 2025 +.Dd May 30, 2025 .Dt NFSD 8 .Os .Sh NAME .Nm nfsd .Nd remote NFS server .Sh SYNOPSIS .Nm .Op Fl arduteN .Op Fl n Ar num_servers .Op Fl h Ar bindip .Op Fl p Ar pnfs_setup .Op Fl m Ar mirror_level +.Op Fl P Ar pidfile .Op Fl V Ar virtual_hostname .Op Fl Fl maxthreads Ar max_threads .Op Fl Fl minthreads Ar min_threads .Sh DESCRIPTION The .Nm utility runs on a server machine to service NFS requests from client machines. At least one .Nm must be running for a machine to operate as a server. .Pp Unless otherwise specified, eight servers per CPU for UDP transport are started. .Pp When .Nm is run in an appropriately configured vnet jail, the server is restricted to TCP transport and no pNFS service. Therefore, the .Fl t option must be specified and none of the .Fl u , .Fl p and .Fl m options can be specified when run in a vnet jail. See .Xr jail 8 for more information. .Pp The following options are available: .Bl -tag -width Ds .It Fl r Register the NFS service with .Xr rpcbind 8 without creating any servers. This option can be used along with the .Fl u or .Fl t options to re-register NFS if the rpcbind server is restarted. .It Fl d Unregister the NFS service with .Xr rpcbind 8 without creating any servers. +.It Fl P Ar pidfile +Specify alternative location of a file where main process PID will be stored. +The default location is +.Pa /var/run/nfsd.pid . .It Fl V Ar virtual_hostname Specifies a hostname to be used as a principal name, instead of the default hostname. .It Fl n Ar threads This option is deprecated and is limited to a maximum of 256 threads. The options .Fl Fl maxthreads and .Fl Fl minthreads should now be used. The .Ar threads argument for .Fl Fl minthreads and .Fl Fl maxthreads may be set to the same value to avoid dynamic changes to the number of threads. .It Fl Fl maxthreads Ar threads Specifies the maximum servers that will be kept around to service requests. .It Fl Fl minthreads Ar threads Specifies the minimum servers that will be kept around to service requests. .It Fl h Ar bindip Specifies which IP address or hostname to bind to on the local host. This option is recommended when a host has multiple interfaces. Multiple .Fl h options may be specified. .It Fl a Specifies that nfsd should bind to the wildcard IP address. This is the default if no .Fl h options are given. It may also be specified in addition to any .Fl h options given. Note that NFS/UDP does not operate properly when bound to the wildcard IP address whether you use -a or do not use -h. .It Fl p Ar pnfs_setup Enables pNFS support in the server and specifies the information that the daemon needs to start it. This option can only be used on one server and specifies that this server will be the MetaData Server (MDS) for the pNFS service. This can only be done if there is at least one .Fx system configured as a Data Server (DS) for it to use. .Pp The .Ar pnfs_setup string is a set of fields separated by ',' characters: Each of these fields specifies one DS. It consists of a server hostname, followed by a ':' and the directory path where the DS's data storage file system is mounted on this MDS server. This can optionally be followed by a '#' and the mds_path, which is the directory path for an exported file system on this MDS. If this is specified, it means that this DS is to be used to store data files for this mds_path file system only. If this optional component does not exist, the DS will be used to store data files for all exported MDS file systems. The DS storage file systems must be mounted on this system before the .Nm is started with this option specified. .br For example: .sp nfsv4-data0:/data0,nfsv4-data1:/data1 .sp would specify two DS servers called nfsv4-data0 and nfsv4-data1 that comprise the data storage component of the pNFS service. These two DSs would be used to store data files for all exported file systems on this MDS. The directories .Dq /data0 and .Dq /data1 are where the data storage servers exported storage directories are mounted on this system (which will act as the MDS). .br Whereas, for the example: .sp nfsv4-data0:/data0#/export1,nfsv4-data1:/data1#/export2 .sp would specify two DSs as above, however nfsv4-data0 will be used to store data files for .Dq /export1 and nfsv4-data1 will be used to store data files for .Dq /export2 . .sp When using IPv6 addresses for DSs be wary of using link local addresses. The IPv6 address for the DS is sent to the client and there is no scope zone in it. As such, a link local address may not work for a pNFS client to DS TCP connection. When parsed, .Nm will only use a link local address if it is the only address returned by .Xr getaddrinfo 3 for the DS hostname. .It Fl m Ar mirror_level This option is only meaningful when used with the .Fl p option. It specifies the .Dq mirror_level , which defines how many of the DSs will have a copy of a file's data storage file. The default of one implies no mirroring of data storage files on the DSs. The .Dq mirror_level would normally be set to 2 to enable mirroring, but can be as high as NFSDEV_MAXMIRRORS. There must be at least .Dq mirror_level DSs for each exported file system on the MDS, as specified in the .Fl p option. This implies that, for the above example using "#/export1" and "#/export2", mirroring cannot be done. There would need to be two DS entries for each of "#/export1" and "#/export2" in order to support a .Dq mirror_level of two. .Pp If mirroring is enabled, the server must use the Flexible File layout. If mirroring is not enabled, the server will use the File layout by default, but this default can be changed to the Flexible File layout if the .Xr sysctl 8 vfs.nfsd.default_flexfile is set non-zero. .It Fl t Serve TCP NFS clients. .It Fl u Serve UDP NFS clients. .It Fl e Ignored; included for backward compatibility. .It Fl N Cause .Nm to execute in the foreground instead of in daemon mode. .El .Pp For example, .Dq Li "nfsd -u -t --minthreads 6 --maxthreads 6" serves UDP and TCP transports using six kernel threads (servers). .Pp For a system dedicated to servicing NFS RPCs, the number of threads (servers) should be sufficient to handle the peak client RPC load. For systems that perform other services, the number of threads (servers) may need to be limited, so that resources are available for these other services. .Pp The .Nm utility listens for service requests at the port indicated in the NFS server specification; see .%T "Network File System Protocol Specification" , RFC1094, .%T "NFS: Network File System Version 3 Protocol Specification" , RFC1813, .%T "Network File System (NFS) Version 4 Protocol" , RFC7530, .%T "Network File System (NFS) Version 4 Minor Version 1 Protocol" , RFC5661, .%T "Network File System (NFS) Version 4 Minor Version 2 Protocol" , RFC7862, .%T "File System Extended Attributes in NFSv4" , RFC8276 and .%T "Parallel NFS (pNFS) Flexible File Layout" , RFC8435. .Pp If .Nm detects that NFS is not loaded in the running kernel, it will attempt to load a loadable kernel module containing NFS support using .Xr kldload 2 . If this fails, or no NFS KLD is available, .Nm will exit with an error. .Pp If .Nm is to be run on a host with multiple interfaces or interface aliases, use of the .Fl h option is recommended. If you do not use the option NFS may not respond to UDP packets from the same IP address they were sent to. Use of this option is also recommended when securing NFS exports on a firewalling machine such that the NFS sockets can only be accessed by the inside interface. The .Nm ipfw utility would then be used to block NFS-related packets that come in on the outside interface. .Pp If the server has stopped servicing clients and has generated a console message like .Dq Li "nfsd server cache flooded..." , the value for vfs.nfsd.tcphighwater needs to be increased. This should allow the server to again handle requests without a reboot. Also, you may want to consider decreasing the value for vfs.nfsd.tcpcachetimeo to several minutes (in seconds) instead of 12 hours when this occurs. .Pp Unfortunately making vfs.nfsd.tcphighwater too large can result in the mbuf limit being reached, as indicated by a console message like .Dq Li "kern.ipc.nmbufs limit reached" . If you cannot find values of the above .Nm sysctl values that work, you can disable the DRC cache for TCP by setting vfs.nfsd.cachetcp to 0. .Pp The .Nm utility has to be terminated with .Dv SIGUSR1 and cannot be killed with .Dv SIGTERM or .Dv SIGQUIT . The .Nm utility needs to ignore these signals in order to stay alive as long as possible during a shutdown, otherwise loopback mounts will not be able to unmount. If you have to kill .Nm just do a .Dq Li "kill -USR1 " .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr nfsstat 1 , .Xr kldload 2 , .Xr nfssvc 2 , .Xr nfsv4 4 , .Xr pnfs 4 , .Xr pnfsserver 4 , .Xr exports 5 , .Xr stablerestart 5 , .Xr gssd 8 , .Xr ipfw 8 , .Xr jail 8 , .Xr mountd 8 , .Xr nfsiod 8 , .Xr nfsrevoke 8 , .Xr nfsuserd 8 , .Xr rpcbind 8 .Sh HISTORY The .Nm utility first appeared in .Bx 4.4 . .Sh BUGS If .Nm is started when .Xr gssd 8 is not running, it will service AUTH_SYS requests only. To fix the problem you must kill .Nm and then restart it, after the .Xr gssd 8 is running. .Pp For a Flexible File Layout pNFS server, if there are Linux clients doing NFSv4.1 or NFSv4.2 mounts, those clients might need the .Xr sysctl 8 vfs.nfsd.flexlinuxhack to be set to one on the MDS as a workaround. .Pp Linux 5.n kernels appear to have been patched such that this .Xr sysctl 8 does not need to be set. .Pp For NFSv4.2, a Copy operation can take a long time to complete. If there is a concurrent ExchangeID or DelegReturn operation which requires the exclusive lock on all NFSv4 state, this can result in a .Dq stall of the .Nm server. If your storage is on ZFS without block cloning enabled, setting the .Xr sysctl 8 .Va vfs.zfs.dmu_offset_next_sync to 0 can often avoid this problem. It is also possible to set the .Xr sysctl 8 .Va vfs.nfsd.maxcopyrange to 10-100 megabytes to try and reduce Copy operation times. As a last resort, setting .Xr sysctl 8 .Va vfs.nfsd.maxcopyrange to 0 disables the Copy operation. diff --git a/usr.sbin/nfsd/nfsd.c b/usr.sbin/nfsd/nfsd.c index f1f04af192da..94c30ae6dee1 100644 --- a/usr.sbin/nfsd/nfsd.c +++ b/usr.sbin/nfsd/nfsd.c @@ -1,1377 +1,1400 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1989, 1993, 1994 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Rick Macklem at The University of Guelph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include static int debug = 0; static int nofork = 0; +#define DEFAULT_PIDFILE "/var/run/nfsd.pid" #define NFSD_STABLERESTART "/var/db/nfs-stablerestart" #define NFSD_STABLEBACKUP "/var/db/nfs-stablerestart.bak" #define MAXNFSDCNT 256 #define DEFNFSDCNT 4 #define NFS_VER2 2 #define NFS_VER3 3 #define NFS_VER4 4 static pid_t children[MAXNFSDCNT]; /* PIDs of children */ static pid_t masterpid; /* PID of master/parent */ +static struct pidfh *masterpidfh = NULL; /* pidfh of master/parent */ static int nfsdcnt; /* number of children */ static int nfsdcnt_set; static int minthreads; static int maxthreads; static int nfssvc_nfsd; /* Set to correct NFSSVC_xxx flag */ static int stablefd = -1; /* Fd for the stable restart file */ static int backupfd; /* Fd for the backup stable restart file */ static const char *getopt_shortopts; static const char *getopt_usage; static int nfs_minvers = NFS_VER2; static int minthreads_set; static int maxthreads_set; static struct option longopts[] = { { "debug", no_argument, &debug, 1 }, { "minthreads", required_argument, &minthreads_set, 1 }, { "maxthreads", required_argument, &maxthreads_set, 1 }, { "pnfs", required_argument, NULL, 'p' }, { "mirror", required_argument, NULL, 'm' }, { NULL, 0, NULL, 0} }; static void cleanup(int); static void child_cleanup(int); static void killchildren(void); static void nfsd_exit(int); static void nonfs(int); static void reapchild(int); static int setbindhost(struct addrinfo **ia, const char *bindhost, struct addrinfo hints); static void start_server(int, struct nfsd_nfsd_args *, const char *vhost); static void unregistration(void); static void usage(void); static void open_stable(int *, int *); static void copy_stable(int, int); static void backup_stable(int); static void set_nfsdcnt(int); static void parse_dsserver(const char *, struct nfsd_nfsd_args *); /* * Nfs server daemon mostly just a user context for nfssvc() * * 1 - do file descriptor and signal cleanup * 2 - fork the nfsd(s) * 3 - create server socket(s) * 4 - register socket with rpcbind * * For connectionless protocols, just pass the socket into the kernel via. * nfssvc(). * For connection based sockets, loop doing accepts. When you get a new * socket from accept, pass the msgsock into the kernel via. nfssvc(). * The arguments are: * -r - reregister with rpcbind * -d - unregister with rpcbind * -t - support tcp nfs clients * -u - support udp nfs clients * -e - forces it to run a server that supports nfsv4 * -p - enable a pNFS service * -m - set the mirroring level for a pNFS service * followed by "n" which is the number of nfsds' to fork off */ int main(int argc, char **argv) { struct nfsd_addsock_args addsockargs; struct addrinfo *ai_udp, *ai_tcp, *ai_udp6, *ai_tcp6, hints; struct netconfig *nconf_udp, *nconf_tcp, *nconf_udp6, *nconf_tcp6; struct netbuf nb_udp, nb_tcp, nb_udp6, nb_tcp6; struct sockaddr_storage peer; fd_set ready, sockbits; int ch, connect_type_cnt, i, maxsock, msgsock; socklen_t len; int on = 1, unregister, reregister, sock; int tcp6sock, ip6flag, tcpflag, tcpsock; int udpflag, ecode, error, s; int bindhostc, bindanyflag, rpcbreg, rpcbregcnt; int nfssvc_addsock; int jailed, longindex = 0; size_t jailed_size, nfs_minvers_size; const char *lopt; char **bindhost = NULL; - pid_t pid; + const char *pidfile_path = DEFAULT_PIDFILE; + pid_t pid, otherpid; struct nfsd_nfsd_args nfsdargs; const char *vhostname = NULL; nfsdargs.mirrorcnt = 1; nfsdargs.addr = NULL; nfsdargs.addrlen = 0; nfsdcnt = DEFNFSDCNT; unregister = reregister = tcpflag = maxsock = 0; bindanyflag = udpflag = connect_type_cnt = bindhostc = 0; - getopt_shortopts = "ah:n:rdtuep:m:V:N"; + getopt_shortopts = "ah:n:rdtuep:m:V:NP:"; getopt_usage = "usage:\n" " nfsd [-ardtueN] [-h bindip]\n" " [-n numservers] [--minthreads #] [--maxthreads #]\n" " [-p/--pnfs dsserver0:/dsserver0-mounted-on-dir,...," "dsserverN:/dsserverN-mounted-on-dir] [-m mirrorlevel]\n" - " [-V virtual_hostname]\n"; + " [-P pidfile ] [-V virtual_hostname]\n"; while ((ch = getopt_long(argc, argv, getopt_shortopts, longopts, &longindex)) != -1) switch (ch) { case 'V': if (strlen(optarg) <= MAXHOSTNAMELEN) vhostname = optarg; else warnx("Virtual host name (%s) is too long", optarg); break; case 'a': bindanyflag = 1; break; case 'n': set_nfsdcnt(atoi(optarg)); break; case 'h': bindhostc++; bindhost = realloc(bindhost,sizeof(char *)*bindhostc); if (bindhost == NULL) errx(1, "Out of memory"); bindhost[bindhostc-1] = strdup(optarg); if (bindhost[bindhostc-1] == NULL) errx(1, "Out of memory"); break; case 'r': reregister = 1; break; case 'd': unregister = 1; break; case 't': tcpflag = 1; break; case 'u': udpflag = 1; break; case 'e': /* now a no-op, since this is the default */ break; case 'p': /* Parse out the DS server host names and mount pts. */ parse_dsserver(optarg, &nfsdargs); break; case 'm': /* Set the mirror level for a pNFS service. */ i = atoi(optarg); if (i < 2 || i > NFSDEV_MAXMIRRORS) errx(1, "Mirror level out of range 2<-->%d", NFSDEV_MAXMIRRORS); nfsdargs.mirrorcnt = i; break; case 'N': nofork = 1; break; + case 'P': + pidfile_path = optarg; + break; case 0: lopt = longopts[longindex].name; if (!strcmp(lopt, "minthreads")) { minthreads = atoi(optarg); } else if (!strcmp(lopt, "maxthreads")) { maxthreads = atoi(optarg); } break; default: case '?': usage(); } if (!tcpflag && !udpflag) udpflag = 1; argv += optind; argc -= optind; if (minthreads_set && maxthreads_set && minthreads > maxthreads) errx(EX_USAGE, "error: minthreads(%d) can't be greater than " "maxthreads(%d)", minthreads, maxthreads); /* * XXX * Backward compatibility, trailing number is the count of daemons. */ if (argc > 1) usage(); if (argc == 1) set_nfsdcnt(atoi(argv[0])); /* * Unless the "-o" option was specified, try and run "nfsd". * If "-o" was specified, try and run "nfsserver". */ if (modfind("nfsd") < 0) { /* Not present in kernel, try loading it */ if (kldload("nfsd") < 0 || modfind("nfsd") < 0) errx(1, "NFS server is not available"); } ip6flag = 1; s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (s == -1) { if (errno != EPROTONOSUPPORT && errno != EAFNOSUPPORT) err(1, "socket"); ip6flag = 0; } else if (getnetconfigent("udp6") == NULL || getnetconfigent("tcp6") == NULL) { ip6flag = 0; } if (s != -1) close(s); if (bindhostc == 0 || bindanyflag) { bindhostc++; bindhost = realloc(bindhost,sizeof(char *)*bindhostc); if (bindhost == NULL) errx(1, "Out of memory"); bindhost[bindhostc-1] = strdup("*"); if (bindhost[bindhostc-1] == NULL) errx(1, "Out of memory"); } if (unregister) { /* * Unregister before setting nfs_minvers, in case the * value of vfs.nfsd.server_min_nfsvers has changed * since registering with rpcbind. */ unregistration(); exit (0); } nfs_minvers_size = sizeof(nfs_minvers); error = sysctlbyname("vfs.nfsd.server_min_nfsvers", &nfs_minvers, &nfs_minvers_size, NULL, 0); if (error != 0 || nfs_minvers < NFS_VER2 || nfs_minvers > NFS_VER4) { warnx("sysctlbyname(vfs.nfsd.server_min_nfsvers) failed," " defaulting to NFSv2"); nfs_minvers = NFS_VER2; } if (reregister) { if (udpflag) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; ecode = getaddrinfo(NULL, "nfs", &hints, &ai_udp); if (ecode != 0) err(1, "getaddrinfo udp: %s", gai_strerror(ecode)); nconf_udp = getnetconfigent("udp"); if (nconf_udp == NULL) err(1, "getnetconfigent udp failed"); nb_udp.buf = ai_udp->ai_addr; nb_udp.len = nb_udp.maxlen = ai_udp->ai_addrlen; if (nfs_minvers == NFS_VER2) if (!rpcb_set(NFS_PROGRAM, 2, nconf_udp, &nb_udp)) err(1, "rpcb_set udp failed"); if (nfs_minvers <= NFS_VER3) if (!rpcb_set(NFS_PROGRAM, 3, nconf_udp, &nb_udp)) err(1, "rpcb_set udp failed"); freeaddrinfo(ai_udp); } if (udpflag && ip6flag) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; ecode = getaddrinfo(NULL, "nfs", &hints, &ai_udp6); if (ecode != 0) err(1, "getaddrinfo udp6: %s", gai_strerror(ecode)); nconf_udp6 = getnetconfigent("udp6"); if (nconf_udp6 == NULL) err(1, "getnetconfigent udp6 failed"); nb_udp6.buf = ai_udp6->ai_addr; nb_udp6.len = nb_udp6.maxlen = ai_udp6->ai_addrlen; if (nfs_minvers == NFS_VER2) if (!rpcb_set(NFS_PROGRAM, 2, nconf_udp6, &nb_udp6)) err(1, "rpcb_set udp6 failed"); if (nfs_minvers <= NFS_VER3) if (!rpcb_set(NFS_PROGRAM, 3, nconf_udp6, &nb_udp6)) err(1, "rpcb_set udp6 failed"); freeaddrinfo(ai_udp6); } if (tcpflag) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; ecode = getaddrinfo(NULL, "nfs", &hints, &ai_tcp); if (ecode != 0) err(1, "getaddrinfo tcp: %s", gai_strerror(ecode)); nconf_tcp = getnetconfigent("tcp"); if (nconf_tcp == NULL) err(1, "getnetconfigent tcp failed"); nb_tcp.buf = ai_tcp->ai_addr; nb_tcp.len = nb_tcp.maxlen = ai_tcp->ai_addrlen; if (nfs_minvers == NFS_VER2) if (!rpcb_set(NFS_PROGRAM, 2, nconf_tcp, &nb_tcp)) err(1, "rpcb_set tcp failed"); if (nfs_minvers <= NFS_VER3) if (!rpcb_set(NFS_PROGRAM, 3, nconf_tcp, &nb_tcp)) err(1, "rpcb_set tcp failed"); freeaddrinfo(ai_tcp); } if (tcpflag && ip6flag) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; ecode = getaddrinfo(NULL, "nfs", &hints, &ai_tcp6); if (ecode != 0) err(1, "getaddrinfo tcp6: %s", gai_strerror(ecode)); nconf_tcp6 = getnetconfigent("tcp6"); if (nconf_tcp6 == NULL) err(1, "getnetconfigent tcp6 failed"); nb_tcp6.buf = ai_tcp6->ai_addr; nb_tcp6.len = nb_tcp6.maxlen = ai_tcp6->ai_addrlen; if (nfs_minvers == NFS_VER2) if (!rpcb_set(NFS_PROGRAM, 2, nconf_tcp6, &nb_tcp6)) err(1, "rpcb_set tcp6 failed"); if (nfs_minvers <= NFS_VER3) if (!rpcb_set(NFS_PROGRAM, 3, nconf_tcp6, &nb_tcp6)) err(1, "rpcb_set tcp6 failed"); freeaddrinfo(ai_tcp6); } exit (0); } + + if (pidfile_path != NULL) { + masterpidfh = pidfile_open(pidfile_path, 0600, &otherpid); + if (masterpidfh == NULL) { + if (errno == EEXIST) + errx(1, "daemon already running, pid: %jd.", + (intmax_t)otherpid); + warn("cannot open pid file"); + } + } if (debug == 0 && nofork == 0) { daemon(0, 0); (void)signal(SIGHUP, SIG_IGN); (void)signal(SIGINT, SIG_IGN); /* * nfsd sits in the kernel most of the time. It needs * to ignore SIGTERM/SIGQUIT in order to stay alive as long * as possible during a shutdown, otherwise loopback * mounts will not be able to unmount. */ (void)signal(SIGTERM, SIG_IGN); (void)signal(SIGQUIT, SIG_IGN); } (void)signal(SIGSYS, nonfs); (void)signal(SIGCHLD, reapchild); (void)signal(SIGUSR2, backup_stable); openlog("nfsd", LOG_PID | (debug ? LOG_PERROR : 0), LOG_DAEMON); + if (masterpidfh != NULL && pidfile_write(masterpidfh) != 0) + syslog(LOG_ERR, "pidfile_write(): %m"); + /* * For V4, we open the stablerestart file and call nfssvc() * to get it loaded. This is done before the daemons do the * regular nfssvc() call to service NFS requests. * (This way the file remains open until the last nfsd is killed * off.) * It and the backup copy will be created as empty files * the first time this nfsd is started and should never be * deleted/replaced if at all possible. It should live on a * local, non-volatile storage device that does not do hardware * level write-back caching. (See SCSI doc for more information * on how to prevent write-back caching on SCSI disks.) */ open_stable(&stablefd, &backupfd); if (stablefd < 0) { syslog(LOG_ERR, "Can't open %s: %m\n", NFSD_STABLERESTART); exit(1); } /* This system call will fail for old kernels, but that's ok. */ nfssvc(NFSSVC_BACKUPSTABLE, NULL); if (nfssvc(NFSSVC_STABLERESTART, (caddr_t)&stablefd) < 0) { if (errno == EPERM) { jailed = 0; jailed_size = sizeof(jailed); sysctlbyname("security.jail.jailed", &jailed, &jailed_size, NULL, 0); if (jailed != 0) syslog(LOG_ERR, "nfssvc stablerestart failed: " "allow.nfsd might not be configured"); else syslog(LOG_ERR, "nfssvc stablerestart failed"); } else if (errno == ENXIO) syslog(LOG_ERR, "nfssvc stablerestart failed: is nfsd " "already running?"); else syslog(LOG_ERR, "Can't read stable storage file: %m\n"); exit(1); } nfssvc_addsock = NFSSVC_NFSDADDSOCK; nfssvc_nfsd = NFSSVC_NFSDNFSD | NFSSVC_NEWSTRUCT; if (tcpflag) { /* * For TCP mode, we fork once to start the first * kernel nfsd thread. The kernel will add more * threads as needed. */ masterpid = getpid(); pid = fork(); if (pid == -1) { syslog(LOG_ERR, "fork: %m"); nfsd_exit(1); } if (pid) { children[0] = pid; } else { + pidfile_close(masterpidfh); (void)signal(SIGUSR1, child_cleanup); setproctitle("server"); start_server(0, &nfsdargs, vhostname); } } (void)signal(SIGUSR1, cleanup); FD_ZERO(&sockbits); rpcbregcnt = 0; /* Set up the socket for udp and rpcb register it. */ if (udpflag) { rpcbreg = 0; for (i = 0; i < bindhostc; i++) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; if (setbindhost(&ai_udp, bindhost[i], hints) == 0) { rpcbreg = 1; rpcbregcnt++; if ((sock = socket(ai_udp->ai_family, ai_udp->ai_socktype, ai_udp->ai_protocol)) < 0) { syslog(LOG_ERR, "can't create udp socket"); nfsd_exit(1); } if (bind(sock, ai_udp->ai_addr, ai_udp->ai_addrlen) < 0) { syslog(LOG_ERR, "can't bind udp addr %s: %m", bindhost[i]); nfsd_exit(1); } freeaddrinfo(ai_udp); addsockargs.sock = sock; addsockargs.name = NULL; addsockargs.namelen = 0; if (nfssvc(nfssvc_addsock, &addsockargs) < 0) { syslog(LOG_ERR, "can't Add UDP socket"); nfsd_exit(1); } (void)close(sock); } } if (rpcbreg == 1) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; ecode = getaddrinfo(NULL, "nfs", &hints, &ai_udp); if (ecode != 0) { syslog(LOG_ERR, "getaddrinfo udp: %s", gai_strerror(ecode)); nfsd_exit(1); } nconf_udp = getnetconfigent("udp"); if (nconf_udp == NULL) { syslog(LOG_ERR, "getnetconfigent udp failed"); nfsd_exit(1); } nb_udp.buf = ai_udp->ai_addr; nb_udp.len = nb_udp.maxlen = ai_udp->ai_addrlen; if (nfs_minvers == NFS_VER2) if (!rpcb_set(NFS_PROGRAM, 2, nconf_udp, &nb_udp)) { syslog(LOG_ERR, "rpcb_set udp failed"); nfsd_exit(1); } if (nfs_minvers <= NFS_VER3) if (!rpcb_set(NFS_PROGRAM, 3, nconf_udp, &nb_udp)) { syslog(LOG_ERR, "rpcb_set udp failed"); nfsd_exit(1); } freeaddrinfo(ai_udp); } } /* Set up the socket for udp6 and rpcb register it. */ if (udpflag && ip6flag) { rpcbreg = 0; for (i = 0; i < bindhostc; i++) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; if (setbindhost(&ai_udp6, bindhost[i], hints) == 0) { rpcbreg = 1; rpcbregcnt++; if ((sock = socket(ai_udp6->ai_family, ai_udp6->ai_socktype, ai_udp6->ai_protocol)) < 0) { syslog(LOG_ERR, "can't create udp6 socket"); nfsd_exit(1); } if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof on) < 0) { syslog(LOG_ERR, "can't set v6-only binding for " "udp6 socket: %m"); nfsd_exit(1); } if (bind(sock, ai_udp6->ai_addr, ai_udp6->ai_addrlen) < 0) { syslog(LOG_ERR, "can't bind udp6 addr %s: %m", bindhost[i]); nfsd_exit(1); } freeaddrinfo(ai_udp6); addsockargs.sock = sock; addsockargs.name = NULL; addsockargs.namelen = 0; if (nfssvc(nfssvc_addsock, &addsockargs) < 0) { syslog(LOG_ERR, "can't add UDP6 socket"); nfsd_exit(1); } (void)close(sock); } } if (rpcbreg == 1) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; ecode = getaddrinfo(NULL, "nfs", &hints, &ai_udp6); if (ecode != 0) { syslog(LOG_ERR, "getaddrinfo udp6: %s", gai_strerror(ecode)); nfsd_exit(1); } nconf_udp6 = getnetconfigent("udp6"); if (nconf_udp6 == NULL) { syslog(LOG_ERR, "getnetconfigent udp6 failed"); nfsd_exit(1); } nb_udp6.buf = ai_udp6->ai_addr; nb_udp6.len = nb_udp6.maxlen = ai_udp6->ai_addrlen; if (nfs_minvers == NFS_VER2) if (!rpcb_set(NFS_PROGRAM, 2, nconf_udp6, &nb_udp6)) { syslog(LOG_ERR, "rpcb_set udp6 failed"); nfsd_exit(1); } if (nfs_minvers <= NFS_VER3) if (!rpcb_set(NFS_PROGRAM, 3, nconf_udp6, &nb_udp6)) { syslog(LOG_ERR, "rpcb_set udp6 failed"); nfsd_exit(1); } freeaddrinfo(ai_udp6); } } /* Set up the socket for tcp and rpcb register it. */ if (tcpflag) { rpcbreg = 0; for (i = 0; i < bindhostc; i++) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; if (setbindhost(&ai_tcp, bindhost[i], hints) == 0) { rpcbreg = 1; rpcbregcnt++; if ((tcpsock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { syslog(LOG_ERR, "can't create tcp socket"); nfsd_exit(1); } if (setsockopt(tcpsock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) syslog(LOG_ERR, "setsockopt SO_REUSEADDR: %m"); if (bind(tcpsock, ai_tcp->ai_addr, ai_tcp->ai_addrlen) < 0) { syslog(LOG_ERR, "can't bind tcp addr %s: %m", bindhost[i]); nfsd_exit(1); } if (listen(tcpsock, -1) < 0) { syslog(LOG_ERR, "listen failed"); nfsd_exit(1); } freeaddrinfo(ai_tcp); FD_SET(tcpsock, &sockbits); maxsock = tcpsock; connect_type_cnt++; } } if (rpcbreg == 1) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; ecode = getaddrinfo(NULL, "nfs", &hints, &ai_tcp); if (ecode != 0) { syslog(LOG_ERR, "getaddrinfo tcp: %s", gai_strerror(ecode)); nfsd_exit(1); } nconf_tcp = getnetconfigent("tcp"); if (nconf_tcp == NULL) { syslog(LOG_ERR, "getnetconfigent tcp failed"); nfsd_exit(1); } nb_tcp.buf = ai_tcp->ai_addr; nb_tcp.len = nb_tcp.maxlen = ai_tcp->ai_addrlen; if (nfs_minvers == NFS_VER2) if (!rpcb_set(NFS_PROGRAM, 2, nconf_tcp, &nb_tcp)) { syslog(LOG_ERR, "rpcb_set tcp failed"); nfsd_exit(1); } if (nfs_minvers <= NFS_VER3) if (!rpcb_set(NFS_PROGRAM, 3, nconf_tcp, &nb_tcp)) { syslog(LOG_ERR, "rpcb_set tcp failed"); nfsd_exit(1); } freeaddrinfo(ai_tcp); } } /* Set up the socket for tcp6 and rpcb register it. */ if (tcpflag && ip6flag) { rpcbreg = 0; for (i = 0; i < bindhostc; i++) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; if (setbindhost(&ai_tcp6, bindhost[i], hints) == 0) { rpcbreg = 1; rpcbregcnt++; if ((tcp6sock = socket(ai_tcp6->ai_family, ai_tcp6->ai_socktype, ai_tcp6->ai_protocol)) < 0) { syslog(LOG_ERR, "can't create tcp6 socket"); nfsd_exit(1); } if (setsockopt(tcp6sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) syslog(LOG_ERR, "setsockopt SO_REUSEADDR: %m"); if (setsockopt(tcp6sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof on) < 0) { syslog(LOG_ERR, "can't set v6-only binding for tcp6 " "socket: %m"); nfsd_exit(1); } if (bind(tcp6sock, ai_tcp6->ai_addr, ai_tcp6->ai_addrlen) < 0) { syslog(LOG_ERR, "can't bind tcp6 addr %s: %m", bindhost[i]); nfsd_exit(1); } if (listen(tcp6sock, -1) < 0) { syslog(LOG_ERR, "listen failed"); nfsd_exit(1); } freeaddrinfo(ai_tcp6); FD_SET(tcp6sock, &sockbits); if (maxsock < tcp6sock) maxsock = tcp6sock; connect_type_cnt++; } } if (rpcbreg == 1) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; ecode = getaddrinfo(NULL, "nfs", &hints, &ai_tcp6); if (ecode != 0) { syslog(LOG_ERR, "getaddrinfo tcp6: %s", gai_strerror(ecode)); nfsd_exit(1); } nconf_tcp6 = getnetconfigent("tcp6"); if (nconf_tcp6 == NULL) { syslog(LOG_ERR, "getnetconfigent tcp6 failed"); nfsd_exit(1); } nb_tcp6.buf = ai_tcp6->ai_addr; nb_tcp6.len = nb_tcp6.maxlen = ai_tcp6->ai_addrlen; if (nfs_minvers == NFS_VER2) if (!rpcb_set(NFS_PROGRAM, 2, nconf_tcp6, &nb_tcp6)) { syslog(LOG_ERR, "rpcb_set tcp6 failed"); nfsd_exit(1); } if (nfs_minvers <= NFS_VER3) if (!rpcb_set(NFS_PROGRAM, 3, nconf_tcp6, &nb_tcp6)) { syslog(LOG_ERR, "rpcb_set tcp6 failed"); nfsd_exit(1); } freeaddrinfo(ai_tcp6); } } if (rpcbregcnt == 0) { syslog(LOG_ERR, "rpcb_set() failed, nothing to do: %m"); nfsd_exit(1); } if (tcpflag && connect_type_cnt == 0) { syslog(LOG_ERR, "tcp connects == 0, nothing to do: %m"); nfsd_exit(1); } setproctitle("master"); /* * We always want a master to have a clean way to shut nfsd down * (with unregistration): if the master is killed, it unregisters and * kills all children. If we run for UDP only (and so do not have to * loop waiting for accept), we instead make the parent * a "server" too. start_server will not return. */ if (!tcpflag) start_server(1, &nfsdargs, vhostname); /* * Loop forever accepting connections and passing the sockets * into the kernel for the mounts. */ for (;;) { ready = sockbits; if (connect_type_cnt > 1) { if (select(maxsock + 1, &ready, NULL, NULL, NULL) < 1) { error = errno; if (error == EINTR) continue; syslog(LOG_ERR, "select failed: %m"); nfsd_exit(1); } } for (tcpsock = 0; tcpsock <= maxsock; tcpsock++) { if (FD_ISSET(tcpsock, &ready)) { len = sizeof(peer); if ((msgsock = accept(tcpsock, (struct sockaddr *)&peer, &len)) < 0) { error = errno; syslog(LOG_ERR, "accept failed: %m"); if (error == ECONNABORTED || error == EINTR) continue; nfsd_exit(1); } if (setsockopt(msgsock, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on)) < 0) syslog(LOG_ERR, "setsockopt SO_KEEPALIVE: %m"); addsockargs.sock = msgsock; addsockargs.name = (caddr_t)&peer; addsockargs.namelen = len; nfssvc(nfssvc_addsock, &addsockargs); (void)close(msgsock); } } } } static int setbindhost(struct addrinfo **ai, const char *bindhost, struct addrinfo hints) { int ecode; u_int32_t host_addr[4]; /* IPv4 or IPv6 */ const char *hostptr; if (bindhost == NULL || strcmp("*", bindhost) == 0) hostptr = NULL; else hostptr = bindhost; if (hostptr != NULL) { switch (hints.ai_family) { case AF_INET: if (inet_pton(AF_INET, hostptr, host_addr) == 1) { hints.ai_flags = AI_NUMERICHOST; } else { if (inet_pton(AF_INET6, hostptr, host_addr) == 1) return (1); } break; case AF_INET6: if (inet_pton(AF_INET6, hostptr, host_addr) == 1) { hints.ai_flags = AI_NUMERICHOST; } else { if (inet_pton(AF_INET, hostptr, host_addr) == 1) return (1); } break; default: break; } } ecode = getaddrinfo(hostptr, "nfs", &hints, ai); if (ecode != 0) { syslog(LOG_ERR, "getaddrinfo %s: %s", bindhost, gai_strerror(ecode)); return (1); } return (0); } static void set_nfsdcnt(int proposed) { if (proposed < 1) { warnx("nfsd count too low %d; reset to %d", proposed, DEFNFSDCNT); nfsdcnt = DEFNFSDCNT; } else if (proposed > MAXNFSDCNT) { warnx("nfsd count too high %d; truncated to %d", proposed, MAXNFSDCNT); nfsdcnt = MAXNFSDCNT; } else nfsdcnt = proposed; nfsdcnt_set = 1; } static void usage(void) { (void)fprintf(stderr, "%s", getopt_usage); exit(1); } static void nonfs(__unused int signo) { syslog(LOG_ERR, "missing system call: NFS not available"); } static void reapchild(__unused int signo) { pid_t pid; int i; while ((pid = wait3(NULL, WNOHANG, NULL)) > 0) { for (i = 0; i < nfsdcnt; i++) if (pid == children[i]) children[i] = -1; } } static void unregistration(void) { if ((nfs_minvers == NFS_VER2 && !rpcb_unset(NFS_PROGRAM, 2, NULL)) || (nfs_minvers <= NFS_VER3 && !rpcb_unset(NFS_PROGRAM, 3, NULL))) syslog(LOG_ERR, "rpcb_unset failed"); } static void killchildren(void) { int i; for (i = 0; i < nfsdcnt; i++) { if (children[i] > 0) kill(children[i], SIGKILL); } } /* * Cleanup master after SIGUSR1. */ static void cleanup(__unused int signo) { nfsd_exit(0); } /* * Cleanup child after SIGUSR1. */ static void child_cleanup(__unused int signo) { exit(0); } static void nfsd_exit(int status) { killchildren(); unregistration(); + if (masterpidfh != NULL) + pidfile_remove(masterpidfh); exit(status); } static int get_tuned_nfsdcount(void) { int ncpu, error, tuned_nfsdcnt; size_t ncpu_size; ncpu_size = sizeof(ncpu); error = sysctlbyname("hw.ncpu", &ncpu, &ncpu_size, NULL, 0); if (error) { warnx("sysctlbyname(hw.ncpu) failed defaulting to %d nfs servers", DEFNFSDCNT); tuned_nfsdcnt = DEFNFSDCNT; } else { tuned_nfsdcnt = ncpu * 8; } return tuned_nfsdcnt; } static void start_server(int master, struct nfsd_nfsd_args *nfsdargp, const char *vhost) { char principal[MAXHOSTNAMELEN + 5]; int status, error; char hostname[MAXHOSTNAMELEN + 1], *cp; struct addrinfo *aip, hints; status = 0; if (vhost == NULL) gethostname(hostname, sizeof (hostname)); else strlcpy(hostname, vhost, sizeof (hostname)); snprintf(principal, sizeof (principal), "nfs@%s", hostname); if ((cp = strchr(hostname, '.')) == NULL || *(cp + 1) == '\0') { /* If not fully qualified, try getaddrinfo() */ memset((void *)&hints, 0, sizeof (hints)); hints.ai_flags = AI_CANONNAME; error = getaddrinfo(hostname, NULL, &hints, &aip); if (error == 0) { if (aip->ai_canonname != NULL && (cp = strchr(aip->ai_canonname, '.')) != NULL && *(cp + 1) != '\0') snprintf(principal, sizeof (principal), "nfs@%s", aip->ai_canonname); freeaddrinfo(aip); } } nfsdargp->principal = principal; if (nfsdcnt_set) nfsdargp->minthreads = nfsdargp->maxthreads = nfsdcnt; else { nfsdargp->minthreads = minthreads_set ? minthreads : get_tuned_nfsdcount(); nfsdargp->maxthreads = maxthreads_set ? maxthreads : nfsdargp->minthreads; if (nfsdargp->maxthreads < nfsdargp->minthreads) nfsdargp->maxthreads = nfsdargp->minthreads; } error = nfssvc(nfssvc_nfsd, nfsdargp); if (error < 0 && errno == EAUTH) { /* * This indicates that it could not register the * rpcsec_gss credentials, usually because the * gssd daemon isn't running. * (only the experimental server with nfsv4) */ syslog(LOG_ERR, "No gssd, using AUTH_SYS only"); principal[0] = '\0'; error = nfssvc(nfssvc_nfsd, nfsdargp); } if (error < 0) { if (errno == ENXIO) { syslog(LOG_ERR, "Bad -p option, cannot run"); if (masterpid != 0 && master == 0) kill(masterpid, SIGUSR1); } else syslog(LOG_ERR, "nfssvc: %m"); status = 1; } if (master) nfsd_exit(status); else exit(status); } /* * Open the stable restart file and return the file descriptor for it. */ static void open_stable(int *stable_fdp, int *backup_fdp) { int stable_fd, backup_fd = -1, ret; struct stat st, backup_st; /* Open and stat the stable restart file. */ stable_fd = open(NFSD_STABLERESTART, O_RDWR, 0); if (stable_fd < 0) stable_fd = open(NFSD_STABLERESTART, O_RDWR | O_CREAT, 0600); if (stable_fd >= 0) { ret = fstat(stable_fd, &st); if (ret < 0) { close(stable_fd); stable_fd = -1; } } /* Open and stat the backup stable restart file. */ if (stable_fd >= 0) { backup_fd = open(NFSD_STABLEBACKUP, O_RDWR, 0); if (backup_fd < 0) backup_fd = open(NFSD_STABLEBACKUP, O_RDWR | O_CREAT, 0600); if (backup_fd >= 0) { ret = fstat(backup_fd, &backup_st); if (ret < 0) { close(backup_fd); backup_fd = -1; } } if (backup_fd < 0) { close(stable_fd); stable_fd = -1; } } *stable_fdp = stable_fd; *backup_fdp = backup_fd; if (stable_fd < 0) return; /* Sync up the 2 files, as required. */ if (st.st_size > 0) copy_stable(stable_fd, backup_fd); else if (backup_st.st_size > 0) copy_stable(backup_fd, stable_fd); } /* * Copy the stable restart file to the backup or vice versa. */ static void copy_stable(int from_fd, int to_fd) { int cnt, ret; static char buf[1024]; ret = lseek(from_fd, (off_t)0, SEEK_SET); if (ret >= 0) ret = lseek(to_fd, (off_t)0, SEEK_SET); if (ret >= 0) ret = ftruncate(to_fd, (off_t)0); if (ret >= 0) do { cnt = read(from_fd, buf, 1024); if (cnt > 0) ret = write(to_fd, buf, cnt); else if (cnt < 0) ret = cnt; } while (cnt > 0 && ret >= 0); if (ret >= 0) ret = fsync(to_fd); if (ret < 0) syslog(LOG_ERR, "stable restart copy failure: %m"); } /* * Back up the stable restart file when indicated by the kernel. */ static void backup_stable(__unused int signo) { if (stablefd >= 0) copy_stable(stablefd, backupfd); } /* * Parse the pNFS string and extract the DS servers and ports numbers. */ static void parse_dsserver(const char *optionarg, struct nfsd_nfsd_args *nfsdargp) { char *cp, *cp2, *dsaddr, *dshost, *dspath, *dsvol, nfsprt[9]; char *mdspath, *mdsp, ip6[INET6_ADDRSTRLEN]; const char *ad; int ecode; u_int adsiz, dsaddrcnt, dshostcnt, dspathcnt, hostsiz, pathsiz; u_int mdspathcnt; size_t dsaddrsiz, dshostsiz, dspathsiz, nfsprtsiz, mdspathsiz; struct addrinfo hints, *ai_tcp, *res; struct sockaddr_in sin; struct sockaddr_in6 sin6; cp = strdup(optionarg); if (cp == NULL) errx(1, "Out of memory"); /* Now, do the host names. */ dspathsiz = 1024; dspathcnt = 0; dspath = malloc(dspathsiz); if (dspath == NULL) errx(1, "Out of memory"); dshostsiz = 1024; dshostcnt = 0; dshost = malloc(dshostsiz); if (dshost == NULL) errx(1, "Out of memory"); dsaddrsiz = 1024; dsaddrcnt = 0; dsaddr = malloc(dsaddrsiz); if (dsaddr == NULL) errx(1, "Out of memory"); mdspathsiz = 1024; mdspathcnt = 0; mdspath = malloc(mdspathsiz); if (mdspath == NULL) errx(1, "Out of memory"); /* Put the NFS port# in "." form. */ snprintf(nfsprt, 9, ".%d.%d", 2049 >> 8, 2049 & 0xff); nfsprtsiz = strlen(nfsprt); ai_tcp = NULL; /* Loop around for each DS server name. */ do { cp2 = strchr(cp, ','); if (cp2 != NULL) { /* Not the last DS in the list. */ *cp2++ = '\0'; if (*cp2 == '\0') usage(); } dsvol = strchr(cp, ':'); if (dsvol == NULL || *(dsvol + 1) == '\0') usage(); *dsvol++ = '\0'; /* Optional path for MDS file system to be stored on DS. */ mdsp = strchr(dsvol, '#'); if (mdsp != NULL) { if (*(mdsp + 1) == '\0' || mdsp <= dsvol) usage(); *mdsp++ = '\0'; } /* Append this pathname to dspath. */ pathsiz = strlen(dsvol); if (dspathcnt + pathsiz + 1 > dspathsiz) { dspathsiz *= 2; dspath = realloc(dspath, dspathsiz); if (dspath == NULL) errx(1, "Out of memory"); } strcpy(&dspath[dspathcnt], dsvol); dspathcnt += pathsiz + 1; /* Append this pathname to mdspath. */ if (mdsp != NULL) pathsiz = strlen(mdsp); else pathsiz = 0; if (mdspathcnt + pathsiz + 1 > mdspathsiz) { mdspathsiz *= 2; mdspath = realloc(mdspath, mdspathsiz); if (mdspath == NULL) errx(1, "Out of memory"); } if (mdsp != NULL) strcpy(&mdspath[mdspathcnt], mdsp); else mdspath[mdspathcnt] = '\0'; mdspathcnt += pathsiz + 1; if (ai_tcp != NULL) freeaddrinfo(ai_tcp); /* Get the fully qualified domain name and IP address. */ memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG; hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; ecode = getaddrinfo(cp, NULL, &hints, &ai_tcp); if (ecode != 0) err(1, "getaddrinfo pnfs: %s %s", cp, gai_strerror(ecode)); ad = NULL; for (res = ai_tcp; res != NULL; res = res->ai_next) { if (res->ai_addr->sa_family == AF_INET) { if (res->ai_addrlen < sizeof(sin)) err(1, "getaddrinfo() returned " "undersized IPv4 address"); /* * Mips cares about sockaddr_in alignment, * so copy the address. */ memcpy(&sin, res->ai_addr, sizeof(sin)); ad = inet_ntoa(sin.sin_addr); break; } else if (res->ai_family == AF_INET6) { if (res->ai_addrlen < sizeof(sin6)) err(1, "getaddrinfo() returned " "undersized IPv6 address"); /* * Mips cares about sockaddr_in6 alignment, * so copy the address. */ memcpy(&sin6, res->ai_addr, sizeof(sin6)); ad = inet_ntop(AF_INET6, &sin6.sin6_addr, ip6, sizeof(ip6)); /* * XXX * Since a link local address will only * work if the client and DS are in the * same scope zone, only use it if it is * the only address. */ if (ad != NULL && !IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) break; } } if (ad == NULL) err(1, "No IP address for %s", cp); /* Append this address to dsaddr. */ adsiz = strlen(ad); if (dsaddrcnt + adsiz + nfsprtsiz + 1 > dsaddrsiz) { dsaddrsiz *= 2; dsaddr = realloc(dsaddr, dsaddrsiz); if (dsaddr == NULL) errx(1, "Out of memory"); } strcpy(&dsaddr[dsaddrcnt], ad); strcat(&dsaddr[dsaddrcnt], nfsprt); dsaddrcnt += adsiz + nfsprtsiz + 1; /* Append this hostname to dshost. */ hostsiz = strlen(ai_tcp->ai_canonname); if (dshostcnt + hostsiz + 1 > dshostsiz) { dshostsiz *= 2; dshost = realloc(dshost, dshostsiz); if (dshost == NULL) errx(1, "Out of memory"); } strcpy(&dshost[dshostcnt], ai_tcp->ai_canonname); dshostcnt += hostsiz + 1; cp = cp2; } while (cp != NULL); nfsdargp->addr = dsaddr; nfsdargp->addrlen = dsaddrcnt; nfsdargp->dnshost = dshost; nfsdargp->dnshostlen = dshostcnt; nfsdargp->dspath = dspath; nfsdargp->dspathlen = dspathcnt; nfsdargp->mdspath = mdspath; nfsdargp->mdspathlen = mdspathcnt; freeaddrinfo(ai_tcp); } diff --git a/usr.sbin/rpcbind/Makefile b/usr.sbin/rpcbind/Makefile index 1bb2d7584ed2..eadadc71310e 100644 --- a/usr.sbin/rpcbind/Makefile +++ b/usr.sbin/rpcbind/Makefile @@ -1,30 +1,32 @@ # $NetBSD: Makefile,v 1.3 2000/06/20 13:56:43 fvdl Exp $ .include PROG= rpcbind MAN= rpcbind.8 SRCS= check_bound.c rpcb_stat.c rpcb_svc_4.c rpcbind.c pmap_svc.c \ rpcb_svc.c rpcb_svc_com.c security.c warmstart.c util.c CFLAGS+= -DPORTMAP +LIBADD= util + .if ${MK_INET6_SUPPORT} != "no" CFLAGS+= -DINET6 .endif .if ${MK_RPCBIND_WARMSTART_SUPPORT} != "no" CFLAGS+= -DWARMSTART .endif .if ${MK_TCP_WRAPPERS} != "no" CFLAGS+= -DLIBWRAP LIBADD+= wrap .endif HAS_TESTS= SUBDIR.${MK_TESTS}+= tests WARNS?= 1 .include diff --git a/usr.sbin/rpcbind/Makefile.depend b/usr.sbin/rpcbind/Makefile.depend index 732a025c9552..7e5c47e39608 100644 --- a/usr.sbin/rpcbind/Makefile.depend +++ b/usr.sbin/rpcbind/Makefile.depend @@ -1,18 +1,19 @@ # Autogenerated - do NOT edit! DIRDEPS = \ include \ include/arpa \ include/rpc \ include/rpcsvc \ include/xlocale \ lib/${CSU_DIR} \ lib/libc \ lib/libcompiler_rt \ + lib/libutil \ .include .if ${DEP_RELDIR} == ${_DEP_RELDIR} # local dependencies - needed for -jN in clean tree .endif diff --git a/usr.sbin/rpcbind/rpcbind.8 b/usr.sbin/rpcbind/rpcbind.8 index 36c7a8da9984..0132c8f6a5d0 100644 --- a/usr.sbin/rpcbind/rpcbind.8 +++ b/usr.sbin/rpcbind/rpcbind.8 @@ -1,185 +1,189 @@ .\" Copyright 1989 AT&T .\" Copyright 1991 Sun Microsystems, Inc. -.Dd July 11, 2024 +.Dd May 30, 2025 .Dt RPCBIND 8 .Os .Sh NAME .Nm rpcbind .Nd universal addresses to RPC program number mapper .Sh SYNOPSIS .Nm -.Op Fl 6adIiLlNswW +.Op Fl 6adIiLlNPswW .Op Fl h Ar bindip .Sh DESCRIPTION The .Nm utility is a server that converts .Tn RPC program numbers into universal addresses. It must be running on the host to be able to make .Tn RPC calls on a server on that machine. .Pp When an .Tn RPC service is started, it tells .Nm the address at which it is listening, and the .Tn RPC program numbers it is prepared to serve. When a client wishes to make an .Tn RPC call to a given program number, it first contacts .Nm on the server machine to determine the address where .Tn RPC requests should be sent. .Pp The .Nm utility should be started before any other RPC service. Normally, standard .Tn RPC servers are started by port monitors, so .Nm must be started before port monitors are invoked. .Pp When .Nm is started, it checks that certain name-to-address translation-calls function correctly. If they fail, the network configuration databases may be corrupt. Since .Tn RPC services cannot function correctly in this situation, .Nm reports the condition and terminates. .Pp The .Nm utility can only be started by the super-user. .Sh OPTIONS .Bl -tag -width indent .It Fl 6 Bind to AF_INET6 (IPv6) addresses only. .It Fl a When debugging .Pq Fl d , do an abort on errors. .It Fl d Run in debug mode. In this mode, .Nm will not fork when it starts, will print additional information during operation, and will abort on certain errors if .Fl a is also specified. With this option, the name-to-address translation consistency checks are shown in detail. .It Fl h Ar bindip IP addresses to bind to when servicing TCP and UDP requests. This option may be specified multiple times and is typically necessary when running on a multi-homed host. If no .Fl h option is specified, .Nm will bind to .Dv INADDR_ANY , which could lead to problems on a multi-homed host due to .Nm returning a UDP packet from a different IP address than it was sent to. Note that when specifying IP addresses with .Fl h , and no .Fl I option is specified, .Nm will automatically add .Li 127.0.0.1 and if IPv6 is enabled, .Li ::1 to the list. .It Fl I Cause .Nm not to bind to the localhost when .Fl h has been specified. .It Fl i .Dq Insecure mode. Allow calls to SET and UNSET from any host. Normally .Nm accepts these requests only from the loopback interface for security reasons. This change is necessary for programs that were compiled with earlier versions of the rpc library and do not make those requests using the loopback interface. .It Fl L Allow old-style local connections over the loopback interface. Without this flag, local connections are only allowed over a local socket, .Pa /var/run/rpcbind.sock . .It Fl l Turn on libwrap connection logging. .It Fl N Run in foreground mode. In this mode, .Nm will not fork when it starts. +.It Fl P +Specify alternative location of a file where main process PID will be stored. +The default location is +.Pa /var/run/rpcbind.pid . .It Fl s Cause .Nm to change to the user daemon as soon as possible. This causes .Nm to use non-privileged ports for outgoing connections, preventing non-privileged clients from using .Nm to connect to services from a privileged port. .It Fl W Enable libwrap (TCP wrappers) support. .It Fl w Enable the warmstart feature. .Pp The warmstart feature saves RPC registrations on termination. Any saved RPC registrations are restored on restart if .Fl w is specified. This feature helps avoid RPC service interruption when restarting .Nm . warmstart support must be compiled in to .Nm . Portmap registrations are stored in .Pa /tmp/portmap.file . .Nm registrations are stored in .Pa /tmp/rpcbind.file . .El .Sh NOTES All RPC servers must be restarted if .Nm is restarted. .Sh FILES .Bl -tag -width /var/run/rpcbind.sock -compact .It Pa /tmp/portmap.file saved portmap registrations file. .It Pa /tmp/rpcbind.file saved .Nm registrations file. .It Pa /var/run/rpcbind.sock socket used for local connections. .El .Sh SEE ALSO .Xr rpcbind 3 , .Xr netconfig 5 , .Xr rpcinfo 8 diff --git a/usr.sbin/rpcbind/rpcbind.c b/usr.sbin/rpcbind/rpcbind.c index 622e954f903b..d165bcd9fbfc 100644 --- a/usr.sbin/rpcbind/rpcbind.c +++ b/usr.sbin/rpcbind/rpcbind.c @@ -1,902 +1,939 @@ /* $NetBSD: rpcbind.c,v 1.3 2002/11/08 00:16:40 fvdl Exp $ */ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2009, Sun Microsystems, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - 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. * - Neither the name of Sun Microsystems, Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. */ /* * Copyright (c) 1984 - 1991 by Sun Microsystems, Inc. */ /* * rpcbind.c * Implements the program, version to address mapping for rpc. * */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PORTMAP #include #endif #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include "rpcbind.h" /* Global variables */ int debugging = 0; /* Tell me what's going on */ int doabort = 0; /* When debugging, do an abort on errors */ int terminate_rfd; /* Pipefd to wake on signal */ volatile sig_atomic_t doterminate = 0; /* Terminal signal received */ rpcblist_ptr list_rbl; /* A list of version 3/4 rpcbind services */ int rpcbindlockfd; /* who to suid to if -s is given */ #define RUN_AS "daemon" #define RPCBINDDLOCK "/var/run/rpcbind.lock" +#define DEFAULT_PIDFILE "/var/run/rpcbind.pid" + +char *pidfile_path = DEFAULT_PIDFILE; +struct pidfh *pidfh = NULL; + static int runasdaemon = 0; int insecure = 0; int oldstyle_local = 0; #ifdef LIBWRAP int libwrap = 0; #endif int nofork = 0; int verboselog = 0; int nobind_localhost = 0; static char **hosts = NULL; static struct sockaddr **bound_sa; static int ipv6_only = 0; static int nhosts = 0; static int on = 1; static int terminate_wfd; #ifdef WARMSTART /* Local Variable */ static int warmstart = 0; /* Grab an old copy of registrations. */ #endif #ifdef PORTMAP struct pmaplist *list_pml; /* A list of version 2 rpcbind services */ char *udptrans; /* Name of UDP transport */ char *tcptrans; /* Name of TCP transport */ char *udp_uaddr; /* Universal UDP address */ char *tcp_uaddr; /* Universal TCP address */ #endif static char servname[] = "rpcbind"; static char superuser[] = "superuser"; static char nlname[] = "netlink"; static struct netconfig netlink_nconf = { .nc_netid = nlname, .nc_semantics = NC_TPI_CLTS, }; static struct t_bind netlink_taddr = { .addr = { .maxlen = sizeof(nlname), .len = sizeof(nlname), .buf = nlname, }, }; static int init_transport(struct netconfig *); static void rbllist_add(rpcprog_t, rpcvers_t, struct netconfig *, struct netbuf *); +static void cleanup_pidfile(void); static void terminate(int); static void parseargs(int, char *[]); static void update_bound_sa(void); int main(int argc, char *argv[]) { struct netconfig *nconf; void *nc_handle; /* Net config handle */ struct rlimit rl; int maxrec = RPC_MAXDATASIZE; int error, fds[2]; parseargs(argc, argv); update_bound_sa(); /* Ensure krpc is loaded */ if (modfind("krpc") < 0 && kldload("krpc") < 0) err(1, "krpc"); /* Check that another rpcbind isn't already running. */ if ((rpcbindlockfd = open(RPCBINDDLOCK, O_RDONLY|O_CREAT, 0444)) < 0) err(1, "%s", RPCBINDDLOCK); if (flock(rpcbindlockfd, LOCK_EX|LOCK_NB) != 0 && errno == EWOULDBLOCK) errx(1, "another rpcbind is already running. Aborting"); + if (pidfile_path != NULL) { + pidfh = pidfile_open(pidfile_path, 0600, NULL); + if (pidfh == NULL) + warn("cannot open pid file"); + atexit(cleanup_pidfile); + } + getrlimit(RLIMIT_NOFILE, &rl); if (rl.rlim_cur < 128) { if (rl.rlim_max <= 128) rl.rlim_cur = rl.rlim_max; else rl.rlim_cur = 128; setrlimit(RLIMIT_NOFILE, &rl); } openlog("rpcbind", LOG_CONS, LOG_DAEMON); if (geteuid()) { /* This command allowed only to root */ fprintf(stderr, "Sorry. You are not superuser\n"); exit(1); } nc_handle = setnetconfig(); /* open netconfig file */ if (nc_handle == NULL) { syslog(LOG_ERR, "could not read /etc/netconfig"); exit(1); } #ifdef PORTMAP udptrans = ""; tcptrans = ""; #endif nconf = getnetconfigent("local"); if (nconf == NULL) nconf = getnetconfigent("unix"); if (nconf == NULL) { syslog(LOG_ERR, "%s: can't find local transport\n", argv[0]); exit(1); } rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec); init_transport(nconf); while ((nconf = getnetconfig(nc_handle))) { if (nconf->nc_flag & NC_VISIBLE) { if (ipv6_only == 1 && strcmp(nconf->nc_protofmly, "inet") == 0) { /* DO NOTHING */ } else init_transport(nconf); } } endnetconfig(nc_handle); init_transport(&netlink_nconf); /* * Allocate pipe fd to wake main thread from signal handler in non-racy * way. */ error = pipe(fds); if (error != 0) err(1, "pipe failed"); terminate_rfd = fds[0]; terminate_wfd = fds[1]; /* catch the usual termination signals for graceful exit */ (void) signal(SIGCHLD, reap); (void) signal(SIGINT, terminate); (void) signal(SIGTERM, terminate); (void) signal(SIGQUIT, terminate); /* ignore others that could get sent */ (void) signal(SIGPIPE, SIG_IGN); (void) signal(SIGHUP, SIG_IGN); (void) signal(SIGUSR1, SIG_IGN); (void) signal(SIGUSR2, SIG_IGN); #ifdef WARMSTART if (warmstart) { read_warmstart(); } #endif if (debugging) { printf("rpcbind debugging enabled."); if (doabort) { printf(" Will abort on errors!\n"); } else { printf("\n"); } } else if (!nofork) { if (daemon(0, 0)) err(1, "fork failed"); } + if (pidfh != NULL && pidfile_write(pidfh) != 0) + syslog(LOG_ERR, "pidfile_write(): %m"); + if (runasdaemon) { struct passwd *p; if((p = getpwnam(RUN_AS)) == NULL) { syslog(LOG_ERR, "cannot get uid of daemon: %m"); exit(1); } if (setuid(p->pw_uid) == -1) { syslog(LOG_ERR, "setuid to daemon failed: %m"); exit(1); } } network_init(); my_svc_run(); syslog(LOG_ERR, "svc_run returned unexpectedly"); rpcbind_abort(); /* NOTREACHED */ return 0; } /* * Adds the entry into the rpcbind database. * If PORTMAP, then for UDP and TCP, it adds the entries for version 2 also * Returns 0 if succeeds, else fails */ static int init_transport(struct netconfig *nconf) { int fd = -1; struct t_bind taddr; struct addrinfo hints, *res = NULL; struct __rpc_sockinfo si; SVCXPRT *my_xprt = NULL; int status; /* bound checking ? */ int aicode; int addrlen; int nhostsbak; int bound; struct sockaddr *sa; u_int32_t host_addr[4]; /* IPv4 or IPv6 */ struct sockaddr_un sun; mode_t oldmask; bool local, netlink; local = strcmp(nconf->nc_netid, "local") == 0 || strcmp(nconf->nc_netid, "unix") == 0; netlink = strcmp(nconf->nc_netid, "netlink") == 0; if ((nconf->nc_semantics != NC_TPI_CLTS) && (nconf->nc_semantics != NC_TPI_COTS) && (nconf->nc_semantics != NC_TPI_COTS_ORD)) return (1); /* not my type */ #ifdef ND_DEBUG if (debugging) { int i; char **s; (void)fprintf(stderr, "%s: %ld lookup routines :\n", nconf->nc_netid, nconf->nc_nlookups); for (i = 0, s = nconf->nc_lookups; i < nconf->nc_nlookups; i++, s++) fprintf(stderr, "[%d] - %s\n", i, *s); } #endif /* * XXX - using RPC library internal functions. */ if (local) { /* * For other transports we call this later, for each socket we * like to bind. */ if ((fd = __rpc_nconf2fd(nconf)) < 0) { int non_fatal = 0; if (errno == EAFNOSUPPORT) non_fatal = 1; syslog(non_fatal?LOG_DEBUG:LOG_ERR, "cannot create socket for %s", nconf->nc_netid); return (1); } } if (!__rpc_nconf2sockinfo(nconf, &si)) { syslog(LOG_ERR, "cannot get information for %s", nconf->nc_netid); return (1); } if (local) { memset(&sun, 0, sizeof sun); sun.sun_family = AF_LOCAL; unlink(_PATH_RPCBINDSOCK); strcpy(sun.sun_path, _PATH_RPCBINDSOCK); sun.sun_len = SUN_LEN(&sun); addrlen = sizeof (struct sockaddr_un); sa = (struct sockaddr *)&sun; } else if (!netlink) { /* Get rpcbind's address on this transport */ memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = si.si_af; hints.ai_socktype = si.si_socktype; hints.ai_protocol = si.si_proto; } if (!local && !netlink) { /* * If no hosts were specified, just bind to INADDR_ANY. * Otherwise make sure 127.0.0.1 is added to the list. */ nhostsbak = nhosts + 1; hosts = realloc(hosts, nhostsbak * sizeof(char *)); if (nhostsbak == 1) hosts[0] = "*"; else { if (hints.ai_family == AF_INET && nobind_localhost == 0) { hosts[nhostsbak - 1] = "127.0.0.1"; } else if (hints.ai_family == AF_INET6 && nobind_localhost == 0) { hosts[nhostsbak - 1] = "::1"; } else return 1; } /* * Bind to specific IPs if asked to */ bound = 0; while (nhostsbak > 0) { --nhostsbak; /* * XXX - using RPC library internal functions. */ if ((fd = __rpc_nconf2fd(nconf)) < 0) { int non_fatal = 0; if (errno == EAFNOSUPPORT && nconf->nc_semantics != NC_TPI_CLTS) non_fatal = 1; syslog(non_fatal ? LOG_DEBUG : LOG_ERR, "cannot create socket for %s", nconf->nc_netid); return (1); } switch (hints.ai_family) { case AF_INET: if (inet_pton(AF_INET, hosts[nhostsbak], host_addr) == 1) { hints.ai_flags &= AI_NUMERICHOST; } else { /* * Skip if we have an AF_INET6 address. */ if (inet_pton(AF_INET6, hosts[nhostsbak], host_addr) == 1) { close(fd); continue; } } break; case AF_INET6: if (inet_pton(AF_INET6, hosts[nhostsbak], host_addr) == 1) { hints.ai_flags &= AI_NUMERICHOST; } else { /* * Skip if we have an AF_INET address. */ if (inet_pton(AF_INET, hosts[nhostsbak], host_addr) == 1) { close(fd); continue; } } if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof on) < 0) { syslog(LOG_ERR, "can't set v6-only binding for " "ipv6 socket: %m"); continue; } break; default: break; } /* * If no hosts were specified, just bind to INADDR_ANY */ if (strcmp("*", hosts[nhostsbak]) == 0) hosts[nhostsbak] = NULL; if ((aicode = getaddrinfo(hosts[nhostsbak], servname, &hints, &res)) != 0) { syslog(LOG_ERR, "cannot get local address for %s: %s", nconf->nc_netid, gai_strerror(aicode)); continue; } addrlen = res->ai_addrlen; sa = (struct sockaddr *)res->ai_addr; oldmask = umask(S_IXUSR|S_IXGRP|S_IXOTH); if (bind(fd, sa, addrlen) != 0) { syslog(LOG_ERR, "cannot bind %s on %s: %m", (hosts[nhostsbak] == NULL) ? "*" : hosts[nhostsbak], nconf->nc_netid); if (res != NULL) freeaddrinfo(res); continue; } else bound = 1; (void)umask(oldmask); /* Copy the address */ taddr.addr.len = taddr.addr.maxlen = addrlen; taddr.addr.buf = malloc(addrlen); if (taddr.addr.buf == NULL) { syslog(LOG_ERR, "cannot allocate memory for %s address", nconf->nc_netid); if (res != NULL) freeaddrinfo(res); return 1; } memcpy(taddr.addr.buf, sa, addrlen); #ifdef ND_DEBUG if (debugging) { /* * for debugging print out our universal * address */ char *uaddr; struct netbuf nb; nb.buf = sa; nb.len = nb.maxlen = sa->sa_len; uaddr = taddr2uaddr(nconf, &nb); (void)fprintf(stderr, "rpcbind : my address is %s\n", uaddr); (void)free(uaddr); } #endif if (nconf->nc_semantics != NC_TPI_CLTS) listen(fd, SOMAXCONN); my_xprt = (SVCXPRT *)svc_tli_create(fd, nconf, &taddr, RPC_MAXDATASIZE, RPC_MAXDATASIZE); } } else if (local) { oldmask = umask(S_IXUSR|S_IXGRP|S_IXOTH); if (bind(fd, sa, addrlen) < 0) { syslog(LOG_ERR, "cannot bind %s: %m", nconf->nc_netid); if (res != NULL) freeaddrinfo(res); return 1; } (void) umask(oldmask); /* Copy the address */ taddr.addr.len = taddr.addr.maxlen = addrlen; taddr.addr.buf = malloc(addrlen); if (taddr.addr.buf == NULL) { syslog(LOG_ERR, "cannot allocate memory for %s address", nconf->nc_netid); if (res != NULL) freeaddrinfo(res); return 1; } memcpy(taddr.addr.buf, sa, addrlen); #ifdef ND_DEBUG if (debugging) { /* for debugging print out our universal address */ char *uaddr; struct netbuf nb; nb.buf = sa; nb.len = nb.maxlen = sa->sa_len; uaddr = taddr2uaddr(nconf, &nb); (void) fprintf(stderr, "rpcbind : my address is %s\n", uaddr); (void) free(uaddr); } #endif if (nconf->nc_semantics != NC_TPI_CLTS) listen(fd, SOMAXCONN); my_xprt = (SVCXPRT *)svc_tli_create(fd, nconf, &taddr, RPC_MAXDATASIZE, RPC_MAXDATASIZE); } else { assert(netlink); taddr = netlink_taddr; my_xprt = svc_nl_create("rpcbind"); } if (my_xprt == (SVCXPRT *)NULL) { syslog(LOG_ERR, "%s: could not create service", nconf->nc_netid); goto error; } #ifdef PORTMAP /* * Register both the versions for tcp/ip, udp/ip and local. */ if (!netlink && (local || (strcmp(nconf->nc_protofmly, NC_INET) == 0 && (strcmp(nconf->nc_proto, NC_TCP) == 0 || strcmp(nconf->nc_proto, NC_UDP) == 0)))) { struct pmaplist *pml; if (!svc_register(my_xprt, PMAPPROG, PMAPVERS, pmap_service, 0)) { syslog(LOG_ERR, "could not register on %s", nconf->nc_netid); goto error; } pml = malloc(sizeof (struct pmaplist)); if (pml == NULL) { syslog(LOG_ERR, "no memory!"); exit(1); } pml->pml_map.pm_prog = PMAPPROG; pml->pml_map.pm_vers = PMAPVERS; pml->pml_map.pm_port = PMAPPORT; if (strcmp(nconf->nc_proto, NC_TCP) == 0) { if (tcptrans[0]) { free(pml); pml = NULL; syslog(LOG_ERR, "cannot have more than one TCP transport"); goto error; } tcptrans = strdup(nconf->nc_netid); pml->pml_map.pm_prot = IPPROTO_TCP; /* Let's snarf the universal address */ /* "h1.h2.h3.h4.p1.p2" */ tcp_uaddr = taddr2uaddr(nconf, &taddr.addr); } else if (strcmp(nconf->nc_proto, NC_UDP) == 0) { if (udptrans[0]) { syslog(LOG_ERR, "cannot have more than one UDP transport"); goto error; } udptrans = strdup(nconf->nc_netid); pml->pml_map.pm_prot = IPPROTO_UDP; /* Let's snarf the universal address */ /* "h1.h2.h3.h4.p1.p2" */ udp_uaddr = taddr2uaddr(nconf, &taddr.addr); } else if (strcmp(nconf->nc_netid, "local") == 0) pml->pml_map.pm_prot = IPPROTO_ST; else if (strcmp(nconf->nc_netid, "unix") == 0) pml->pml_map.pm_prot = IPPROTO_ST; pml->pml_next = list_pml; list_pml = pml; /* Add version 3 information */ pml = malloc(sizeof (struct pmaplist)); if (pml == NULL) { syslog(LOG_ERR, "no memory!"); exit(1); } pml->pml_map = list_pml->pml_map; pml->pml_map.pm_vers = RPCBVERS; pml->pml_next = list_pml; list_pml = pml; /* Add version 4 information */ pml = malloc (sizeof (struct pmaplist)); if (pml == NULL) { syslog(LOG_ERR, "no memory!"); exit(1); } pml->pml_map = list_pml->pml_map; pml->pml_map.pm_vers = RPCBVERS4; pml->pml_next = list_pml; list_pml = pml; /* Also add version 2 stuff to rpcbind list */ rbllist_add(PMAPPROG, PMAPVERS, nconf, &taddr.addr); } #endif /* version 3 registration */ if (!svc_reg(my_xprt, RPCBPROG, RPCBVERS, rpcb_service_3, NULL)) { syslog(LOG_ERR, "could not register %s version 3", nconf->nc_netid); goto error; } rbllist_add(RPCBPROG, RPCBVERS, nconf, &taddr.addr); /* version 4 registration */ if (!svc_reg(my_xprt, RPCBPROG, RPCBVERS4, rpcb_service_4, NULL)) { syslog(LOG_ERR, "could not register %s version 4", nconf->nc_netid); goto error; } rbllist_add(RPCBPROG, RPCBVERS4, nconf, &taddr.addr); /* decide if bound checking works for this transport */ status = add_bndlist(nconf, &taddr.addr); #ifdef BIND_DEBUG if (debugging) { if (status < 0) { fprintf(stderr, "Error in finding bind status for %s\n", nconf->nc_netid); } else if (status == 0) { fprintf(stderr, "check binding for %s\n", nconf->nc_netid); } else if (status > 0) { fprintf(stderr, "No check binding for %s\n", nconf->nc_netid); } } #endif /* * rmtcall only supported on CLTS transports for now. */ if (!netlink && nconf->nc_semantics == NC_TPI_CLTS) { status = create_rmtcall_fd(nconf); #ifdef BIND_DEBUG if (debugging) { if (status < 0) { fprintf(stderr, "Could not create rmtcall fd for %s\n", nconf->nc_netid); } else { fprintf(stderr, "rmtcall fd for %s is %d\n", nconf->nc_netid, status); } } #endif } return (0); error: if (fd != -1) close(fd); return (1); } /* * Create the list of addresses that we're bound to. Normally, this * list is empty because we're listening on the wildcard address * (nhost == 0). If -h is specified on the command line, then * bound_sa will have a list of the addresses that the program binds * to specifically. This function takes that list and converts them to * struct sockaddr * and stores them in bound_sa. */ static void update_bound_sa(void) { struct addrinfo hints, *res = NULL; int i; if (nhosts == 0) return; bound_sa = malloc(sizeof(*bound_sa) * nhosts); memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; for (i = 0; i < nhosts; i++) { if (getaddrinfo(hosts[i], NULL, &hints, &res) != 0) continue; bound_sa[i] = malloc(res->ai_addrlen); memcpy(bound_sa[i], res->ai_addr, res->ai_addrlen); } } /* * Match the sa against the list of addresses we've bound to. If * we've not specifically bound to anything, we match everything. * Otherwise, if the IPv4 or IPv6 address matches one of the addresses * in bound_sa, we return true. If not, we return false. */ int listen_addr(const struct sockaddr *sa) { int i; /* * If nhosts == 0, then there were no -h options on the * command line, so all addresses are addresses we're * listening to. */ if (nhosts == 0) return 1; for (i = 0; i < nhosts; i++) { if (bound_sa[i] == NULL || sa->sa_family != bound_sa[i]->sa_family) continue; switch (sa->sa_family) { case AF_INET: if (memcmp(&SA2SINADDR(sa), &SA2SINADDR(bound_sa[i]), sizeof(struct in_addr)) == 0) return (1); break; #ifdef INET6 case AF_INET6: if (memcmp(&SA2SIN6ADDR(sa), &SA2SIN6ADDR(bound_sa[i]), sizeof(struct in6_addr)) == 0) return (1); break; #endif default: break; } } return (0); } static void rbllist_add(rpcprog_t prog, rpcvers_t vers, struct netconfig *nconf, struct netbuf *addr) { rpcblist_ptr rbl; rbl = malloc(sizeof (rpcblist)); if (rbl == NULL) { syslog(LOG_ERR, "no memory!"); exit(1); } rbl->rpcb_map.r_prog = prog; rbl->rpcb_map.r_vers = vers; rbl->rpcb_map.r_netid = strdup(nconf->nc_netid); rbl->rpcb_map.r_addr = taddr2uaddr(nconf, addr); rbl->rpcb_map.r_owner = strdup(superuser); rbl->rpcb_next = list_rbl; /* Attach to global list */ list_rbl = rbl; } +/* + * atexit callback for pidfh cleanup + */ +static void +cleanup_pidfile(void) +{ + if (pidfh != NULL) + pidfile_remove(pidfh); +} + /* * Catch the signal and die */ static void terminate(int signum) { char c = '\0'; ssize_t wr; doterminate = signum; wr = write(terminate_wfd, &c, 1); - if (wr < 1) + if (wr < 1) { + /* + * The call to cleanup_pidfile should be async-signal safe. + * pidfile_remove calls fstat and funlinkat system calls, and + * we are exiting immediately. + */ + cleanup_pidfile(); _exit(2); + } } void rpcbind_abort(void) { #ifdef WARMSTART write_warmstart(); /* Dump yourself */ #endif abort(); } /* get command line options */ static void parseargs(int argc, char *argv[]) { int c; #ifdef WARMSTART #define WSOP "w" #else #define WSOP "" #endif #ifdef LIBWRAP #define WRAPOP "W" #else #define WRAPOP "" #endif - while ((c = getopt(argc, argv, "6adh:IiLlNs" WRAPOP WSOP)) != -1) { + while ((c = getopt(argc, argv, "6adh:IiLlNP:s" WRAPOP WSOP)) != -1) { switch (c) { case '6': ipv6_only = 1; break; case 'a': doabort = 1; /* when debugging, do an abort on */ break; /* errors; for rpcbind developers */ /* only! */ case 'd': debugging = 1; break; case 'h': ++nhosts; hosts = realloc(hosts, nhosts * sizeof(char *)); if (hosts == NULL) errx(1, "Out of memory"); hosts[nhosts - 1] = strdup(optarg); if (hosts[nhosts - 1] == NULL) errx(1, "Out of memory"); break; case 'I': nobind_localhost = 1; break; case 'i': insecure = 1; break; case 'L': oldstyle_local = 1; break; case 'l': verboselog = 1; break; case 'N': nofork = 1; break; case 's': runasdaemon = 1; break; + case 'P': + pidfile_path = strdup(optarg); + break; #ifdef LIBWRAP case 'W': libwrap = 1; break; #endif #ifdef WARMSTART case 'w': warmstart = 1; break; #endif default: /* error */ fprintf(stderr, - "usage: rpcbind [-6adIiLls%s%s] [-h bindip]\n", + "usage: rpcbind [-6adIiLlNPs%s%s] [-h bindip]\n", WRAPOP, WSOP); exit (1); } } if (doabort && !debugging) { fprintf(stderr, "-a (abort) specified without -d (debugging) -- ignored.\n"); doabort = 0; } #undef WSOP } void reap(int dummy __unused) { int save_errno = errno; while (wait3(NULL, WNOHANG, NULL) > 0) ; errno = save_errno; } void toggle_verboselog(int dummy __unused) { verboselog = !verboselog; }