diff options
-rw-r--r-- | usr.sbin/rpc.statd/Makefile | 26 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/procs.c | 345 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/rpc.statd.8 | 113 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/statd.c | 597 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/statd.h | 123 | ||||
-rw-r--r-- | usr.sbin/rpc.statd/test.c | 126 |
6 files changed, 1330 insertions, 0 deletions
diff --git a/usr.sbin/rpc.statd/Makefile b/usr.sbin/rpc.statd/Makefile new file mode 100644 index 00000000000..05b4f83e7e1 --- /dev/null +++ b/usr.sbin/rpc.statd/Makefile @@ -0,0 +1,26 @@ +# $OpenBSD: Makefile,v 1.1 2008/06/15 04:43:28 sturm Exp $ + +PROG= rpc.statd +SRCS= procs.c sm_inter_svc.c statd.c +MAN= rpc.statd.8 + +DPADD= ${LIBRPCSVC} +LDADD= -lrpcsvc + +CFLAGS+= -I. + +CLEANFILES= sm_inter_svc.c sm_inter.h + +RPCSRC= ${DESTDIR}/usr/include/rpcsvc/sm_inter.x +RPCGEN= rpcgen -L -C + +sm_inter_svc.c: ${RPCSRC} sm_inter.h + ${RPCGEN} -m -o ${.TARGET} ${RPCSRC} + +sm_inter.h: ${RPCSRC} + ${RPCGEN} -h -o ${.TARGET} ${RPCSRC} + +test: ${.CURDIR}/test.c + cc -o test ${.CURDIR}/test.c -lrpcsvc + +.include <bsd.prog.mk> diff --git a/usr.sbin/rpc.statd/procs.c b/usr.sbin/rpc.statd/procs.c new file mode 100644 index 00000000000..285a7b88afb --- /dev/null +++ b/usr.sbin/rpc.statd/procs.c @@ -0,0 +1,345 @@ +/* $OpenBSD: procs.c,v 1.1 2008/06/15 04:43:28 sturm Exp $ */ + +/* + * 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 <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <signal.h> +#include <unistd.h> + +#include <rpc/rpc.h> + +#include "statd.h" + +/* 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; + + NO_ALARM; + if (debug) + syslog(LOG_DEBUG, "stat called for host %s", arg->mon_name); + + if (gethostbyname(arg->mon_name)) + res.res_stat = stat_succ; + else { + syslog(LOG_ERR, "invalid hostname to sm_stat: %s", + arg->mon_name); + res.res_stat = stat_fail; + } + + res.state = status_info.ourState; + ALARM; + 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 gethostbyname()) + */ +struct sm_stat_res * +sm_mon_1_svc(mon *arg, struct svc_req *req) +{ + static sm_stat_res res; + HostInfo *hp, h; + MonList *lp; + + NO_ALARM; + 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 (!gethostbyname(arg->mon_id.mon_name)) { + syslog(LOG_ERR, "Invalid hostname to sm_mon: %s", + arg->mon_id.mon_name); + return &res; + } + + if ((hp = find_host(arg->mon_id.mon_name, &h)) == NULL) + memset(hp = &h, 0, sizeof(h)); + + 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; + change_host(arg->mon_id.mon_name, hp); + sync_file(); + res.res_stat = stat_succ; /* Report success */ + } + ALARM; + 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. + */ +int +do_unmon(char *name, HostInfo *hp, void *ptr) +{ + my_id *idp = ptr; + 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) +{ + static sm_stat res; + HostInfo *hp, h; + + NO_ALARM; + 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->my_id.my_name, arg->my_id.my_prog, + arg->my_id.my_vers, arg->my_id.my_proc); + } + if ((hp = find_host(arg->mon_name, &h)) != NULL) { + if (do_unmon(arg->mon_name, hp, &arg->my_id)) { + change_host(arg->mon_name, hp); + 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; + ALARM; + + 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) +{ + static sm_stat res; + + NO_ALARM; + 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); + } + + unmon_hosts(); + sync_file(); + + res.state = status_info.ourState; + ALARM; + + 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, struct svc_req *req) +{ + static char dummy; + + NO_ALARM; + if (debug) + syslog(LOG_DEBUG, "simu_crash called!!"); + + reset_database(); + ALARM; + notify_handler(0); + + 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) +{ + struct timeval timeout = {20, 0}; /* 20 secs timeout */ + CLIENT *cli; + static char dummy; + status tx_arg; /* arg sent to callback procedure */ + MonList *lp; + HostInfo *hp, h; + 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, &h); + if (!hp) { + /* Never heard of this host - why is it notifying us? */ + syslog(LOG_DEBUG, "Unsolicited notification from host %s", + arg->mon_name); + return (&dummy); + } + lp = hp->monList; + if (!lp) /* We know this host, but have no outstanding requests. */ + return (&dummy); + + sync_file(); + pid = fork(); + if (pid == -1) { + syslog(LOG_ERR, "Unable to fork notify process - %s", + strerror(errno)); + return (FALSE); + } + 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, xdr_status, &tx_arg, + 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 00000000000..da0ed7073f5 --- /dev/null +++ b/usr.sbin/rpc.statd/rpc.statd.8 @@ -0,0 +1,113 @@ +.\" $OpenBSD: rpc.statd.8,v 1.1 2008/06/15 04:43:28 sturm Exp $ +.\" +.\" 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 by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE 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. +.\" +.\" +.Dd September 19, 1995 +.Dt RPC.STATD 8 +.Os +.Sh NAME +.Nm rpc.statd +.Nd host status monitoring daemon +.Sh SYNOPSIS +.Nm +.Op Fl d +.Sh DESCRIPTION +.Nm +is a daemon which co-operates with rpc.statd 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 restarts, when +.Nm +restarts, it will notify all of the hosts which were being monitored +at the time of the crash. +.Pp +Options and operands available for +.Nm +: +.Bl -tag -width Ds +.It Fl d +The +.Fl d +option causes 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. +.El +.Pp +The +.Nm +daemon 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 configured in +.Xr rc.conf 5 +to run at system startup. +.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.conf 5 , +.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 HISTORY +A version of +.Nm +appeared in +.Tn SunOS 4 . +.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 retry 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 00000000000..a6aa05390bb --- /dev/null +++ b/usr.sbin/rpc.statd/statd.c @@ -0,0 +1,597 @@ +/* $OpenBSD: statd.c,v 1.1 2008/06/15 04:43:28 sturm Exp $ */ + +/* + * Copyright (c) 1997 Christos Zoulas. All rights reserved. + * 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 + * This product includes software developed by Christos Zoulas. + * 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/param.h> +#include <sys/wait.h> + +#include <err.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <db.h> + +#include <rpc/rpc.h> + +#include "statd.h" + +struct sigaction sa; +int debug = 0; /* Controls syslog() for debug msgs */ +int _rpcsvcdirty = 0; /* XXX ??? */ +static DB *db; /* Database file */ + +Header status_info; + +static char undefdata[] = "\0\1\2\3\4\5\6\7"; +static DBT undefkey = { + undefdata, + sizeof(undefdata) +}; +extern char *__progname; + +/* statd.c */ +static int walk_one(int (*fun )(DBT *, HostInfo *, void *), DBT *, DBT *, void *); +static int walk_db(int (*fun )(DBT *, HostInfo *, void *), void *); +static int reset_host(DBT *, HostInfo *, void *); +static int check_work(DBT *, HostInfo *, void *); +static int unmon_host(DBT *, HostInfo *, void *); +static int notify_one(DBT *, HostInfo *, void *); +static void init_file(char *); +static int notify_one_host(char *); +static void die(int); + +int main(int, char **); + +int +main(int argc, char **argv) +{ + SVCXPRT *transp; + int ch; + struct sigaction nsa; + + while ((ch = getopt(argc, argv, "d")) != (-1)) { + switch (ch) { + case 'd': + debug = 1; + break; + default: + case '?': + fprintf(stderr, "usage: %s [-d]\n", __progname); + exit(1); + /* NOTREACHED */ + } + } + pmap_unset(SM_PROG, SM_VERS); + + transp = svcudp_create(RPC_ANYSOCK); + if (transp == NULL) { + errx(1, "cannot create udp service."); + /* NOTREACHED */ + } + if (!svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_UDP)) { + errx(1, "unable to register (SM_PROG, SM_VERS, udp)."); + /* NOTREACHED */ + } + transp = svctcp_create(RPC_ANYSOCK, 0, 0); + if (transp == NULL) { + errx(1, "cannot create tcp service."); + /* NOTREACHED */ + } + if (!svc_register(transp, SM_PROG, SM_VERS, sm_prog_1, IPPROTO_TCP)) { + errx(1, "unable to register (SM_PROG, SM_VERS, tcp)."); + /* NOTREACHED */ + } + + 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); + + sigemptyset(&nsa.sa_mask); + nsa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT; + nsa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &nsa, NULL); + + openlog("rpc.statd", 0, LOG_DAEMON); + if (debug) + syslog(LOG_INFO, "Starting - debug enabled"); + else + syslog(LOG_INFO, "Starting"); + + sa.sa_handler = die; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGALRM); + + /* Initialisation now complete - start operating */ + + /* Notify hosts that need it */ + notify_handler(0); + + while (1) + svc_run(); /* Should never return */ + die(0); +} + +/* notify_handler ---------------------------------------------------------- */ +/* + * Purpose: Catch SIGALRM and collect process status + * Returns: 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. + */ +void +notify_handler(int sig) +{ + time_t now; + + NO_ALARM; + sa.sa_handler = SIG_IGN; + sigaction(SIGALRM, &sa, NULL); + + now = time(NULL); + + walk_db(notify_one, &now); + + if (walk_db(check_work, &now) == 0) { + /* + * No more work to be done. + */ + CLR_ALARM; + return; + } + sync_file(); + ALARM; + alarm(5); +} + +/* sync_file --------------------------------------------------------------- */ +/* + * Purpose: Packaged call of msync() to flush changes to mmap()ed file + * Returns: Nothing. Errors to syslog. + */ +void +sync_file() +{ + DBT data; + + data.data = &status_info; + data.size = sizeof(status_info); + switch ((*db->put)(db, &undefkey, &data, 0)) { + case 0: + return; + case -1: + goto bad; + default: + abort(); + } + if ((*db->sync)(db, 0) == -1) { +bad: + syslog(LOG_ERR, "database corrupted %m"); + die(1); + } +} + +/* change_host -------------------------------------------------------------- */ +/* + * Purpose: Update/Create an entry for host + * Returns: Nothing + * Notes: + * + */ +void +change_host(char *hostnamep, HostInfo *hp) +{ + DBT key, data; + char *ptr; + char hostname[MAXHOSTNAMELEN + 1]; + HostInfo h; + + strlcpy(hostname, hostnamep, sizeof(hostname)); + h = *hp; + + for (ptr = hostname; *ptr; ptr++) + if (isupper((unsigned char) *ptr)) + *ptr = tolower((unsigned char) *ptr); + + key.data = hostname; + key.size = ptr - hostname + 1; + data.data = &h; + data.size = sizeof(h); + + switch ((*db->put)(db, &key, &data, 0)) { + case -1: + syslog(LOG_ERR, "database corrupted %m"); + die(1); + case 0: + return; + default: + abort(); + } +} + + +/* find_host -------------------------------------------------------------- */ +/* + * Purpose: Find the entry in the status file for a given host + * Returns: Copy of entry in hd, or NULL + * Notes: + * + */ +HostInfo * +find_host(char *hostname, HostInfo *hp) +{ + DBT key, data; + char *ptr; + + for (ptr = hostname; *ptr; ptr++) + if (isupper((unsigned char) *ptr)) + *ptr = tolower((unsigned char) *ptr); + + key.data = hostname; + key.size = ptr - hostname + 1; + switch ((*db->get)(db, &key, &data, 0)) { + case 0: + if (data.size != sizeof(*hp)) + goto bad; + return memcpy(hp, data.data, sizeof(*hp)); + case 1: + return NULL; + case -1: + goto bad; + default: + abort(); + } + +bad: + syslog(LOG_ERR, "Database corrupted %m"); + return NULL; +} + +/* walk_one ------------------------------------------------------------- */ +/* + * Purpose: Call the given function if the element is valid + * Returns: Nothing - exits on error + * Notes: + */ +static int +walk_one(int (*fun)(DBT *, HostInfo *, void *), DBT *key, DBT *data, void *ptr) +{ + HostInfo h; + if (key->size == undefkey.size && + memcmp(key->data, undefkey.data, key->size) == 0) + return 0; + if (data->size != sizeof(HostInfo)) { + syslog(LOG_ERR, "Bad data in database"); + die(1); + } + memcpy(&h, data->data, sizeof(h)); + return (*fun)(key, &h, ptr); +} + +/* walk_db -------------------------------------------------------------- */ +/* + * Purpose: Iterate over all elements calling the given function + * Returns: -1 if function failed, 0 on success + * Notes: + */ +static int +walk_db(int (*fun)(DBT *, HostInfo *, void *), void *ptr) +{ + DBT key, data; + + switch ((*db->seq)(db, &key, &data, R_FIRST)) { + case -1: + goto bad; + case 1: + /* We should have at least the magic entry at this point */ + abort(); + case 0: + if (walk_one(fun, &key, &data, ptr) == -1) + return -1; + break; + default: + abort(); + } + + for (;;) + switch ((*db->seq)(db, &key, &data, R_NEXT)) { + case -1: + goto bad; + case 0: + if (walk_one(fun, &key, &data, ptr) == -1) + return -1; + break; + case 1: + return 0; + default: + abort(); + } +bad: + syslog(LOG_ERR, "Corrupted database %m"); + die(1); +} + +/* reset_host ------------------------------------------------------------ */ +/* + * Purpose: Clean up existing hosts in file. + * Returns: Always success 0. + * Notes: 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. + */ +static int +reset_host(DBT *key, HostInfo *hi, void *ptr) +{ + if (hi->monList) { + hi->notifyReqd = *(time_t *) ptr; + hi->attempts = 0; + hi->monList = NULL; + change_host((char *)key->data, hi); + } + return 0; +} + +/* check_work ------------------------------------------------------------ */ +/* + * Purpose: Check if there is work to be done. + * Returns: 0 if there is no work to be done -1 if there is. + * Notes: + */ +static int +check_work(DBT *key, HostInfo *hi, void *ptr) +{ + return hi->notifyReqd ? -1 : 0; +} + +/* unmon_host ------------------------------------------------------------ */ +/* + * Purpose: Unmonitor a host + * Returns: 0 + * Notes: + */ +static int +unmon_host(DBT *key, HostInfo *hi, void *ptr) +{ + char *name = key->data; + + if (do_unmon(name, hi, ptr)) + change_host(name, hi); + return 0; +} + +/* notify_one ------------------------------------------------------------ */ +/* + * Purpose: Notify one host. + * Returns: 0 if success -1 on failure + * Notes: + */ +static int +notify_one(DBT *key, HostInfo *hi, void *ptr) +{ + time_t now = *(time_t *) ptr; + char *name = key->data; + int error; + + if (hi->notifyReqd == 0 || hi->notifyReqd > now) + return 0; + + /* + * If one of the initial attempts fails, we wait + * 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. + */ + if (notify_one_host(name) || hi->attempts++ >= 44) { + error = 0; + hi->notifyReqd = 0; + hi->attempts = 0; + } else { + error = -1; + if (hi->attempts < 10) + hi->notifyReqd += 5; + else if (hi->attempts < 20) + hi->notifyReqd += 60; + else + hi->notifyReqd += 60 * 60; + } + change_host(name, hi); + return error; +} + +/* 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. + */ +static void +init_file(char *filename) +{ + DBT data; + + db = dbopen(filename, O_RDWR|O_CREAT|O_NDELAY|O_EXLOCK, 0644, DB_HASH, + NULL); + if (db == NULL) + err(1, "Cannot open `%s'", filename); + + switch ((*db->get)(db, &undefkey, &data, 0)) { + case 1: + /* New database */ + memset(&status_info, 0, sizeof(status_info)); + sync_file(); + return; + case -1: + err(1, "error accessing database (%m)"); + case 0: + /* Existing database */ + if (data.size != sizeof(status_info)) + errx(1, "database corrupted %lu != %lu", + (u_long)data.size, (u_long)sizeof(status_info)); + memcpy(&status_info, data.data, data.size); + break; + default: + abort(); + } + + reset_database(); + return; +} + +/* reset_database --------------------------------------------------------- */ +/* + * Purpose: Clears the statd database + * Returns: Nothing + * Notes: If this is not called on reset, it will leak memory. + */ +void +reset_database(void) +{ + time_t now = time(NULL); + walk_db(reset_host, &now); + + /* Select the next higher even number for the state counter */ + status_info.ourState = + (status_info.ourState + 2) & 0xfffffffe; + status_info.ourState++; /* XXX - ??? */ + sync_file(); +} + +/* unmon_hosts --------------------------------------------------------- */ +/* + * Purpose: Unmonitor all the hosts + * Returns: Nothing + * Notes: + */ +void +unmon_hosts(void) +{ + time_t now = time(NULL); + walk_db(unmon_host, &now); + sync_file(); +} + +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[MAXHOSTNAMELEN + 1]; + + gethostname(our_hostname, sizeof(our_hostname)); + our_hostname[sizeof(our_hostname) - 1] = '\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, xdr_stat_chge, &arg, 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); +} + +static void +die(int n) +{ + (*db->close)(db); + exit(n); +} diff --git a/usr.sbin/rpc.statd/statd.h b/usr.sbin/rpc.statd/statd.h new file mode 100644 index 00000000000..1b6a35298c3 --- /dev/null +++ b/usr.sbin/rpc.statd/statd.h @@ -0,0 +1,123 @@ +/* $OpenBSD: statd.h,v 1.1 2008/06/15 04:43:28 sturm Exp $ */ + +/* + * 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 "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 Header. 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 */ + u_char notifyData[16]; /* Opaque data from caller */ +} MonList; + +typedef struct { + int notifyReqd; /* Time of our next attempt or 0 + informed the monitored host */ + int attempts; /* Number of attempts we tried so far */ + 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 magic; /* Zero magic */ + int ourState; /* State number as defined in statd protocol */ +} Header; + +/* ------------------------------------------------------------------------- */ + +/* Global variables */ + +extern int debug; /* = 1 to enable diagnostics to syslog */ +extern struct sigaction sa; +extern Header status_info; + +/* Function prototypes */ + +/* stat_proc.c */ +struct sm_stat_res *sm_stat_1_svc(sm_name *, struct svc_req *); +struct sm_stat_res *sm_mon_1_svc(mon *, struct svc_req *); +struct sm_stat *sm_unmon_1_svc(mon_id *, struct svc_req *); +struct sm_stat *sm_unmon_all_1_svc(my_id *, struct svc_req *); +void *sm_simu_crash_1_svc(void *, struct svc_req *); +void *sm_notify_1_svc(stat_chge *, struct svc_req *); +int do_unmon(char *, HostInfo *, void *); + +/* statd.c */ +void notify_handler(int); +void sync_file(void); +void unmon_hosts(void); +void change_host(char *, HostInfo *); +HostInfo *find_host(char *, HostInfo *); +void reset_database(void); + +void sm_prog_1(struct svc_req *, SVCXPRT *); + +#define NO_ALARM sa.sa_handler == SIG_DFL ? 0 : \ + (sa.sa_handler = SIG_IGN, sigaction(SIGALRM, &sa, NULL)) +#define ALARM sa.sa_handler == SIG_DFL ? 0 : \ + (sa.sa_handler = notify_handler, sigaction(SIGALRM, &sa, NULL)) +#define CLR_ALARM sa.sa_handler == SIG_DFL ? 0 : \ + (sa.sa_handler = SIG_DFL, sigaction(SIGALRM, &sa, NULL)) diff --git a/usr.sbin/rpc.statd/test.c b/usr.sbin/rpc.statd/test.c new file mode 100644 index 00000000000..c0aad1e8482 --- /dev/null +++ b/usr.sbin/rpc.statd/test.c @@ -0,0 +1,126 @@ +/* $OpenBSD: test.c,v 1.1 2008/06/15 04:43:28 sturm Exp $ */ + +#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(argc, argv) + int argc; + char **argv; +{ + CLIENT *cli; + char dummy; + void *out; + struct mon mon; + + if (argc < 2) { + warnx("usage: test {<hostname> | crash}"); + errx(1, "Always talks to statd at localhost"); + } + printf("Creating client for localhost\n"); + cli = clnt_create("localhost", SM_PROG, SM_VERS, "udp"); + if (!cli) { + errx(1, "Failed to create client"); + } + 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; +} |