diff options
Diffstat (limited to 'usr.sbin/rpc.statd')
-rw-r--r-- | usr.sbin/rpc.statd/Makefile | 26 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/Makefile.depend | 32 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/file.c | 361 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/procs.c | 436 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/rpc.statd.8 | 137 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/statd.c | 655 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/statd.h | 111 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/test.c | 144 |
8 files changed, 1902 insertions, 0 deletions
diff --git a/usr.sbin/rpc.statd/Makefile b/usr.sbin/rpc.statd/Makefile new file mode 100644 index 0000000..43504e4 --- /dev/null +++ b/usr.sbin/rpc.statd/Makefile @@ -0,0 +1,26 @@ +# $FreeBSD$ + +PROG= rpc.statd +MAN= rpc.statd.8 +SRCS= file.c sm_inter_svc.c sm_inter.h statd.c procs.c + +CFLAGS+= -I. +WARNS?= 2 + +LIBADD= rpcsvc + +CLEANFILES= sm_inter_svc.c sm_inter.h + +RPCSRC= ${DESTDIR}/usr/include/rpcsvc/sm_inter.x +RPCGEN= RPCGEN_CPP=${CPP:Q} rpcgen -L -C + +sm_inter_svc.c: ${RPCSRC} + ${RPCGEN} -m -o ${.TARGET} ${RPCSRC} + +sm_inter.h: ${RPCSRC} + ${RPCGEN} -h -o ${.TARGET} ${RPCSRC} + +test: test.c + cc -o test test.c -lrpcsvc + +.include <bsd.prog.mk> diff --git a/usr.sbin/rpc.statd/Makefile.depend b/usr.sbin/rpc.statd/Makefile.depend new file mode 100644 index 0000000..a356b97 --- /dev/null +++ b/usr.sbin/rpc.statd/Makefile.depend @@ -0,0 +1,32 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + gnu/lib/libgcc \ + include \ + include/arpa \ + include/rpc \ + include/rpcsvc \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + lib/librpcsvc \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +file.o: sm_inter.h +file.po: sm_inter.h +procs.o: sm_inter.h +procs.po: sm_inter.h +sm_inter_svc.o: sm_inter.h +sm_inter_svc.o: sm_inter_svc.c +sm_inter_svc.po: sm_inter.h +sm_inter_svc.po: sm_inter_svc.c +statd.o: sm_inter.h +statd.po: sm_inter.h +.endif diff --git a/usr.sbin/rpc.statd/file.c b/usr.sbin/rpc.statd/file.c new file mode 100644 index 0000000..0625e30 --- /dev/null +++ b/usr.sbin/rpc.statd/file.c @@ -0,0 +1,361 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON 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 <err.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/mman.h> /* For mmap() */ +#include <rpc/rpc.h> +#include <syslog.h> +#include <stdlib.h> + +#include "statd.h" + +FileLayout *status_info; /* Pointer to the mmap()ed status file */ +static int status_fd; /* File descriptor for the open file */ +static off_t status_file_len; /* Current on-disc length of file */ + +/* sync_file --------------------------------------------------------------- */ +/* + Purpose: Packaged call of msync() to flush changes to mmap()ed file + Returns: Nothing. Errors to syslog. +*/ + +void sync_file(void) +{ + if (msync((void *)status_info, 0, 0) < 0) + { + syslog(LOG_ERR, "msync() failed: %s", strerror(errno)); + } +} + +/* find_host -------------------------------------------------------------- */ +/* + Purpose: Find the entry in the status file for a given host + Returns: Pointer to that entry in the mmap() region, or NULL. + Notes: Also creates entries if requested. + Failure to create also returns NULL. +*/ + +HostInfo *find_host(char *hostname, int create) +{ + HostInfo *hp; + HostInfo *spare_slot = NULL; + HostInfo *result = NULL; + struct addrinfo *ai1, *ai2; + int i; + + if (getaddrinfo(hostname, NULL, NULL, &ai1) != 0) + ai1 = NULL; + for (i = 0, hp = status_info->hosts; i < status_info->noOfHosts; i++, hp++) + { + if (!strncasecmp(hostname, hp->hostname, SM_MAXSTRLEN)) + { + result = hp; + break; + } + if (hp->hostname[0] && + getaddrinfo(hp->hostname, NULL, NULL, &ai2) != 0) + ai2 = NULL; + if (ai1 && ai2) + { + struct addrinfo *p1, *p2; + for (p1 = ai1; !result && p1; p1 = p1->ai_next) + { + for (p2 = ai2; !result && p2; p2 = p2->ai_next) + { + if (p1->ai_family == p2->ai_family + && p1->ai_addrlen == p2->ai_addrlen + && !memcmp(p1->ai_addr, p2->ai_addr, p1->ai_addrlen)) + { + result = hp; + break; + } + } + } + if (result) + break; + } + if (ai2) + freeaddrinfo(ai2); + if (!spare_slot && !hp->monList && !hp->notifyReqd) + spare_slot = hp; + } + if (ai1) + freeaddrinfo(ai1); + + /* Return if entry found, or if not asked to create one. */ + if (result || !create) return (result); + + /* Now create an entry, using the spare slot if one was found or */ + /* adding to the end of the list otherwise, extending file if reqd */ + if (!spare_slot) + { + off_t desired_size; + spare_slot = &status_info->hosts[status_info->noOfHosts]; + desired_size = ((char*)spare_slot - (char*)status_info) + sizeof(HostInfo); + if (desired_size > status_file_len) + { + /* Extend file by writing 1 byte of junk at the desired end pos */ + lseek(status_fd, desired_size - 1, SEEK_SET); + i = write(status_fd, &i, 1); + if (i < 1) + { + syslog(LOG_ERR, "Unable to extend status file"); + return (NULL); + } + status_file_len = desired_size; + } + status_info->noOfHosts++; + } + + /* Initialise the spare slot that has been found/created */ + /* Note that we do not msync(), since the caller is presumed to be */ + /* about to modify the entry further */ + memset(spare_slot, 0, sizeof(HostInfo)); + strncpy(spare_slot->hostname, hostname, SM_MAXSTRLEN); + return (spare_slot); +} + +/* init_file -------------------------------------------------------------- */ +/* + Purpose: Open file, create if necessary, initialise it. + Returns: Nothing - exits on error + Notes: Called before process becomes daemon, hence logs to + stderr rather than syslog. + Opens the file, then mmap()s it for ease of access. + Also performs initial clean-up of the file, zeroing + monitor list pointers, setting the notifyReqd flag in + all hosts that had a monitor list, and incrementing + the state number to the next even value. +*/ + +void init_file(const char *filename) +{ + int new_file = FALSE; + char buf[HEADER_LEN]; + int i; + + /* try to open existing file - if not present, create one */ + status_fd = open(filename, O_RDWR); + if ((status_fd < 0) && (errno == ENOENT)) + { + status_fd = open(filename, O_RDWR | O_CREAT, 0644); + new_file = TRUE; + } + if (status_fd < 0) + errx(1, "unable to open status file %s", filename); + + /* File now open. mmap() it, with a generous size to allow for */ + /* later growth, where we will extend the file but not re-map it. */ + status_info = (FileLayout *) + mmap(NULL, 0x10000000, PROT_READ | PROT_WRITE, MAP_SHARED, status_fd, 0); + + if (status_info == (FileLayout *) MAP_FAILED) + err(1, "unable to mmap() status file"); + + status_file_len = lseek(status_fd, 0L, SEEK_END); + + /* If the file was not newly created, validate the contents, and if */ + /* defective, re-create from scratch. */ + if (!new_file) + { + if ((status_file_len < HEADER_LEN) || (status_file_len + < (HEADER_LEN + sizeof(HostInfo) * status_info->noOfHosts)) ) + { + warnx("status file is corrupt"); + new_file = TRUE; + } + } + + /* Initialisation of a new, empty file. */ + if (new_file) + { + memset(buf, 0, sizeof(buf)); + lseek(status_fd, 0L, SEEK_SET); + write(status_fd, buf, HEADER_LEN); + status_file_len = HEADER_LEN; + } + else + { + /* Clean-up of existing file - monitored hosts will have a pointer */ + /* to a list of clients, which refers to memory in the previous */ + /* incarnation of the program and so are meaningless now. These */ + /* pointers are zeroed and the fact that the host was previously */ + /* monitored is recorded by setting the notifyReqd flag, which will */ + /* in due course cause a SM_NOTIFY to be sent. */ + /* Note that if we crash twice in quick succession, some hosts may */ + /* already have notifyReqd set, where we didn't manage to notify */ + /* them before the second crash occurred. */ + for (i = 0; i < status_info->noOfHosts; i++) + { + HostInfo *this_host = &status_info->hosts[i]; + + if (this_host->monList) + { + this_host->notifyReqd = TRUE; + this_host->monList = NULL; + } + } + /* Select the next higher even number for the state counter */ + status_info->ourState = (status_info->ourState + 2) & 0xfffffffe; +/*???????******/ status_info->ourState++; + } +} + +/* notify_one_host --------------------------------------------------------- */ +/* + Purpose: Perform SM_NOTIFY procedure at specified host + Returns: TRUE if success, FALSE if failed. +*/ + +static int notify_one_host(char *hostname) +{ + struct timeval timeout = { 20, 0 }; /* 20 secs timeout */ + CLIENT *cli; + char dummy; + stat_chge arg; + char our_hostname[SM_MAXSTRLEN+1]; + + gethostname(our_hostname, sizeof(our_hostname)); + our_hostname[SM_MAXSTRLEN] = '\0'; + arg.mon_name = our_hostname; + arg.state = status_info->ourState; + + if (debug) syslog (LOG_DEBUG, "Sending SM_NOTIFY to host %s from %s", hostname, our_hostname); + + cli = clnt_create(hostname, SM_PROG, SM_VERS, "udp"); + if (!cli) + { + syslog(LOG_ERR, "Failed to contact host %s%s", hostname, + clnt_spcreateerror("")); + return (FALSE); + } + + if (clnt_call(cli, SM_NOTIFY, (xdrproc_t)xdr_stat_chge, &arg, + (xdrproc_t)xdr_void, &dummy, timeout) + != RPC_SUCCESS) + { + syslog(LOG_ERR, "Failed to contact rpc.statd at host %s", hostname); + clnt_destroy(cli); + return (FALSE); + } + + clnt_destroy(cli); + return (TRUE); +} + +/* notify_hosts ------------------------------------------------------------ */ +/* + Purpose: Send SM_NOTIFY to all hosts marked as requiring it + Returns: Nothing, immediately - forks a process to do the work. + Notes: Does nothing if there are no monitored hosts. + Called after all the initialisation has been done - + logs to syslog. +*/ + +void notify_hosts(void) +{ + int i; + int attempts; + int work_to_do = FALSE; + HostInfo *hp; + pid_t pid; + + /* First check if there is in fact any work to do. */ + for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++) + { + if (hp->notifyReqd) + { + work_to_do = TRUE; + break; + } + } + + if (!work_to_do) return; /* No work found */ + + pid = fork(); + if (pid == -1) + { + syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno)); + return; + } + if (pid) return; + + /* Here in the child process. We continue until all the hosts marked */ + /* as requiring notification have been duly notified. */ + /* If one of the initial attempts fails, we sleep for a while and */ + /* have another go. This is necessary because when we have crashed, */ + /* (eg. a power outage) it is quite possible that we won't be able to */ + /* contact all monitored hosts immediately on restart, either because */ + /* they crashed too and take longer to come up (in which case the */ + /* notification isn't really required), or more importantly if some */ + /* router etc. needed to reach the monitored host has not come back */ + /* up yet. In this case, we will be a bit late in re-establishing */ + /* locks (after the grace period) but that is the best we can do. */ + /* We try 10 times at 5 sec intervals, 10 more times at 1 minute */ + /* intervals, then 24 more times at hourly intervals, finally */ + /* giving up altogether if the host hasn't come back to life after */ + /* 24 hours. */ + + for (attempts = 0; attempts < 44; attempts++) + { + work_to_do = FALSE; /* Unless anything fails */ + for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++) + { + if (hp->notifyReqd) + { + if (notify_one_host(hp->hostname)) + { + hp->notifyReqd = FALSE; + sync_file(); + } + else work_to_do = TRUE; + } + } + if (!work_to_do) break; + if (attempts < 10) sleep(5); + else if (attempts < 20) sleep(60); + else sleep(60*60); + } + exit(0); +} + + diff --git a/usr.sbin/rpc.statd/procs.c b/usr.sbin/rpc.statd/procs.c new file mode 100644 index 0000000..f6067e7 --- /dev/null +++ b/usr.sbin/rpc.statd/procs.c @@ -0,0 +1,436 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON 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 <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <rpc/rpc.h> +#include <syslog.h> +#include <vis.h> +#include <netdb.h> /* for getaddrinfo() */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "statd.h" + +static const char * +from_addr(saddr) + struct sockaddr *saddr; +{ + static char inet_buf[INET6_ADDRSTRLEN]; + + if (getnameinfo(saddr, saddr->sa_len, inet_buf, sizeof(inet_buf), + NULL, 0, NI_NUMERICHOST) == 0) + return inet_buf; + return "???"; +} + +/* sm_check_hostname -------------------------------------------------------- */ +/* + * Purpose: Check `mon_name' member of sm_name struct to ensure that the array + * consists only of printable characters. + * + * Returns: TRUE if hostname is good. FALSE if hostname contains binary or + * otherwise non-printable characters. + * + * Notes: Will syslog(3) to warn of corrupt hostname. + */ + +int sm_check_hostname(struct svc_req *req, char *arg) +{ + int len, dstlen, ret; + struct sockaddr *claddr; + char *dst; + + len = strlen(arg); + dstlen = (4 * len) + 1; + dst = malloc(dstlen); + claddr = (struct sockaddr *) (svc_getrpccaller(req->rq_xprt)->buf) ; + ret = 1; + + if (claddr == NULL || dst == NULL) + { + ret = 0; + } + else if (strvis(dst, arg, VIS_WHITE) != len) + { + syslog(LOG_ERR, + "sm_stat: client %s hostname %s contained invalid characters.", + from_addr(claddr), + dst); + ret = 0; + } + free(dst); + return (ret); +} + +/* sm_stat_1 --------------------------------------------------------------- */ +/* + Purpose: RPC call to enquire if a host can be monitored + Returns: TRUE for any hostname that can be looked up to give + an address. +*/ + +struct sm_stat_res *sm_stat_1_svc(sm_name *arg, struct svc_req *req) +{ + static sm_stat_res res; + struct addrinfo *ai; + struct sockaddr *claddr; + static int err; + + err = 1; + if ((err = sm_check_hostname(req, arg->mon_name)) == 0) + { + res.res_stat = stat_fail; + } + if (err != 0) + { + if (debug) + syslog(LOG_DEBUG, "stat called for host %s", arg->mon_name); + if (getaddrinfo(arg->mon_name, NULL, NULL, &ai) == 0) { + res.res_stat = stat_succ; + freeaddrinfo(ai); + } + else + { + claddr = (struct sockaddr *) (svc_getrpccaller(req->rq_xprt)->buf) ; + syslog(LOG_ERR, "invalid hostname to sm_stat from %s: %s", + from_addr(claddr), arg->mon_name); + res.res_stat = stat_fail; + } + } + res.state = status_info->ourState; + return (&res); +} + +/* sm_mon_1 ---------------------------------------------------------------- */ +/* + Purpose: RPC procedure to establish a monitor request + Returns: Success, unless lack of resources prevents + the necessary structures from being set up + to record the request, or if the hostname is not + valid (as judged by getaddrinfo()) +*/ + +struct sm_stat_res *sm_mon_1_svc(mon *arg, struct svc_req *req) +{ + static sm_stat_res res; + HostInfo *hp; + static int err; + MonList *lp; + struct addrinfo *ai; + + if ((err = sm_check_hostname(req, arg->mon_id.mon_name)) == 0) + { + res.res_stat = stat_fail; + } + + if (err != 0) + { + if (debug) + { + syslog(LOG_DEBUG, "monitor request for host %s", arg->mon_id.mon_name); + syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d", + arg->mon_id.my_id.my_name, + arg->mon_id.my_id.my_prog, + arg->mon_id.my_id.my_vers, + arg->mon_id.my_id.my_proc); + } + res.res_stat = stat_fail; /* Assume fail until set otherwise */ + res.state = status_info->ourState; + + /* Find existing host entry, or create one if not found */ + /* If find_host() fails, it will have logged the error already. */ + if (getaddrinfo(arg->mon_id.mon_name, NULL, NULL, &ai) != 0) + { + syslog(LOG_ERR, "Invalid hostname to sm_mon: %s", arg->mon_id.mon_name); + return (&res); + } + freeaddrinfo(ai); + if ((hp = find_host(arg->mon_id.mon_name, TRUE))) + { + lp = (MonList *)malloc(sizeof(MonList)); + if (!lp) + { + syslog(LOG_ERR, "Out of memory"); + } + else + { + strncpy(lp->notifyHost, arg->mon_id.my_id.my_name, SM_MAXSTRLEN); + lp->notifyProg = arg->mon_id.my_id.my_prog; + lp->notifyVers = arg->mon_id.my_id.my_vers; + lp->notifyProc = arg->mon_id.my_id.my_proc; + memcpy(lp->notifyData, arg->priv, sizeof(lp->notifyData)); + + lp->next = hp->monList; + hp->monList = lp; + sync_file(); + + res.res_stat = stat_succ; /* Report success */ + } + } + } + return (&res); +} + +/* do_unmon ---------------------------------------------------------------- */ +/* + Purpose: Remove a monitor request from a host + Returns: TRUE if found, FALSE if not found. + Notes: Common code from sm_unmon_1_svc and sm_unmon_all_1_svc + In the unlikely event of more than one identical monitor + request, all are removed. +*/ + +static int do_unmon(HostInfo *hp, my_id *idp) +{ + MonList *lp, *next; + MonList *last = NULL; + int result = FALSE; + + lp = hp->monList; + while (lp) + { + if (!strncasecmp(idp->my_name, lp->notifyHost, SM_MAXSTRLEN) + && (idp->my_prog == lp->notifyProg) && (idp->my_proc == lp->notifyProc) + && (idp->my_vers == lp->notifyVers)) + { + /* found one. Unhook from chain and free. */ + next = lp->next; + if (last) last->next = next; + else hp->monList = next; + free(lp); + lp = next; + result = TRUE; + } + else + { + last = lp; + lp = lp->next; + } + } + return (result); +} + +/* sm_unmon_1 -------------------------------------------------------------- */ +/* + Purpose: RPC procedure to release a monitor request. + Returns: Local machine's status number + Notes: The supplied mon_id should match the value passed in an + earlier call to sm_mon_1 +*/ + +struct sm_stat *sm_unmon_1_svc(mon_id *arg, struct svc_req *req __unused) +{ + static sm_stat res; + HostInfo *hp; + + if (debug) + { + syslog(LOG_DEBUG, "un-monitor request for host %s", arg->mon_name); + syslog(LOG_DEBUG, "recall host: %s prog: %d ver: %d proc: %d", + arg->mon_name, + arg->my_id.my_prog, arg->my_id.my_vers, arg->my_id.my_proc); + } + + if ((hp = find_host(arg->mon_name, FALSE))) + { + if (do_unmon(hp, &arg->my_id)) sync_file(); + else + { + syslog(LOG_ERR, "unmon request from %s, no matching monitor", + arg->my_id.my_name); + } + } + else syslog(LOG_ERR, "unmon request from %s for unknown host %s", + arg->my_id.my_name, arg->mon_name); + + res.state = status_info->ourState; + + return (&res); +} + +/* sm_unmon_all_1 ---------------------------------------------------------- */ +/* + Purpose: RPC procedure to release monitor requests. + Returns: Local machine's status number + Notes: Releases all monitor requests (if any) from the specified + host and program number. +*/ + +struct sm_stat *sm_unmon_all_1_svc(my_id *arg, struct svc_req *req __unused) +{ + static sm_stat res; + HostInfo *hp; + int i; + + if (debug) + { + syslog(LOG_DEBUG, "unmon_all for host: %s prog: %d ver: %d proc: %d", + arg->my_name, arg->my_prog, arg->my_vers, arg->my_proc); + } + + for (i = status_info->noOfHosts, hp = status_info->hosts; i; i--, hp++) + { + do_unmon(hp, arg); + } + sync_file(); + + res.state = status_info->ourState; + + return (&res); +} + +/* sm_simu_crash_1 --------------------------------------------------------- */ +/* + Purpose: RPC procedure to simulate a crash + Returns: Nothing + Notes: Standardised mechanism for debug purposes + The specification says that we should drop all of our + status information (apart from the list of monitored hosts + on disc). However, this would confuse the rpc.lockd + which would be unaware that all of its monitor requests + had been silently junked. Hence we in fact retain all + current requests and simply increment the status counter + and inform all hosts on the monitor list. +*/ + +void *sm_simu_crash_1_svc(void *v __unused, struct svc_req *req __unused) +{ + static char dummy; + int work_to_do; + HostInfo *hp; + int i; + + work_to_do = FALSE; + if (debug) syslog(LOG_DEBUG, "simu_crash called!!"); + + /* Simulate crash by setting notify-required flag on all monitored */ + /* hosts, and incrementing our status number. notify_hosts() is */ + /* then called to fork a process to do the notifications. */ + + for (i = status_info->noOfHosts, hp = status_info->hosts; i ; i--, hp++) + { + if (hp->monList) + { + work_to_do = TRUE; + hp->notifyReqd = TRUE; + } + } + status_info->ourState += 2; /* always even numbers if not crashed */ + + if (work_to_do) notify_hosts(); + + return (&dummy); +} + +/* sm_notify_1 ------------------------------------------------------------- */ +/* + Purpose: RPC procedure notifying local statd of the crash of another + Returns: Nothing + Notes: There is danger of deadlock, since it is quite likely that + the client procedure that we call will in turn call us + to remove or adjust the monitor request. + We therefore fork() a process to do the notifications. + Note that the main HostInfo structure is in a mmap() + region and so will be shared with the child, but the + monList pointed to by the HostInfo is in normal memory. + Hence if we read the monList before forking, we are + protected from the parent servicing other requests + that modify the list. +*/ + +void *sm_notify_1_svc(stat_chge *arg, struct svc_req *req __unused) +{ + struct timeval timeout = { 20, 0 }; /* 20 secs timeout */ + CLIENT *cli; + static char dummy; + sm_status tx_arg; /* arg sent to callback procedure */ + MonList *lp; + HostInfo *hp; + pid_t pid; + + if (debug) syslog(LOG_DEBUG, "notify from host %s, new state %d", + arg->mon_name, arg->state); + + hp = find_host(arg->mon_name, FALSE); + if (!hp) + { + /* Never heard of this host - why is it notifying us? */ + syslog(LOG_ERR, "Unsolicited notification from host %s", arg->mon_name); + return (&dummy); + } + lp = hp->monList; + if (!lp) return (&dummy); /* We know this host, but have no */ + /* outstanding requests. */ + pid = fork(); + if (pid == -1) + { + syslog(LOG_ERR, "Unable to fork notify process - %s", strerror(errno)); + return (NULL); /* no answer, the client will retry */ + } + if (pid) return (&dummy); /* Parent returns */ + + while (lp) + { + tx_arg.mon_name = arg->mon_name; + tx_arg.state = arg->state; + memcpy(tx_arg.priv, lp->notifyData, sizeof(tx_arg.priv)); + cli = clnt_create(lp->notifyHost, lp->notifyProg, lp->notifyVers, "udp"); + if (!cli) + { + syslog(LOG_ERR, "Failed to contact host %s%s", lp->notifyHost, + clnt_spcreateerror("")); + } + else + { + if (clnt_call(cli, lp->notifyProc, (xdrproc_t)xdr_sm_status, &tx_arg, + (xdrproc_t)xdr_void, &dummy, timeout) != RPC_SUCCESS) + { + syslog(LOG_ERR, "Failed to call rpc.statd client at host %s", + lp->notifyHost); + } + clnt_destroy(cli); + } + lp = lp->next; + } + + exit (0); /* Child quits */ +} diff --git a/usr.sbin/rpc.statd/rpc.statd.8 b/usr.sbin/rpc.statd/rpc.statd.8 new file mode 100644 index 0000000..32559ad --- /dev/null +++ b/usr.sbin/rpc.statd/rpc.statd.8 @@ -0,0 +1,137 @@ +.\" -*- nroff -*- +.\" +.\" Copyright (c) 1995 A.R.Gordon, andrew.gordon@net-tel.co.uk +.\" 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 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$ +.\" +.Dd November 1, 2007 +.Dt RPC.STATD 8 +.Os +.Sh NAME +.Nm rpc.statd +.Nd host status monitoring daemon +.Sh SYNOPSIS +.Nm +.Op Fl d +.Op Fl h Ar bindip +.Op Fl p Ar port +.Sh DESCRIPTION +The +.Nm +utility +is a daemon which co-operates with +.Nm +daemons on other hosts to provide +a status monitoring service. +The daemon accepts requests from +programs running on the local host (typically, +.Xr rpc.lockd 8 , +the NFS file locking daemon) to monitor the status of specified +hosts. +If a monitored host crashes and restarts, the remote daemon will +notify the local daemon, which in turn will notify the local program(s) +which requested the monitoring service. +Conversely, if this host crashes +and re-starts, when the +.Nm +re-starts, it will notify all of the hosts which were being monitored +at the time of the crash. +.Pp +The following option is available: +.Bl -tag -width indent +.It Fl d +Cause debugging information to be written to syslog, recording +all RPC transactions to the daemon. +These messages are logged with level +LOG_DEBUG and facility LOG_DAEMON. +Error conditions are logged irrespective +of this option, using level LOG_ERR. +.It Fl h Ar bindip +Specify specific IP addresses to bind to. +This option may be specified multiple times. +If no +.Fl h +option is specified, +.Nm +will bind to +.Dv INADDR_ANY . +Note that when specifying IP addresses with +.Fl h , +.Nm +will automatically add +.Li 127.0.0.1 +and if IPv6 is enabled, +.Li ::1 +to the list. +.It Fl p +The +.Fl p +option allow to force the daemon to bind to the specified +.Ar port , +for both AF_INET and AF_INET6 address families. +.El +.Pp +The +.Nm +utility must NOT be invoked by +.Xr inetd 8 +because the protocol assumes that the daemon will run from system start time. +Instead, it should be run from +.Xr rc 8 +after the network has been started. +.Sh FILES +.Bl -tag -width /usr/include/rpcsvc/sm_inter.x -compact +.It Pa /var/db/statd.status +non-volatile record of currently monitored hosts. +.It Pa /usr/include/rpcsvc/sm_inter.x +RPC protocol specification used by local applications to register monitoring requests. +.El +.Sh SEE ALSO +.Xr syslog 3 , +.Xr rc 8 , +.Xr rpc.lockd 8 +.Sh STANDARDS +The implementation is based on the specification in X/Open CAE Specification +C218, "Protocols for X/Open PC Interworking: XNFS, Issue 4", ISBN 1 872630 66 9 +.Sh BUGS +There is no means for the daemon to tell when a monitored host has +disappeared permanently (e.g.\& catastrophic hardware failure), as opposed +to transient failure of the host or an intermediate router. +At present, +it will re-try notification attempts at frequent intervals for 10 minutes, +then hourly, and finally gives up after 24 hours. +.Pp +The protocol requires that symmetric monitor requests are made to both +the local and remote daemon in order to establish a monitored relationship. +This is convenient for the NFS locking protocol, but probably reduces the +usefulness of the monitoring system for other applications. +.Pp +The current implementation uses more than 1Kbyte per monitored host in +the status file (and also in VM). +This may be inefficient for NFS servers +with large numbers of clients. diff --git a/usr.sbin/rpc.statd/statd.c b/usr.sbin/rpc.statd/statd.c new file mode 100644 index 0000000..256f58d --- /dev/null +++ b/usr.sbin/rpc.statd/statd.c @@ -0,0 +1,655 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON 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. + * + */ + +/* main() function for status monitor daemon. Some of the code in this */ +/* file was generated by running rpcgen /usr/include/rpcsvc/sm_inter.x */ +/* The actual program logic is in the file procs.c */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <rpc/rpc.h> +#include <rpc/rpc_com.h> +#include <string.h> +#include <syslog.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <signal.h> +#include <unistd.h> +#include "statd.h" + +#define GETPORT_MAXTRY 20 /* Max tries to get a port # */ + +int debug = 0; /* Controls syslog() calls for debug messages */ + +char **hosts, *svcport_str = NULL; +int nhosts = 0; +int xcreated = 0; +static int mallocd_svcport = 0; +static int *sock_fd; +static int sock_fdcnt; +static int sock_fdpos; + +static int create_service(struct netconfig *nconf); +static void complete_service(struct netconfig *nconf, char *port_str); +static void clearout_service(void); +static void handle_sigchld(int sig); +void out_of_mem(void); + +static void usage(void); + +int +main(int argc, char **argv) +{ + struct sigaction sa; + struct netconfig *nconf; + void *nc_handle; + in_port_t svcport; + int ch, i, s; + char *endptr, **hosts_bak; + int have_v6 = 1; + int maxrec = RPC_MAXDATASIZE; + int attempt_cnt, port_len, port_pos, ret; + char **port_list; + + while ((ch = getopt(argc, argv, "dh:p:")) != -1) + switch (ch) { + case 'd': + debug = 1; + break; + case 'h': + ++nhosts; + hosts_bak = hosts; + hosts_bak = realloc(hosts, nhosts * sizeof(char *)); + if (hosts_bak == NULL) { + if (hosts != NULL) { + for (i = 0; i < nhosts; i++) + free(hosts[i]); + free(hosts); + out_of_mem(); + } + } + hosts = hosts_bak; + hosts[nhosts - 1] = strdup(optarg); + if (hosts[nhosts - 1] == NULL) { + for (i = 0; i < (nhosts - 1); i++) + free(hosts[i]); + free(hosts); + out_of_mem(); + } + break; + case 'p': + endptr = NULL; + svcport = (in_port_t)strtoul(optarg, &endptr, 10); + if (endptr == NULL || *endptr != '\0' || svcport == 0 || + svcport >= IPPORT_MAX) + usage(); + + svcport_str = strdup(optarg); + break; + default: + usage(); + } + argc -= optind; + argv += optind; + + (void)rpcb_unset(SM_PROG, SM_VERS, NULL); + + /* + * Check if IPv6 support is present. + */ + s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (s < 0) + have_v6 = 0; + else + close(s); + + rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec); + + /* + * If no hosts were specified, add a wildcard entry to bind to + * INADDR_ANY. Otherwise make sure 127.0.0.1 and ::1 are added to the + * list. + */ + if (nhosts == 0) { + hosts = malloc(sizeof(char *)); + if (hosts == NULL) + out_of_mem(); + + hosts[0] = "*"; + nhosts = 1; + } else { + hosts_bak = hosts; + if (have_v6) { + hosts_bak = realloc(hosts, (nhosts + 2) * + sizeof(char *)); + if (hosts_bak == NULL) { + for (i = 0; i < nhosts; i++) + free(hosts[i]); + free(hosts); + out_of_mem(); + } else + hosts = hosts_bak; + + nhosts += 2; + hosts[nhosts - 2] = "::1"; + } else { + hosts_bak = realloc(hosts, (nhosts + 1) * sizeof(char *)); + if (hosts_bak == NULL) { + for (i = 0; i < nhosts; i++) + free(hosts[i]); + + free(hosts); + out_of_mem(); + } else { + nhosts += 1; + hosts = hosts_bak; + } + } + hosts[nhosts - 1] = "127.0.0.1"; + } + + attempt_cnt = 1; + sock_fdcnt = 0; + sock_fd = NULL; + port_list = NULL; + port_len = 0; + nc_handle = setnetconfig(); + while ((nconf = getnetconfig(nc_handle))) { + /* We want to listen only on udp6, tcp6, udp, tcp transports */ + if (nconf->nc_flag & NC_VISIBLE) { + /* Skip if there's no IPv6 support */ + if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) { + /* DO NOTHING */ + } else { + ret = create_service(nconf); + if (ret == 1) + /* Ignore this call */ + continue; + if (ret < 0) { + /* + * Failed to bind port, so close off + * all sockets created and try again + * if the port# was dynamically + * assigned via bind(2). + */ + clearout_service(); + if (mallocd_svcport != 0 && + attempt_cnt < GETPORT_MAXTRY) { + free(svcport_str); + svcport_str = NULL; + mallocd_svcport = 0; + } else { + errno = EADDRINUSE; + syslog(LOG_ERR, + "bindresvport_sa: %m"); + exit(1); + } + + /* Start over at the first service. */ + free(sock_fd); + sock_fdcnt = 0; + sock_fd = NULL; + nc_handle = setnetconfig(); + attempt_cnt++; + } else if (mallocd_svcport != 0 && + attempt_cnt == GETPORT_MAXTRY) { + /* + * For the last attempt, allow + * different port #s for each nconf + * by saving the svcport_str and + * setting it back to NULL. + */ + port_list = realloc(port_list, + (port_len + 1) * sizeof(char *)); + if (port_list == NULL) + out_of_mem(); + port_list[port_len++] = svcport_str; + svcport_str = NULL; + mallocd_svcport = 0; + } + } + } + } + + /* + * Successfully bound the ports, so call complete_service() to + * do the rest of the setup on the service(s). + */ + sock_fdpos = 0; + port_pos = 0; + nc_handle = setnetconfig(); + while ((nconf = getnetconfig(nc_handle))) { + /* We want to listen only on udp6, tcp6, udp, tcp transports */ + if (nconf->nc_flag & NC_VISIBLE) { + /* Skip if there's no IPv6 support */ + if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) { + /* DO NOTHING */ + } else if (port_list != NULL) { + if (port_pos >= port_len) { + syslog(LOG_ERR, "too many port#s"); + exit(1); + } + complete_service(nconf, port_list[port_pos++]); + } else + complete_service(nconf, svcport_str); + } + } + endnetconfig(nc_handle); + free(sock_fd); + if (port_list != NULL) { + for (port_pos = 0; port_pos < port_len; port_pos++) + free(port_list[port_pos]); + free(port_list); + } + + init_file("/var/db/statd.status"); + + /* Note that it is NOT sensible to run this program from inetd - the */ + /* protocol assumes that it will run immediately at boot time. */ + daemon(0, 0); + openlog("rpc.statd", 0, LOG_DAEMON); + if (debug) syslog(LOG_INFO, "Starting - debug enabled"); + else syslog(LOG_INFO, "Starting"); + + /* Install signal handler to collect exit status of child processes */ + sa.sa_handler = handle_sigchld; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGCHLD); + sa.sa_flags = SA_RESTART; + sigaction(SIGCHLD, &sa, NULL); + + /* Initialisation now complete - start operating */ + notify_hosts(); /* Forks a process (if necessary) to do the */ + /* SM_NOTIFY calls, which may be slow. */ + + svc_run(); /* Should never return */ + exit(1); +} + +/* + * This routine creates and binds sockets on the appropriate + * addresses. It gets called one time for each transport. + * It returns 0 upon success, 1 for ingore the call and -1 to indicate + * bind failed with EADDRINUSE. + * Any file descriptors that have been created are stored in sock_fd and + * the total count of them is maintained in sock_fdcnt. + */ +static int +create_service(struct netconfig *nconf) +{ + struct addrinfo hints, *res = NULL; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct __rpc_sockinfo si; + int aicode; + int fd; + int nhostsbak; + int r; + u_int32_t host_addr[4]; /* IPv4 or IPv6 */ + int mallocd_res; + + if ((nconf->nc_semantics != NC_TPI_CLTS) && + (nconf->nc_semantics != NC_TPI_COTS) && + (nconf->nc_semantics != NC_TPI_COTS_ORD)) + return (1); /* not my type */ + + /* + * XXX - using RPC library internal functions. + */ + if (!__rpc_nconf2sockinfo(nconf, &si)) { + syslog(LOG_ERR, "cannot get information for %s", + nconf->nc_netid); + return (1); + } + + /* Get rpc.statd's address on this transport */ + memset(&hints, 0, sizeof hints); + hints.ai_family = si.si_af; + hints.ai_socktype = si.si_socktype; + hints.ai_protocol = si.si_proto; + + /* + * Bind to specific IPs if asked to + */ + nhostsbak = nhosts; + while (nhostsbak > 0) { + --nhostsbak; + sock_fd = realloc(sock_fd, (sock_fdcnt + 1) * sizeof(int)); + if (sock_fd == NULL) + out_of_mem(); + sock_fd[sock_fdcnt++] = -1; /* Set invalid for now. */ + mallocd_res = 0; + hints.ai_flags = AI_PASSIVE; + + /* + * XXX - using RPC library internal functions. + */ + if ((fd = __rpc_nconf2fd(nconf)) < 0) { + syslog(LOG_ERR, "cannot create socket for %s", + nconf->nc_netid); + continue; + } + 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; + } + } + break; + default: + break; + } + + /* + * If no hosts were specified, just bind to INADDR_ANY + */ + if (strcmp("*", hosts[nhostsbak]) == 0) { + if (svcport_str == NULL) { + res = malloc(sizeof(struct addrinfo)); + if (res == NULL) + out_of_mem(); + mallocd_res = 1; + res->ai_flags = hints.ai_flags; + res->ai_family = hints.ai_family; + res->ai_protocol = hints.ai_protocol; + switch (res->ai_family) { + case AF_INET: + sin = malloc(sizeof(struct sockaddr_in)); + if (sin == NULL) + out_of_mem(); + sin->sin_family = AF_INET; + sin->sin_port = htons(0); + sin->sin_addr.s_addr = htonl(INADDR_ANY); + res->ai_addr = (struct sockaddr*) sin; + res->ai_addrlen = (socklen_t) + sizeof(struct sockaddr_in); + break; + case AF_INET6: + sin6 = malloc(sizeof(struct sockaddr_in6)); + if (sin6 == NULL) + out_of_mem(); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(0); + sin6->sin6_addr = in6addr_any; + res->ai_addr = (struct sockaddr*) sin6; + res->ai_addrlen = (socklen_t) + sizeof(struct sockaddr_in6); + break; + default: + syslog(LOG_ERR, "bad addr fam %d", + res->ai_family); + exit(1); + } + } else { + if ((aicode = getaddrinfo(NULL, svcport_str, + &hints, &res)) != 0) { + syslog(LOG_ERR, + "cannot get local address for %s: %s", + nconf->nc_netid, + gai_strerror(aicode)); + close(fd); + continue; + } + } + } else { + if ((aicode = getaddrinfo(hosts[nhostsbak], svcport_str, + &hints, &res)) != 0) { + syslog(LOG_ERR, + "cannot get local address for %s: %s", + nconf->nc_netid, gai_strerror(aicode)); + close(fd); + continue; + } + } + + /* Store the fd. */ + sock_fd[sock_fdcnt - 1] = fd; + + /* Now, attempt the bind. */ + r = bindresvport_sa(fd, res->ai_addr); + if (r != 0) { + if (errno == EADDRINUSE && mallocd_svcport != 0) { + if (mallocd_res != 0) { + free(res->ai_addr); + free(res); + } else + freeaddrinfo(res); + return (-1); + } + syslog(LOG_ERR, "bindresvport_sa: %m"); + exit(1); + } + + if (svcport_str == NULL) { + svcport_str = malloc(NI_MAXSERV * sizeof(char)); + if (svcport_str == NULL) + out_of_mem(); + mallocd_svcport = 1; + + if (getnameinfo(res->ai_addr, + res->ai_addr->sa_len, NULL, NI_MAXHOST, + svcport_str, NI_MAXSERV * sizeof(char), + NI_NUMERICHOST | NI_NUMERICSERV)) + errx(1, "Cannot get port number"); + } + if (mallocd_res != 0) { + free(res->ai_addr); + free(res); + } else + freeaddrinfo(res); + res = NULL; + } + return (0); +} + +/* + * Called after all the create_service() calls have succeeded, to complete + * the setup and registration. + */ +static void +complete_service(struct netconfig *nconf, char *port_str) +{ + struct addrinfo hints, *res = NULL; + struct __rpc_sockinfo si; + struct netbuf servaddr; + SVCXPRT *transp = NULL; + int aicode, fd, nhostsbak; + int registered = 0; + + if ((nconf->nc_semantics != NC_TPI_CLTS) && + (nconf->nc_semantics != NC_TPI_COTS) && + (nconf->nc_semantics != NC_TPI_COTS_ORD)) + return; /* not my type */ + + /* + * XXX - using RPC library internal functions. + */ + if (!__rpc_nconf2sockinfo(nconf, &si)) { + syslog(LOG_ERR, "cannot get information for %s", + nconf->nc_netid); + return; + } + + nhostsbak = nhosts; + while (nhostsbak > 0) { + --nhostsbak; + if (sock_fdpos >= sock_fdcnt) { + /* Should never happen. */ + syslog(LOG_ERR, "Ran out of socket fd's"); + return; + } + fd = sock_fd[sock_fdpos++]; + if (fd < 0) + continue; + + if (nconf->nc_semantics != NC_TPI_CLTS) + listen(fd, SOMAXCONN); + + transp = svc_tli_create(fd, nconf, NULL, + RPC_MAXDATASIZE, RPC_MAXDATASIZE); + + if (transp != (SVCXPRT *) NULL) { + if (!svc_register(transp, SM_PROG, SM_VERS, + sm_prog_1, 0)) { + syslog(LOG_ERR, "can't register on %s", + nconf->nc_netid); + } else { + if (!svc_reg(transp, SM_PROG, SM_VERS, + sm_prog_1, NULL)) + syslog(LOG_ERR, + "can't register %s SM_PROG service", + nconf->nc_netid); + } + } else + syslog(LOG_WARNING, "can't create %s services", + nconf->nc_netid); + + if (registered == 0) { + registered = 1; + memset(&hints, 0, sizeof hints); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = si.si_af; + hints.ai_socktype = si.si_socktype; + hints.ai_protocol = si.si_proto; + + + if ((aicode = getaddrinfo(NULL, port_str, &hints, + &res)) != 0) { + syslog(LOG_ERR, "cannot get local address: %s", + gai_strerror(aicode)); + exit(1); + } + + servaddr.buf = malloc(res->ai_addrlen); + memcpy(servaddr.buf, res->ai_addr, res->ai_addrlen); + servaddr.len = res->ai_addrlen; + + rpcb_set(SM_PROG, SM_VERS, nconf, &servaddr); + + xcreated++; + freeaddrinfo(res); + } + } /* end while */ +} + +/* + * Clear out sockets after a failure to bind one of them, so that the + * cycle of socket creation/binding can start anew. + */ +static void +clearout_service(void) +{ + int i; + + for (i = 0; i < sock_fdcnt; i++) { + if (sock_fd[i] >= 0) { + shutdown(sock_fd[i], SHUT_RDWR); + close(sock_fd[i]); + } + } +} + +static void +usage() +{ + fprintf(stderr, "usage: rpc.statd [-d] [-h <bindip>] [-p <port>]\n"); + exit(1); +} + +/* handle_sigchld ---------------------------------------------------------- */ +/* + Purpose: Catch SIGCHLD and collect process status + Retruns: Nothing. + Notes: No special action required, other than to collect the + process status and hence allow the child to die: + we only use child processes for asynchronous transmission + of SM_NOTIFY to other systems, so it is normal for the + children to exit when they have done their work. +*/ + +static void handle_sigchld(int sig __unused) +{ + int pid, status; + pid = wait4(-1, &status, WNOHANG, (struct rusage*)0); + if (!pid) syslog(LOG_ERR, "Phantom SIGCHLD??"); + else if (status == 0) + { + if (debug) syslog(LOG_DEBUG, "Child %d exited OK", pid); + } + else syslog(LOG_ERR, "Child %d failed with status %d", pid, + WEXITSTATUS(status)); +} + +/* + * Out of memory, fatal + */ +void +out_of_mem() +{ + + syslog(LOG_ERR, "out of memory"); + exit(2); +} diff --git a/usr.sbin/rpc.statd/statd.h b/usr.sbin/rpc.statd/statd.h new file mode 100644 index 0000000..a82d760 --- /dev/null +++ b/usr.sbin/rpc.statd/statd.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 1995 + * A.R. Gordon (andrew.gordon@net-tel.co.uk). All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the FreeBSD project + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY ANDREW GORDON 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 "sm_inter.h" + +/* ------------------------------------------------------------------------- */ +/* + Data structures for recording monitored hosts + + The information held by the status monitor comprises a list of hosts + that we have been asked to monitor, and, associated with each monitored + host, one or more clients to be called back if the monitored host crashes. + + The list of monitored hosts must be retained over a crash, so that upon + re-boot we can call the SM_NOTIFY procedure in all those hosts so as to + cause them to start recovery processing. On the other hand, the client + call-backs are not required to be preserved: they are assumed (in the + protocol design) to be local processes which will have crashed when + we did, and so are discarded on restart. + + We handle this by keeping the list of monitored hosts in a file + (/var/statd.state) which is mmap()ed and whose format is described + by the typedef FileLayout. The lists of client callbacks are chained + off this structure, but are held in normal memory and so will be + lost after a re-boot. Hence the actual values of MonList * pointers + in the copy on disc have no significance, but their NULL/non-NULL + status indicates whether this host is actually being monitored or if it + is an empty slot in the file. +*/ + +typedef struct MonList_s +{ + struct MonList_s *next; /* Next in list or NULL */ + char notifyHost[SM_MAXSTRLEN + 1]; /* Host to notify */ + int notifyProg; /* RPC program number to call */ + int notifyVers; /* version number */ + int notifyProc; /* procedure number */ + unsigned char notifyData[16]; /* Opaque data from caller */ +} MonList; + +typedef struct +{ + char hostname[SM_MAXSTRLEN + 1]; /* Name of monitored host */ + int notifyReqd; /* TRUE if we've crashed and not yet */ + /* informed the monitored host */ + MonList *monList; /* List of clients to inform if we */ + /* hear that the monitored host has */ + /* crashed, NULL if no longer monitored */ +} HostInfo; + + +/* Overall file layout. */ + +typedef struct +{ + int ourState; /* State number as defined in statd protocol */ + int noOfHosts; /* Number of elements in hosts[] */ + char reserved[248]; /* Reserved for future use */ + HostInfo hosts[1]; /* vector of monitored hosts */ +} FileLayout; + +#define HEADER_LEN (sizeof(FileLayout) - sizeof(HostInfo)) + +/* ------------------------------------------------------------------------- */ + +/* Global variables */ + +extern FileLayout *status_info; /* The mmap()ed status file */ + +extern int debug; /* =1 to enable diagnostics to syslog */ + +/* Function prototypes */ + +extern HostInfo *find_host(char * /*hostname*/, int /*create*/); +extern void init_file(const char * /*filename*/); +extern void notify_hosts(void); +extern void sync_file(void); +extern int sm_check_hostname(struct svc_req *req, char *arg); diff --git a/usr.sbin/rpc.statd/test.c b/usr.sbin/rpc.statd/test.c new file mode 100644 index 0000000..6df1501 --- /dev/null +++ b/usr.sbin/rpc.statd/test.c @@ -0,0 +1,144 @@ + +#ifndef lint +static const char rcsid[] = + "$FreeBSD$"; +#endif /* not lint */ + +#include <stdio.h> +#include <rpc/rpc.h> +#include <rpcsvc/sm_inter.h> + + +/* Default timeout can be changed using clnt_control() */ +static struct timeval TIMEOUT = { 25, 0 }; + +struct sm_stat_res * +sm_stat_1(argp, clnt) + struct sm_name *argp; + CLIENT *clnt; +{ + static struct sm_stat_res res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_STAT, xdr_sm_name, argp, xdr_sm_stat_res, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +struct sm_stat_res * +sm_mon_1(argp, clnt) + struct mon *argp; + CLIENT *clnt; +{ + static struct sm_stat_res res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_MON, xdr_mon, argp, xdr_sm_stat_res, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +struct sm_stat * +sm_unmon_1(argp, clnt) + struct mon_id *argp; + CLIENT *clnt; +{ + static struct sm_stat res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_UNMON, xdr_mon_id, argp, xdr_sm_stat, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +struct sm_stat * +sm_unmon_all_1(argp, clnt) + struct my_id *argp; + CLIENT *clnt; +{ + static struct sm_stat res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_UNMON_ALL, xdr_my_id, argp, xdr_sm_stat, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return (&res); +} + + +void * +sm_simu_crash_1(argp, clnt) + void *argp; + CLIENT *clnt; +{ + static char res; + + bzero((char *)&res, sizeof(res)); + if (clnt_call(clnt, SM_SIMU_CRASH, xdr_void, argp, xdr_void, &res, TIMEOUT) != RPC_SUCCESS) { + return (NULL); + } + return ((void *)&res); +} + + +int main(int argc, char **argv) +{ + CLIENT *cli; + char dummy; + void *out; + struct mon mon; + + if (argc < 2) + { + fprintf(stderr, "usage: test <hostname> | crash\n"); + fprintf(stderr, "always talks to statd at localhost\n"); + exit(1); + } + + printf("Creating client for localhost\n" ); + cli = clnt_create("localhost", SM_PROG, SM_VERS, "udp"); + if (!cli) + { + printf("Failed to create client\n"); + exit(1); + } + + mon.mon_id.mon_name = argv[1]; + mon.mon_id.my_id.my_name = argv[1]; + mon.mon_id.my_id.my_prog = SM_PROG; + mon.mon_id.my_id.my_vers = SM_VERS; + mon.mon_id.my_id.my_proc = 1; /* have it call sm_stat() !!! */ + + if (strcmp(argv[1], "crash")) + { + /* Hostname given */ + struct sm_stat_res *res; + if (res = sm_mon_1(&mon, cli)) + { + printf("Success!\n"); + } + else + { + printf("Fail\n"); + } + } + else + { + if (out = sm_simu_crash_1(&dummy, cli)) + { + printf("Success!\n"); + } + else + { + printf("Fail\n"); + } + } + + return 0; +} |