Module Name: src Committed By: msaitoh Date: Mon Nov 3 18:48:42 UTC 2014
Modified Files: src/usr.sbin/ypbind [netbsd-5]: ypbind.8 ypbind.c Log Message: Pull up following revision(s) (requested by jmcneill in dholland ticket #1925): usr.sbin/ypbind/ypbind.8 1.19-1.20 usr.sbin/ypbind/ypbind.c 1.63-1.98 State handling fixes and cleanups in ypbind; it no longer churns the disk, spams syslogd when the server goes down, or gets confused about its binding state. Fixes PR#15355, PR#32519 and also PR#43900. To generate a diff of this commit: cvs rdiff -u -r1.18 -r1.18.6.1 src/usr.sbin/ypbind/ypbind.8 cvs rdiff -u -r1.57.18.1 -r1.57.18.2 src/usr.sbin/ypbind/ypbind.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/usr.sbin/ypbind/ypbind.8 diff -u src/usr.sbin/ypbind/ypbind.8:1.18 src/usr.sbin/ypbind/ypbind.8:1.18.6.1 --- src/usr.sbin/ypbind/ypbind.8:1.18 Wed Apr 30 13:11:03 2008 +++ src/usr.sbin/ypbind/ypbind.8 Mon Nov 3 18:48:42 2014 @@ -1,4 +1,4 @@ -.\" $NetBSD: ypbind.8,v 1.18 2008/04/30 13:11:03 martin Exp $ +.\" $NetBSD: ypbind.8,v 1.18.6.1 2014/11/03 18:48:42 msaitoh Exp $ .\" .\" Copyright (c) 1996 The NetBSD Foundation, Inc. .\" All rights reserved. @@ -27,7 +27,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd February 26, 2005 +.Dd June 14, 2014 .Dt YPBIND 8 .Os .Sh NAME @@ -94,9 +94,9 @@ it is bound. If the binding is somehow lost, e.g by server reboot, .Nm marks the domain as unbound and attempts to re-establish the binding. -When the binding is once again successful, +If a binding cannot be re-established within 60 seconds, .Nm -marks the domain as bound and resumes its periodic check. +backs off exponentially to trying only once per hour. .Pp The options are as follows: .Bl -tag -width "-broadcast" @@ -114,7 +114,7 @@ or servers. .It Fl ypset .Xr ypset 8 -may be used to change the server to which a domain is bound. +may be used from anywhere to change the server to which a domain is bound. .It Fl ypsetme .Xr ypset 8 may be used only from this machine to change the server @@ -122,11 +122,22 @@ to which a domain is bound. .El .Pp The -.Fl broadcast +.Fl broadcast , .Fl ypset , and -.Fl ypsetme , +.Fl ypsetme options are inherently insecure and should be avoided. +.Sh SIGNALS +.Nm +responds to the following signals: +.Bl -tag -width TERM -compact +.It Dv HUP +causes +.Nm +to immediately retry any unbound domains that are currently in +exponential backoff. +Use this to resume immediately after a long network outage is +resolved. .Sh FILES .Pa /var/yp/binding/\*[Lt]domain\*[Gt].version - binding file for \*[Lt]domain\*[Gt]. @@ -147,7 +158,10 @@ facility. .Xr yppoll 8 , .Xr ypset 8 .Sh AUTHORS +.An -nosplit This version of .Nm -was originally implemented by Theo de Raadt. -The ypservers support was implemented by Luke Mewburn. +was originally implemented by +.An Theo de Raadt . +The ypservers support was implemented by +.An Luke Mewburn . Index: src/usr.sbin/ypbind/ypbind.c diff -u src/usr.sbin/ypbind/ypbind.c:1.57.18.1 src/usr.sbin/ypbind/ypbind.c:1.57.18.2 --- src/usr.sbin/ypbind/ypbind.c:1.57.18.1 Wed Feb 16 21:04:03 2011 +++ src/usr.sbin/ypbind/ypbind.c Mon Nov 3 18:48:42 2014 @@ -1,4 +1,4 @@ -/* $NetBSD: ypbind.c,v 1.57.18.1 2011/02/16 21:04:03 bouyer Exp $ */ +/* $NetBSD: ypbind.c,v 1.57.18.2 2014/11/03 18:48:42 msaitoh Exp $ */ /* * Copyright (c) 1992, 1993 Theo de Raadt <dera...@fsa.ca> @@ -28,138 +28,185 @@ #include <sys/cdefs.h> #ifndef LINT -__RCSID("$NetBSD: ypbind.c,v 1.57.18.1 2011/02/16 21:04:03 bouyer Exp $"); +__RCSID("$NetBSD: ypbind.c,v 1.57.18.2 2014/11/03 18:48:42 msaitoh Exp $"); #endif -#include <sys/param.h> #include <sys/types.h> +#include <sys/param.h> +#include <sys/file.h> #include <sys/ioctl.h> #include <sys/signal.h> #include <sys/socket.h> -#include <sys/file.h> -#include <sys/uio.h> -#include <sys/syslog.h> #include <sys/stat.h> +#include <sys/syslog.h> +#include <sys/uio.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> #include <fcntl.h> +#include <ifaddrs.h> #include <limits.h> +#include <netdb.h> +#include <signal.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> -#include <errno.h> -#include <syslog.h> -#include <stdarg.h> -#include <ctype.h> -#include <dirent.h> -#include <netdb.h> #include <string.h> -#include <err.h> +#include <syslog.h> +#include <unistd.h> +#include <util.h> + #include <rpc/rpc.h> #include <rpc/xdr.h> -#include <net/if.h> -#include <arpa/inet.h> #include <rpc/pmap_clnt.h> #include <rpc/pmap_prot.h> #include <rpc/pmap_rmt.h> -#include <unistd.h> -#include <util.h> #include <rpcsvc/yp_prot.h> #include <rpcsvc/ypclnt.h> -#include <ifaddrs.h> #include "pathnames.h" +#define YPSERVERSSUFF ".ypservers" +#define BINDINGDIR (_PATH_VAR_YP "binding") + #ifndef O_SHLOCK #define O_SHLOCK 0 #endif -#define BUFSIZE 1400 +int _yp_invalid_domain(const char *); /* XXX libc internal */ -#define YPSERVERSSUFF ".ypservers" -#define BINDINGDIR (_PATH_VAR_YP "binding") +//////////////////////////////////////////////////////////// +// types and globals + +typedef enum { + YPBIND_DIRECT, YPBIND_BROADCAST, +} ypbind_mode_t; + +enum domainstates { + DOM_NEW, /* not yet bound */ + DOM_ALIVE, /* bound and healthy */ + DOM_PINGING, /* ping outstanding */ + DOM_LOST, /* binding timed out, looking for a new one */ + DOM_DEAD, /* long-term lost, in exponential backoff */ +}; -struct _dom_binding { - struct _dom_binding *dom_pnext; - char dom_domain[YPMAXDOMAIN + 1]; +struct domain { + struct domain *dom_next; + + char dom_name[YPMAXDOMAIN + 1]; struct sockaddr_in dom_server_addr; - int dom_socket; - CLIENT *dom_client; long dom_vers; - time_t dom_check_t; - time_t dom_ask_t; + time_t dom_checktime; /* time of next check/contact */ + time_t dom_asktime; /* time we were last DOMAIN'd */ + time_t dom_losttime; /* time the binding was lost, or 0 */ + unsigned dom_backofftime; /* current backoff period, when DEAD */ int dom_lockfd; - int dom_alive; - u_int32_t dom_xid; + enum domainstates dom_state; + uint32_t dom_xid; + FILE *dom_serversfile; /* /var/yp/binding/foo.ypservers */ + int dom_been_ypset; /* ypset been done on this domain? */ + ypbind_mode_t dom_ypbindmode; /* broadcast or direct */ }; -static char *domainname; +#define BUFSIZE 1400 -static struct _dom_binding *ypbindlist; +/* the list of all domains */ +static struct domain *domains; static int check; -typedef enum { - YPBIND_DIRECT, YPBIND_BROADCAST, YPBIND_SETLOCAL, YPBIND_SETALL -} ypbind_mode_t; - -ypbind_mode_t ypbindmode; - -/* - * If ypbindmode is YPBIND_SETLOCAL or YPBIND_SETALL, this indicates - * whether or not we've been "ypset". If we haven't, we behave like - * YPBIND_BROADCAST. If we have, we behave like YPBIND_DIRECT. - */ -int been_ypset; - -#ifdef DEBUG -static int debug; -#endif - +/* option settings */ +static ypbind_mode_t default_ypbindmode; +static int allow_local_ypset = 0, allow_any_ypset = 0; static int insecure; + +/* the sockets we use to interact with servers */ static int rpcsock, pingsock; + +/* stuff used for manually interacting with servers */ static struct rmtcallargs rmtca; static struct rmtcallres rmtcr; static bool_t rmtcr_outval; -static u_long rmtcr_port; +static unsigned long rmtcr_port; + +/* The ypbind service transports */ static SVCXPRT *udptransp, *tcptransp; -int _yp_invalid_domain(const char *); /* from libc */ -int main(int, char *[]); +/* set if we get SIGHUP */ +static sig_atomic_t hupped; + +//////////////////////////////////////////////////////////// +// utilities + +/* + * Combo of open() and flock(). + */ +static int +open_locked(const char *path, int flags, mode_t mode) +{ + int fd; -static void usage(void); -static void yp_log(int, const char *, ...) - __attribute__((__format__(__printf__, 2, 3))); -static struct _dom_binding *makebinding(const char *); -static int makelock(struct _dom_binding *); -static void removelock(struct _dom_binding *); -static int purge_bindingdir(const char *); -static void *ypbindproc_null_2(SVCXPRT *, void *); -static void *ypbindproc_domain_2(SVCXPRT *, void *); -static void *ypbindproc_setdom_2(SVCXPRT *, void *); -static void ypbindprog_2(struct svc_req *, SVCXPRT *); -static void checkwork(void); -static int ping(struct _dom_binding *); -static int nag_servers(struct _dom_binding *); -static enum clnt_stat handle_replies(void); -static enum clnt_stat handle_ping(void); -static void rpc_received(char *, struct sockaddr_in *, int); -static struct _dom_binding *xid2ypdb(u_int32_t); -static u_int32_t unique_xid(struct _dom_binding *); -static int broadcast(char *, int); -static int direct(char *, int); -static int direct_set(char *, int, struct _dom_binding *); + fd = open(path, flags|O_SHLOCK, mode); + if (fd < 0) { + return -1; + } +#if O_SHLOCK == 0 + /* dholland 20110522 wouldn't it be better to check this for error? */ + (void)flock(fd, LOCK_SH); +#endif + return fd; +} +/* + * Exponential backoff for pinging servers for a dead domain. + * + * We go 10 -> 20 -> 40 -> 60 seconds, then 2 -> 4 -> 8 -> 15 -> 30 -> + * 60 minutes, and stay at 60 minutes. This is overengineered. + * + * With a 60 minute max backoff the response time for when things come + * back is not awful, but we only try (and log) about 60 times even if + * things are down for a whole long weekend. This is an acceptable log + * load, I think. + */ static void -usage(void) +backoff(unsigned *psecs) { - const char *opt = ""; + unsigned secs; + + secs = *psecs; + if (secs < 60) { + secs *= 2; + if (secs > 60) { + secs = 60; + } + } else if (secs < 60 * 15) { + secs *= 2; + if (secs > 60 * 15) { + secs = 60 * 15; + } + } else if (secs < 60 * 60) { + secs *= 2; + } + *psecs = secs; +} + +//////////////////////////////////////////////////////////// +// logging + #ifdef DEBUG - opt = " [-d]"; +#define DPRINTF(...) (debug ? (void)printf(__VA_ARGS__) : (void)0) +static int debug; +#else +#define DPRINTF(...) #endif - (void)fprintf(stderr, - "Usage: %s [-broadcast] [-insecure] [-ypset] [-ypsetme]%s\n", - getprogname(), opt); - exit(1); -} +static void yp_log(int, const char *, ...) __attribute__((__format__(__printf, 2, 3))); +/* + * Log some stuff, to syslog or stderr depending on the debug setting. + */ static void yp_log(int pri, const char *fmt, ...) { @@ -168,67 +215,199 @@ yp_log(int pri, const char *fmt, ...) va_start(ap, fmt); #if defined(DEBUG) - if (debug) + if (debug) { (void)vprintf(fmt, ap); - else + (void)printf("\n"); + } else #endif vsyslog(pri, fmt, ap); va_end(ap); } -static struct _dom_binding * -makebinding(const char *dm) +//////////////////////////////////////////////////////////// +// ypservers file + +/* + * Get pathname for the ypservers file for a given domain + * (/var/yp/binding/DOMAIN.ypservers) + */ +static const char * +ypservers_filename(const char *domain) +{ + static char ret[PATH_MAX]; + + (void)snprintf(ret, sizeof(ret), "%s/%s%s", + BINDINGDIR, domain, YPSERVERSSUFF); + return ret; +} + +//////////////////////////////////////////////////////////// +// struct domain + +/* + * The state transitions of a domain work as follows: + * + * in state NEW: + * nag_servers every 5 seconds + * upon answer, state is ALIVE + * + * in state ALIVE: + * every 60 seconds, send ping and switch to state PINGING + * + * in state PINGING: + * upon answer, go to state ALIVE + * if no answer in 5 seconds, go to state LOST and do nag_servers + * + * in state LOST: + * do nag_servers every 5 seconds + * upon answer, go to state ALIVE + * if no answer in 60 seconds, go to state DEAD + * + * in state DEAD + * do nag_servers every backofftime seconds (starts at 10) + * upon answer go to state ALIVE + * backofftime doubles (approximately) each try, with a cap of 1 hour + */ + +/* + * Look up a domain by the XID we assigned it. + */ +static struct domain * +domain_find(uint32_t xid) +{ + struct domain *dom; + + for (dom = domains; dom != NULL; dom = dom->dom_next) + if (dom->dom_xid == xid) + break; + return dom; +} + +/* + * Pick an XID for a domain. + * + * XXX: this should just generate a random number. + */ +static uint32_t +unique_xid(struct domain *dom) +{ + uint32_t tmp_xid; + + tmp_xid = ((uint32_t)(unsigned long)dom) & 0xffffffff; + while (domain_find(tmp_xid) != NULL) + tmp_xid++; + + return tmp_xid; +} + +/* + * Construct a new domain. Adds it to the global linked list of all + * domains. + */ +static struct domain * +domain_create(const char *name) { - struct _dom_binding *ypdb; + struct domain *dom; + const char *pathname; + struct stat st; - if ((ypdb = (struct _dom_binding *)malloc(sizeof *ypdb)) == NULL) { - yp_log(LOG_ERR, "makebinding"); + dom = malloc(sizeof *dom); + if (dom == NULL) { + yp_log(LOG_ERR, "domain_create: Out of memory"); exit(1); } - (void)memset(ypdb, 0, sizeof *ypdb); - (void)strlcpy(ypdb->dom_domain, dm, sizeof ypdb->dom_domain); - return ypdb; + dom->dom_next = NULL; + + (void)strlcpy(dom->dom_name, name, sizeof(dom->dom_name)); + (void)memset(&dom->dom_server_addr, 0, sizeof(dom->dom_server_addr)); + dom->dom_vers = YPVERS; + dom->dom_checktime = 0; + dom->dom_asktime = 0; + dom->dom_losttime = 0; + dom->dom_backofftime = 10; + dom->dom_lockfd = -1; + dom->dom_state = DOM_NEW; + dom->dom_xid = unique_xid(dom); + dom->dom_been_ypset = 0; + dom->dom_serversfile = NULL; + + /* + * Per traditional ypbind(8) semantics, if a ypservers + * file does not exist, we revert to broadcast mode. + * + * The sysadmin can force broadcast mode by passing the + * -broadcast flag. There is currently no way to fail and + * reject domains for which there is no ypservers file. + */ + dom->dom_ypbindmode = default_ypbindmode; + if (dom->dom_ypbindmode == YPBIND_DIRECT) { + pathname = ypservers_filename(dom->dom_name); + if (stat(pathname, &st) < 0) { + /* XXX syslog a warning here? */ + DPRINTF("%s does not exist, defaulting to broadcast\n", + pathname); + dom->dom_ypbindmode = YPBIND_BROADCAST; + } + } + + /* add to global list */ + dom->dom_next = domains; + domains = dom; + + return dom; } +//////////////////////////////////////////////////////////// +// locks + +/* + * Open a new binding file. Does not write the contents out; the + * caller (there's only one) does that. + */ static int -makelock(struct _dom_binding *ypdb) +makelock(struct domain *dom) { int fd; char path[MAXPATHLEN]; (void)snprintf(path, sizeof(path), "%s/%s.%ld", BINDINGDIR, - ypdb->dom_domain, ypdb->dom_vers); + dom->dom_name, dom->dom_vers); - if ((fd = open(path, O_CREAT|O_SHLOCK|O_RDWR|O_TRUNC, 0644)) == -1) { + fd = open_locked(path, O_CREAT|O_RDWR|O_TRUNC, 0644); + if (fd == -1) { (void)mkdir(BINDINGDIR, 0755); - if ((fd = open(path, O_CREAT|O_SHLOCK|O_RDWR|O_TRUNC, 0644)) == -1) + fd = open_locked(path, O_CREAT|O_RDWR|O_TRUNC, 0644); + if (fd == -1) { return -1; + } } -#if O_SHLOCK == 0 - (void)flock(fd, LOCK_SH); -#endif return fd; } +/* + * Remove a binding file. + */ static void -removelock(struct _dom_binding *ypdb) +removelock(struct domain *dom) { char path[MAXPATHLEN]; (void)snprintf(path, sizeof(path), "%s/%s.%ld", - BINDINGDIR, ypdb->dom_domain, ypdb->dom_vers); + BINDINGDIR, dom->dom_name, dom->dom_vers); (void)unlink(path); } /* - * purge_bindingdir: remove old binding files (i.e. "rm BINDINGDIR\/\*.[0-9]") + * purge_bindingdir: remove old binding files (i.e. "rm *.[0-9]" in BINDINGDIR) + * + * The local YP functions [e.g. yp_master()] will fail without even + * talking to ypbind if there is a stale (non-flock'd) binding file + * present. * - * local YP functions [e.g. yp_master()] will fail without even talking - * to ypbind if there is a stale (non-flock'd) binding file present. - * we have to scan the entire BINDINGDIR for binding files, because - * ypbind may bind more than just the yp_get_default_domain() domain. + * We have to remove all binding files in BINDINGDIR, not just the one + * for the default domain. */ static int purge_bindingdir(const char *dirpath) @@ -266,105 +445,371 @@ purge_bindingdir(const char *dirpath) return(0); } +//////////////////////////////////////////////////////////// +// sunrpc twaddle + +/* + * Check if the info coming in is (at least somewhat) valid. + */ +static int +rpc_is_valid_response(char *name, struct sockaddr_in *addr) +{ + if (name == NULL) { + return 0; + } + + if (_yp_invalid_domain(name)) { + return 0; + } + + /* don't support insecure servers by default */ + if (!insecure && ntohs(addr->sin_port) >= IPPORT_RESERVED) { + return 0; + } + + return 1; +} + +/* + * Take note of the fact that we've received a reply from a ypserver. + * Or, in the case of being ypset, that we've been ypset, which + * functions much the same. + * + * Note that FORCE is set if and only if IS_YPSET is set. + * + * This function has also for the past 20+ years carried the annotation + * + * LOOPBACK IS MORE IMPORTANT: PUT IN HACK + * + * whose meaning isn't entirely clear. + */ +static void +rpc_received(char *dom_name, struct sockaddr_in *raddrp, int force, + int is_ypset) +{ + struct domain *dom; + struct iovec iov[2]; + struct ypbind_resp ybr; + ssize_t result; + int fd; + + DPRINTF("returned from %s about %s\n", + inet_ntoa(raddrp->sin_addr), dom_name); + + /* validate some stuff */ + if (!rpc_is_valid_response(dom_name, raddrp)) { + return; + } + + /* look for the domain */ + for (dom = domains; dom != NULL; dom = dom->dom_next) + if (!strcmp(dom->dom_name, dom_name)) + break; + + /* if not found, create it, but only if FORCE; otherwise ignore */ + if (dom == NULL) { + if (force == 0) + return; + dom = domain_create(dom_name); + } + + /* the domain needs to know if it's been explicitly ypset */ + if (is_ypset) { + dom->dom_been_ypset = 1; + } + + /* + * If the domain is alive and we aren't being called by ypset, + * we shouldn't be getting a response at all. Log it, as it + * might be hostile. + */ + if (dom->dom_state == DOM_ALIVE && force == 0) { + if (!memcmp(&dom->dom_server_addr, raddrp, + sizeof(dom->dom_server_addr))) { + yp_log(LOG_WARNING, + "Unexpected reply from server %s for domain %s", + inet_ntoa(dom->dom_server_addr.sin_addr), + dom->dom_name); + } else { + yp_log(LOG_WARNING, + "Falsified reply from %s for domain %s", + inet_ntoa(dom->dom_server_addr.sin_addr), + dom->dom_name); + } + return; + } + + /* + * If we're expected a ping response, and we've got it + * (meaning we aren't being called by ypset), we don't need to + * do anything. + */ + if (dom->dom_state == DOM_PINGING && force == 0) { + /* + * If the reply came from the server we expect, set + * dom_state back to ALIVE and ping again in 60 + * seconds. + * + * If it came from somewhere else, log it. + */ + if (!memcmp(&dom->dom_server_addr, raddrp, + sizeof(dom->dom_server_addr))) { + dom->dom_state = DOM_ALIVE; + /* recheck binding in 60 sec */ + dom->dom_checktime = time(NULL) + 60; + } else { + yp_log(LOG_WARNING, + "Falsified reply from %s for domain %s", + inet_ntoa(dom->dom_server_addr.sin_addr), + dom->dom_name); + } + return; + } + +#ifdef HEURISTIC + /* + * If transitioning to the alive state from a non-alive state, + * clear dom_asktime. This will help prevent any requests that + * are still coming in from triggering unnecessary pings via + * the HEURISTIC code. + * + * XXX: this may not be an adequate measure; we may need to + * keep more state so we can disable the HEURISTIC code for + * the first few seconds after rebinding. + */ + if (dom->dom_state == DOM_NEW || + dom->dom_state == DOM_LOST || + dom->dom_state == DOM_DEAD) { + dom->dom_asktime = 0; + } +#endif + + /* + * Take the address we got the message from (or in the case of + * ypset, the explicit address we were given) as the server + * address for this domain, mark the domain alive, and we'll + * check it again in 60 seconds. + * + * XXX: it looks like if we get a random unsolicited reply + * from somewhere, we'll silently switch to that server + * address, regardless of merit. + * + * 1. If we have a foo.ypservers file the address should be + * checked against it and rejected if it's not one of the + * addresses of one of the listed hostnames. Note that it + * might not be the same address we sent to; even fairly smart + * UDP daemons don't always handle multihomed hosts correctly + * and we can't expect sunrpc code to do anything intelligent + * at all. + * + * 2. If we're in broadcast mode the address should be + * checked against the local addresses and netmasks so we + * don't accept responses from Mars. + * + * 2a. If we're in broadcast mode and we've been ypset, we + * should not accept anything else until we drop the ypset + * state for not responding. + * + * 3. Either way we should not accept a response from an + * arbitrary host unless we don't currently have a binding. + * (This is now fixed above.) + * + * Note that for a random unsolicited reply to work it has to + * carry the XID of one of the domains we know about; but + * those values are predictable. + */ + (void)memcpy(&dom->dom_server_addr, raddrp, + sizeof(dom->dom_server_addr)); + /* recheck binding in 60 seconds */ + dom->dom_checktime = time(NULL) + 60; + dom->dom_state = DOM_ALIVE; + + /* Clear the dead/backoff state. */ + dom->dom_losttime = 0; + dom->dom_backofftime = 10; + + if (is_ypset == 0) { + yp_log(LOG_NOTICE, "Domain %s is alive; server %s", + dom->dom_name, + inet_ntoa(dom->dom_server_addr.sin_addr)); + } + + /* + * Generate a new binding file. If this fails, forget about it. + * (But we keep the binding and we'll report it to anyone who + * asks via the ypbind service.) XXX: this will interact badly, + * maybe very badly, with the code in HEURISTIC. + * + * Note that makelock() doesn't log on failure. + */ + + if (dom->dom_lockfd != -1) + (void)close(dom->dom_lockfd); + + if ((fd = makelock(dom)) == -1) + return; + + dom->dom_lockfd = fd; + + iov[0].iov_base = &(udptransp->xp_port); + iov[0].iov_len = sizeof udptransp->xp_port; + iov[1].iov_base = &ybr; + iov[1].iov_len = sizeof ybr; + + (void)memset(&ybr, 0, sizeof ybr); + ybr.ypbind_status = YPBIND_SUCC_VAL; + ybr.ypbind_respbody.ypbind_bindinfo.ypbind_binding_addr = + raddrp->sin_addr; + ybr.ypbind_respbody.ypbind_bindinfo.ypbind_binding_port = + raddrp->sin_port; + + result = writev(dom->dom_lockfd, iov, 2); + if (result < 0 || (size_t)result != iov[0].iov_len + iov[1].iov_len) { + if (result < 0) + yp_log(LOG_WARNING, "writev: %s", strerror(errno)); + else + yp_log(LOG_WARNING, "writev: short count"); + (void)close(dom->dom_lockfd); + removelock(dom); + dom->dom_lockfd = -1; + } +} + +/* + * The NULL call: do nothing. This is obliged to exist because of + * sunrpc silliness. + */ static void * /*ARGSUSED*/ ypbindproc_null_2(SVCXPRT *transp, void *argp) { static char res; -#ifdef DEBUG - if (debug) - (void)printf("ypbindproc_null_2\n"); -#endif + DPRINTF("ypbindproc_null_2\n"); (void)memset(&res, 0, sizeof(res)); return (void *)&res; } +/* + * The DOMAIN call: look up the ypserver for a specified domain. + */ static void * /*ARGSUSED*/ ypbindproc_domain_2(SVCXPRT *transp, void *argp) { static struct ypbind_resp res; - struct _dom_binding *ypdb; + struct domain *dom; char *arg = *(char **) argp; time_t now; int count; -#ifdef DEBUG - if (debug) - (void)printf("ypbindproc_domain_2 %s\n", arg); -#endif + DPRINTF("ypbindproc_domain_2 %s\n", arg); + + /* Reject invalid domains. */ if (_yp_invalid_domain(arg)) return NULL; (void)memset(&res, 0, sizeof res); res.ypbind_status = YPBIND_FAIL_VAL; - for (count = 0, ypdb = ypbindlist; - ypdb != NULL; - ypdb = ypdb->dom_pnext, count++) { + /* + * Look for the domain. XXX: Behave erratically if we have + * more than 100 domains. The intent here is to avoid allowing + * arbitrary incoming requests to create more than 100 + * domains; but this logic means that if we legitimately have + * more than 100 (e.g. via ypset) we'll only actually bind the + * first 100 and the rest will fail. The test on 'count' should + * be moved further down. + */ + for (count = 0, dom = domains; + dom != NULL; + dom = dom->dom_next, count++) { if (count > 100) return NULL; /* prevent denial of service */ - if (!strcmp(ypdb->dom_domain, arg)) + if (!strcmp(dom->dom_name, arg)) break; } - if (ypdb == NULL) { - ypdb = makebinding(arg); - ypdb->dom_vers = YPVERS; - ypdb->dom_alive = 0; - ypdb->dom_lockfd = -1; - removelock(ypdb); - ypdb->dom_xid = unique_xid(ypdb); - ypdb->dom_pnext = ypbindlist; - ypbindlist = ypdb; + /* + * If the domain doesn't exist, create it, then fail the call + * because we have no information yet. + * + * Set "check" so that checkwork() will run and look for a + * server. + * + * XXX: like during startup there's a spurious call to + * removelock() after domain_create(). + */ + if (dom == NULL) { + dom = domain_create(arg); + removelock(dom); check++; -#ifdef DEBUG - if (debug) - (void)printf("unknown domain %s\n", arg); -#endif + DPRINTF("unknown domain %s\n", arg); return NULL; } - if (ypdb->dom_alive == 0) { -#ifdef DEBUG - if (debug) - (void)printf("dead domain %s\n", arg); -#endif + if (dom->dom_state == DOM_NEW) { + DPRINTF("new domain %s\n", arg); return NULL; } #ifdef HEURISTIC + /* + * Keep track of the last time we were explicitly asked about + * this domain. If it happens a lot, force a ping. This works + * (or "works") because we only get asked specifically when + * things aren't going; otherwise the client code in libc and + * elsewhere uses the binding file. + * + * Note: HEURISTIC is enabled by default. + * + * dholland 20140609: I think this is part of the mechanism + * that causes ypbind to spam. I'm changing this logic so it + * only triggers when the state is DOM_ALIVE: if the domain + * is new, lost, or dead we shouldn't send more requests than + * the ones already scheduled, and if we're already in the + * middle of pinging there's no point doing it again. + */ (void)time(&now); - if (now < ypdb->dom_ask_t + 5) { + if (dom->dom_state == DOM_ALIVE && now < dom->dom_asktime + 5) { /* * Hmm. More than 2 requests in 5 seconds have indicated * that my binding is possibly incorrect. * Ok, do an immediate poll of the server. */ - if (ypdb->dom_check_t >= now) { + if (dom->dom_checktime >= now) { /* don't flood it */ - ypdb->dom_check_t = 0; + dom->dom_checktime = 0; check++; } } - ypdb->dom_ask_t = now; + dom->dom_asktime = now; #endif res.ypbind_status = YPBIND_SUCC_VAL; res.ypbind_respbody.ypbind_bindinfo.ypbind_binding_addr.s_addr = - ypdb->dom_server_addr.sin_addr.s_addr; + dom->dom_server_addr.sin_addr.s_addr; res.ypbind_respbody.ypbind_bindinfo.ypbind_binding_port = - ypdb->dom_server_addr.sin_port; -#ifdef DEBUG - if (debug) - (void)printf("domain %s at %s/%d\n", ypdb->dom_domain, - inet_ntoa(ypdb->dom_server_addr.sin_addr), - ntohs(ypdb->dom_server_addr.sin_port)); -#endif + dom->dom_server_addr.sin_port; + DPRINTF("domain %s at %s/%d\n", dom->dom_name, + inet_ntoa(dom->dom_server_addr.sin_addr), + ntohs(dom->dom_server_addr.sin_port)); return &res; } +/* + * The SETDOM call: ypset. + * + * Unless -ypsetme was given on the command line, this is rejected; + * even then it's only allowed from localhost unless -ypset was + * given on the command line. + * + * Allowing anyone anywhere to ypset you (and therefore provide your + * password file and such) is a horrible thing and it isn't clear to + * me why this functionality even exists. + * + * ypset from localhost has some but limited utility. + */ static void * ypbindproc_setdom_2(SVCXPRT *transp, void *argp) { @@ -372,70 +817,68 @@ ypbindproc_setdom_2(SVCXPRT *transp, voi struct sockaddr_in *fromsin, bindsin; static bool_t res; -#ifdef DEBUG - if (debug) - (void)printf("ypbindproc_setdom_2 %s\n", inet_ntoa(bindsin.sin_addr)); -#endif (void)memset(&res, 0, sizeof(res)); fromsin = svc_getcaller(transp); + DPRINTF("ypbindproc_setdom_2 from %s\n", inet_ntoa(fromsin->sin_addr)); + + /* + * Reject unless enabled. + */ - switch (ypbindmode) { - case YPBIND_SETLOCAL: + if (allow_any_ypset) { + /* nothing */ + } else if (allow_local_ypset) { if (fromsin->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { -#ifdef DEBUG - if (debug) - (void)printf("ypset from %s denied\n", - inet_ntoa(fromsin->sin_addr)); -#endif + DPRINTF("ypset denied from %s\n", + inet_ntoa(fromsin->sin_addr)); return NULL; } - /* FALLTHROUGH */ - - case YPBIND_SETALL: - been_ypset = 1; - break; - - case YPBIND_DIRECT: - case YPBIND_BROADCAST: - default: -#ifdef DEBUG - if (debug) - (void)printf("ypset denied\n"); -#endif + } else { + DPRINTF("ypset denied\n"); return NULL; } + /* Make a "security" check. */ if (ntohs(fromsin->sin_port) >= IPPORT_RESERVED) { -#ifdef DEBUG - if (debug) - (void)printf("ypset from unprivileged port denied\n"); -#endif + DPRINTF("ypset from unprivileged port denied\n"); return &res; } + /* Ignore requests we don't understand. */ if (sd->ypsetdom_vers != YPVERS) { -#ifdef DEBUG - if (debug) - (void)printf("ypset with wrong version denied\n"); -#endif + DPRINTF("ypset with wrong version denied\n"); return &res; } + /* + * Fetch the arguments out of the xdr-decoded blob and call + * rpc_received(), setting FORCE so that the domain will be + * created if we don't already know about it, and also saying + * that it's actually a ypset. + * + * Effectively we're telilng rpc_received() that we got an + * RPC response from the server specified by ypset. + */ (void)memset(&bindsin, 0, sizeof bindsin); bindsin.sin_family = AF_INET; bindsin.sin_len = sizeof(bindsin); bindsin.sin_addr = sd->ypsetdom_addr; bindsin.sin_port = sd->ypsetdom_port; - rpc_received(sd->ypsetdom_domain, &bindsin, 1); + rpc_received(sd->ypsetdom_domain, &bindsin, 1, 1); -#ifdef DEBUG - if (debug) - (void)printf("ypset to %s succeeded\n", inet_ntoa(bindsin.sin_addr)); -#endif + DPRINTF("ypset to %s for domain %s succeeded\n", + inet_ntoa(bindsin.sin_addr), sd->ypsetdom_domain); res = 1; return &res; } +/* + * Dispatcher for the ypbind service. + * + * There are three calls: NULL, which does nothing, DOMAIN, which + * gets the binding for a particular domain, and SETDOM, which + * does ypset. + */ static void ypbindprog_2(struct svc_req *rqstp, register SVCXPRT *transp) { @@ -451,14 +894,14 @@ ypbindprog_2(struct svc_req *rqstp, regi switch (rqstp->rq_proc) { case YPBINDPROC_NULL: - xdr_argument = xdr_void; - xdr_result = xdr_void; + xdr_argument = (xdrproc_t)xdr_void; + xdr_result = (xdrproc_t)xdr_void; local = ypbindproc_null_2; break; case YPBINDPROC_DOMAIN: - xdr_argument = xdr_ypdomain_wrap_string; - xdr_result = xdr_ypbind_resp; + xdr_argument = (xdrproc_t)xdr_ypdomain_wrap_string; + xdr_result = (xdrproc_t)xdr_ypbind_resp; local = ypbindproc_domain_2; break; @@ -476,8 +919,8 @@ ypbindprog_2(struct svc_req *rqstp, regi return; } - xdr_argument = xdr_ypbind_setdom; - xdr_result = xdr_void; + xdr_argument = (xdrproc_t)xdr_ypbind_setdom; + xdr_result = (xdrproc_t)xdr_void; local = ypbindproc_setdom_2; break; @@ -497,68 +940,16 @@ ypbindprog_2(struct svc_req *rqstp, regi return; } -int -main(int argc, char *argv[]) +/* + * Set up sunrpc stuff. + * + * This sets up the ypbind service (both TCP and UDP) and also opens + * the sockets we use for talking to ypservers. + */ +static void +sunrpc_setup(void) { - struct timeval tv; - fd_set fdsr; - int width, lockfd; - int evil = 0, one; - char pathname[MAXPATHLEN]; - struct stat st; - - setprogname(argv[0]); - (void)yp_get_default_domain(&domainname); - if (domainname[0] == '\0') - errx(1, "Domainname not set. Aborting."); - - /* - * Per traditional ypbind(8) semantics, if a ypservers - * file does not exist, we default to broadcast mode. - * If the file does exist, we default to direct mode. - * Note that we can still override direct mode by passing - * the -broadcast flag. - */ - (void)snprintf(pathname, sizeof(pathname), "%s/%s%s", BINDINGDIR, - domainname, YPSERVERSSUFF); - if (stat(pathname, &st) < 0) { -#ifdef DEBUG - if (debug) - (void)printf("%s does not exist, defaulting to " - "broadcast\n", pathname); -#endif - ypbindmode = YPBIND_BROADCAST; - } else - ypbindmode = YPBIND_DIRECT; - - while (--argc) { - ++argv; - if (!strcmp("-insecure", *argv)) - insecure = 1; - else if (!strcmp("-ypset", *argv)) - ypbindmode = YPBIND_SETALL; - else if (!strcmp("-ypsetme", *argv)) - ypbindmode = YPBIND_SETLOCAL; - else if (!strcmp("-broadcast", *argv)) - ypbindmode = YPBIND_BROADCAST; -#ifdef DEBUG - else if (!strcmp("-d", *argv)) - debug++; -#endif - else - usage(); - } - - /* initialise syslog */ - openlog("ypbind", LOG_PERROR | LOG_PID, LOG_DAEMON); - - lockfd = open(_PATH_YPBIND_LOCK, O_CREAT|O_SHLOCK|O_RDWR|O_TRUNC, 0644); - if (lockfd == -1) - err(1, "Cannot create %s", _PATH_YPBIND_LOCK); - -#if O_SHLOCK == 0 - (void)flock(lockfd, LOCK_SH); -#endif + int one; (void)pmap_unset(YPBINDPROG, YPBINDVERS); @@ -596,397 +987,170 @@ main(int argc, char *argv[]) rmtca.xdr_args = NULL; /* set at call time */ rmtca.args_ptr = NULL; /* set at call time */ rmtcr.port_ptr = &rmtcr_port; - rmtcr.xdr_results = xdr_bool; + rmtcr.xdr_results = (xdrproc_t)xdr_bool; rmtcr.results_ptr = (caddr_t)(void *)&rmtcr_outval; +} - if (_yp_invalid_domain(domainname)) - errx(1, "bad domainname: %s", domainname); - - /* blow away old bindings in BINDINGDIR */ - if (purge_bindingdir(BINDINGDIR) < 0) - errx(1, "unable to purge old bindings from %s", BINDINGDIR); +//////////////////////////////////////////////////////////// +// operational logic - /* build initial domain binding, make it "unsuccessful" */ - ypbindlist = makebinding(domainname); - ypbindlist->dom_vers = YPVERS; - ypbindlist->dom_alive = 0; - ypbindlist->dom_lockfd = -1; - removelock(ypbindlist); +/* + * Broadcast an RPC packet to hopefully contact some servers for a + * domain. + */ +static int +broadcast(char *buf, int outlen) +{ + struct ifaddrs *ifap, *ifa; + struct sockaddr_in bindsin; + struct in_addr in; - checkwork(); + (void)memset(&bindsin, 0, sizeof bindsin); + bindsin.sin_family = AF_INET; + bindsin.sin_len = sizeof(bindsin); + bindsin.sin_port = htons(PMAPPORT); - for (;;) { - width = svc_maxfd; - if (rpcsock > width) - width = rpcsock; - if (pingsock > width) - width = pingsock; - width++; - fdsr = svc_fdset; - FD_SET(rpcsock, &fdsr); - FD_SET(pingsock, &fdsr); - tv.tv_sec = 1; - tv.tv_usec = 0; + if (getifaddrs(&ifap) != 0) { + yp_log(LOG_WARNING, "broadcast: getifaddrs: %s", + strerror(errno)); + return (-1); + } + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr->sa_family != AF_INET) + continue; + if ((ifa->ifa_flags & IFF_UP) == 0) + continue; - switch (select(width, &fdsr, NULL, NULL, &tv)) { - case 0: - checkwork(); + switch (ifa->ifa_flags & (IFF_LOOPBACK | IFF_BROADCAST)) { + case IFF_BROADCAST: + if (!ifa->ifa_broadaddr) + continue; + if (ifa->ifa_broadaddr->sa_family != AF_INET) + continue; + in = ((struct sockaddr_in *)(void *)ifa->ifa_broadaddr)->sin_addr; break; - case -1: - yp_log(LOG_WARNING, "select: %m"); + case IFF_LOOPBACK: + in = ((struct sockaddr_in *)(void *)ifa->ifa_addr)->sin_addr; break; default: - if (FD_ISSET(rpcsock, &fdsr)) - (void)handle_replies(); - if (FD_ISSET(pingsock, &fdsr)) - (void)handle_ping(); - svc_getreqset(&fdsr); - if (check) - checkwork(); - break; + continue; } - if (!evil && ypbindlist->dom_alive) { - evil = 1; -#ifdef DEBUG - if (!debug) -#endif - (void)daemon(0, 0); - (void)pidfile(NULL); - } + bindsin.sin_addr = in; + DPRINTF("broadcast %x\n", bindsin.sin_addr.s_addr); + if (sendto(rpcsock, buf, outlen, 0, + (struct sockaddr *)(void *)&bindsin, + (socklen_t)bindsin.sin_len) == -1) + yp_log(LOG_WARNING, "broadcast: sendto: %s", + strerror(errno)); } + freeifaddrs(ifap); + return (0); } /* - * State transition is done like this: + * Send an RPC packet to all the configured (in /var/yp/foo.ypservers) + * servers for a domain. * - * STATE EVENT ACTION NEWSTATE TIMEOUT - * no binding timeout broadcast no binding 5 sec - * no binding answer -- binding 60 sec - * binding timeout ping server checking 5 sec - * checking timeout ping server + broadcast checking 5 sec - * checking answer -- binding 60 sec + * XXX: we should read and parse the file up front and reread it only + * if it changes. */ -void -checkwork(void) +static int +direct(char *buf, int outlen, struct domain *dom) { - struct _dom_binding *ypdb; - time_t t; + const char *path; + char line[_POSIX2_LINE_MAX]; + char *p; + struct hostent *hp; + struct sockaddr_in bindsin; + int i, count = 0; - check = 0; + /* + * XXX what happens if someone's editor unlinks and replaces + * the servers file? + */ - (void)time(&t); - for (ypdb = ypbindlist; ypdb; ypdb = ypdb->dom_pnext) { - if (ypdb->dom_check_t < t) { - if (ypdb->dom_alive == 1) - (void)ping(ypdb); - else - (void)nag_servers(ypdb); - (void)time(&t); - ypdb->dom_check_t = t + 5; + if (dom->dom_serversfile != NULL) { + rewind(dom->dom_serversfile); + } else { + path = ypservers_filename(dom->dom_name); + dom->dom_serversfile = fopen(path, "r"); + if (dom->dom_serversfile == NULL) { + /* + * XXX there should be a time restriction on + * this (and/or on trying the open) so we + * don't flood the log. Or should we fall back + * to broadcast mode? + */ + yp_log(LOG_ERR, "%s: %s", path, + strerror(errno)); + return -1; } } -} - -int -ping(struct _dom_binding *ypdb) -{ - char *dom = ypdb->dom_domain; - struct rpc_msg msg; - char buf[BUFSIZE]; - enum clnt_stat st; - int outlen; - AUTH *rpcua; - XDR xdr; - (void)memset(&xdr, 0, sizeof xdr); - (void)memset(&msg, 0, sizeof msg); - - rpcua = authunix_create_default(); - if (rpcua == NULL) { -#ifdef DEBUG - if (debug) - (void)printf("cannot get unix auth\n"); -#endif - return RPC_SYSTEMERROR; - } + (void)memset(&bindsin, 0, sizeof bindsin); + bindsin.sin_family = AF_INET; + bindsin.sin_len = sizeof(bindsin); + bindsin.sin_port = htons(PMAPPORT); - msg.rm_direction = CALL; - msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; - msg.rm_call.cb_prog = YPPROG; - msg.rm_call.cb_vers = YPVERS; - msg.rm_call.cb_proc = YPPROC_DOMAIN_NONACK; - msg.rm_call.cb_cred = rpcua->ah_cred; - msg.rm_call.cb_verf = rpcua->ah_verf; + while (fgets(line, (int)sizeof(line), dom->dom_serversfile) != NULL) { + /* skip lines that are too big */ + p = strchr(line, '\n'); + if (p == NULL) { + int c; - msg.rm_xid = ypdb->dom_xid; - xdrmem_create(&xdr, buf, (u_int)sizeof(buf), XDR_ENCODE); - if (!xdr_callmsg(&xdr, &msg)) { - st = RPC_CANTENCODEARGS; - AUTH_DESTROY(rpcua); - return st; - } - if (!xdr_ypdomain_wrap_string(&xdr, &dom)) { - st = RPC_CANTENCODEARGS; - AUTH_DESTROY(rpcua); - return st; + while ((c = getc(dom->dom_serversfile)) != '\n' && c != EOF) + ; + continue; + } + *p = '\0'; + p = line; + while (isspace((unsigned char)*p)) + p++; + if (*p == '#') + continue; + hp = gethostbyname(p); + if (!hp) { + yp_log(LOG_WARNING, "%s: %s", p, hstrerror(h_errno)); + continue; + } + /* step through all addresses in case first is unavailable */ + for (i = 0; hp->h_addr_list[i]; i++) { + (void)memcpy(&bindsin.sin_addr, hp->h_addr_list[0], + hp->h_length); + if (sendto(rpcsock, buf, outlen, 0, + (struct sockaddr *)(void *)&bindsin, + (socklen_t)sizeof(bindsin)) < 0) { + yp_log(LOG_WARNING, "direct: sendto: %s", + strerror(errno)); + continue; + } else + count++; + } } - outlen = (int)xdr_getpos(&xdr); - xdr_destroy(&xdr); - if (outlen < 1) { - st = RPC_CANTENCODEARGS; - AUTH_DESTROY(rpcua); - return st; + if (!count) { + yp_log(LOG_WARNING, "No contactable servers found in %s", + ypservers_filename(dom->dom_name)); + return -1; } - AUTH_DESTROY(rpcua); - - ypdb->dom_alive = 2; -#ifdef DEBUG - if (debug) - (void)printf("ping %x\n", - ypdb->dom_server_addr.sin_addr.s_addr); -#endif - if (sendto(pingsock, buf, outlen, 0, - (struct sockaddr *)(void *)&ypdb->dom_server_addr, - (socklen_t)sizeof ypdb->dom_server_addr) == -1) - yp_log(LOG_WARNING, "ping: sendto: %m"); return 0; - } +/* + * Send an RPC packet to the server that's been selected with ypset. + * (This is only used when in broadcast mode and when ypset is + * allowed.) + */ static int -nag_servers(struct _dom_binding *ypdb) +direct_set(char *buf, int outlen, struct domain *dom) { - char *dom = ypdb->dom_domain; - struct rpc_msg msg; - char buf[BUFSIZE]; - enum clnt_stat st; - int outlen; - AUTH *rpcua; - XDR xdr; - -#ifdef DEBUG - if (debug) - (void)printf("nag_servers\n"); -#endif - rmtca.xdr_args = xdr_ypdomain_wrap_string; - rmtca.args_ptr = (caddr_t)(void *)&dom; - - (void)memset(&xdr, 0, sizeof xdr); - (void)memset(&msg, 0, sizeof msg); - - rpcua = authunix_create_default(); - if (rpcua == NULL) { -#ifdef DEBUG - if (debug) - (void)printf("cannot get unix auth\n"); -#endif - return RPC_SYSTEMERROR; - } - msg.rm_direction = CALL; - msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; - msg.rm_call.cb_prog = PMAPPROG; - msg.rm_call.cb_vers = PMAPVERS; - msg.rm_call.cb_proc = PMAPPROC_CALLIT; - msg.rm_call.cb_cred = rpcua->ah_cred; - msg.rm_call.cb_verf = rpcua->ah_verf; - - msg.rm_xid = ypdb->dom_xid; - xdrmem_create(&xdr, buf, (u_int)sizeof(buf), XDR_ENCODE); - if (!xdr_callmsg(&xdr, &msg)) { - st = RPC_CANTENCODEARGS; - AUTH_DESTROY(rpcua); - return st; - } - if (!xdr_rmtcall_args(&xdr, &rmtca)) { - st = RPC_CANTENCODEARGS; - AUTH_DESTROY(rpcua); - return st; - } - outlen = (int)xdr_getpos(&xdr); - xdr_destroy(&xdr); - if (outlen < 1) { - st = RPC_CANTENCODEARGS; - AUTH_DESTROY(rpcua); - return st; - } - AUTH_DESTROY(rpcua); - - if (ypdb->dom_lockfd != -1) { - (void)close(ypdb->dom_lockfd); - ypdb->dom_lockfd = -1; - removelock(ypdb); - } - - if (ypdb->dom_alive == 2) { - /* - * This resolves the following situation: - * ypserver on other subnet was once bound, - * but rebooted and is now using a different port - */ - struct sockaddr_in bindsin; - - (void)memset(&bindsin, 0, sizeof bindsin); - bindsin.sin_family = AF_INET; - bindsin.sin_len = sizeof(bindsin); - bindsin.sin_port = htons(PMAPPORT); - bindsin.sin_addr = ypdb->dom_server_addr.sin_addr; - - if (sendto(rpcsock, buf, outlen, 0, - (struct sockaddr *)(void *)&bindsin, - (socklen_t)sizeof bindsin) == -1) - yp_log(LOG_WARNING, "nag_servers: sendto: %m"); - } - - switch (ypbindmode) { - case YPBIND_SETALL: - case YPBIND_SETLOCAL: - if (been_ypset) - return direct_set(buf, outlen, ypdb); - /* FALLTHROUGH */ - - case YPBIND_BROADCAST: - return broadcast(buf, outlen); - - case YPBIND_DIRECT: - return direct(buf, outlen); - } - /*NOTREACHED*/ - return -1; -} - -static int -broadcast(char *buf, int outlen) -{ - struct ifaddrs *ifap, *ifa; - struct sockaddr_in bindsin; - struct in_addr in; - - (void)memset(&bindsin, 0, sizeof bindsin); - bindsin.sin_family = AF_INET; - bindsin.sin_len = sizeof(bindsin); - bindsin.sin_port = htons(PMAPPORT); - - if (getifaddrs(&ifap) != 0) { - yp_log(LOG_WARNING, "broadcast: getifaddrs: %m"); - return (-1); - } - for (ifa = ifap; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_addr->sa_family != AF_INET) - continue; - if ((ifa->ifa_flags & IFF_UP) == 0) - continue; - - switch (ifa->ifa_flags & (IFF_LOOPBACK | IFF_BROADCAST)) { - case IFF_BROADCAST: - if (!ifa->ifa_broadaddr) - continue; - if (ifa->ifa_broadaddr->sa_family != AF_INET) - continue; - in = ((struct sockaddr_in *)(void *)ifa->ifa_broadaddr)->sin_addr; - break; - case IFF_LOOPBACK: - in = ((struct sockaddr_in *)(void *)ifa->ifa_addr)->sin_addr; - break; - default: - continue; - } - - bindsin.sin_addr = in; -#ifdef DEBUG - if (debug) - (void)printf("broadcast %x\n", - bindsin.sin_addr.s_addr); -#endif - if (sendto(rpcsock, buf, outlen, 0, - (struct sockaddr *)(void *)&bindsin, - (socklen_t)bindsin.sin_len) == -1) - yp_log(LOG_WARNING, "broadcast: sendto: %m"); - } - freeifaddrs(ifap); - return (0); -} - -static int -direct(char *buf, int outlen) -{ - static FILE *df; - static char ypservers_path[MAXPATHLEN]; - char line[_POSIX2_LINE_MAX]; - char *p; - struct hostent *hp; - struct sockaddr_in bindsin; - int i, count = 0; - - if (df) - rewind(df); - else { - (void)snprintf(ypservers_path, sizeof(ypservers_path), - "%s/%s%s", BINDINGDIR, domainname, YPSERVERSSUFF); - df = fopen(ypservers_path, "r"); - if (df == NULL) { - yp_log(LOG_ERR, "%s: ", ypservers_path); - exit(1); - } - } - - (void)memset(&bindsin, 0, sizeof bindsin); - bindsin.sin_family = AF_INET; - bindsin.sin_len = sizeof(bindsin); - bindsin.sin_port = htons(PMAPPORT); - - while(fgets(line, (int)sizeof(line), df) != NULL) { - /* skip lines that are too big */ - p = strchr(line, '\n'); - if (p == NULL) { - int c; - - while ((c = getc(df)) != '\n' && c != EOF) - ; - continue; - } - *p = '\0'; - p = line; - while (isspace((unsigned char)*p)) - p++; - if (*p == '#') - continue; - hp = gethostbyname(p); - if (!hp) { - yp_log(LOG_WARNING, "%s: %s", p, hstrerror(h_errno)); - continue; - } - /* step through all addresses in case first is unavailable */ - for (i = 0; hp->h_addr_list[i]; i++) { - (void)memcpy(&bindsin.sin_addr, hp->h_addr_list[0], - hp->h_length); - if (sendto(rpcsock, buf, outlen, 0, - (struct sockaddr *)(void *)&bindsin, - (socklen_t)sizeof(bindsin)) < 0) { - yp_log(LOG_WARNING, "direct: sendto: %m"); - continue; - } else - count++; - } - } - if (!count) { - yp_log(LOG_WARNING, "no contactable servers found in %s", - ypservers_path); - return -1; - } - return 0; -} - -static int -direct_set(char *buf, int outlen, struct _dom_binding *ypdb) -{ - struct sockaddr_in bindsin; - char path[MAXPATHLEN]; - struct iovec iov[2]; - struct ypbind_resp ybr; - SVCXPRT dummy_svc; - int fd; - ssize_t bytes; + struct sockaddr_in bindsin; + char path[MAXPATHLEN]; + struct iovec iov[2]; + struct ypbind_resp ybr; + SVCXPRT dummy_svc; + int fd; + ssize_t bytes; /* * Gack, we lose if binding file went away. We reset @@ -994,18 +1158,15 @@ direct_set(char *buf, int outlen, struct * bind again. */ (void)snprintf(path, sizeof(path), "%s/%s.%ld", BINDINGDIR, - ypdb->dom_domain, ypdb->dom_vers); + dom->dom_name, dom->dom_vers); - if ((fd = open(path, O_SHLOCK|O_RDONLY, 0644)) == -1) { - yp_log(LOG_WARNING, "%s: %m", path); - been_ypset = 0; + fd = open_locked(path, O_RDONLY, 0644); + if (fd == -1) { + yp_log(LOG_WARNING, "%s: %s", path, strerror(errno)); + dom->dom_been_ypset = 0; return -1; } -#if O_SHLOCK == 0 - (void)flock(fd, LOCK_SH); -#endif - /* Read the binding file... */ iov[0].iov_base = &(dummy_svc.xp_port); iov[0].iov_len = sizeof(dummy_svc.xp_port); @@ -1013,10 +1174,13 @@ direct_set(char *buf, int outlen, struct iov[1].iov_len = sizeof(ybr); bytes = readv(fd, iov, 2); (void)close(fd); - if ((size_t)bytes != (iov[0].iov_len + iov[1].iov_len)) { + if (bytes <0 || (size_t)bytes != (iov[0].iov_len + iov[1].iov_len)) { /* Binding file corrupt? */ - yp_log(LOG_WARNING, "%s: %m", path); - been_ypset = 0; + if (bytes < 0) + yp_log(LOG_WARNING, "%s: %s", path, strerror(errno)); + else + yp_log(LOG_WARNING, "%s: short read", path); + dom->dom_been_ypset = 0; return -1; } @@ -1026,34 +1190,34 @@ direct_set(char *buf, int outlen, struct if (sendto(rpcsock, buf, outlen, 0, (struct sockaddr *)(void *)&bindsin, (socklen_t)sizeof(bindsin)) < 0) { - yp_log(LOG_WARNING, "direct_set: sendto: %m"); + yp_log(LOG_WARNING, "direct_set: sendto: %s", strerror(errno)); return -1; } return 0; } +/* + * Receive and dispatch packets on the general RPC socket. + */ static enum clnt_stat handle_replies(void) { char buf[BUFSIZE]; socklen_t fromlen; ssize_t inlen; - struct _dom_binding *ypdb; + struct domain *dom; struct sockaddr_in raddr; struct rpc_msg msg; XDR xdr; recv_again: -#ifdef DEBUG - if (debug) - printf("handle_replies receiving\n"); -#endif + DPRINTF("handle_replies receiving\n"); (void)memset(&xdr, 0, sizeof(xdr)); (void)memset(&msg, 0, sizeof(msg)); msg.acpted_rply.ar_verf = _null_auth; msg.acpted_rply.ar_results.where = (caddr_t)(void *)&rmtcr; - msg.acpted_rply.ar_results.proc = xdr_rmtcallres; + msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_rmtcallres; try_again: fromlen = sizeof(struct sockaddr); @@ -1062,59 +1226,56 @@ try_again: if (inlen < 0) { if (errno == EINTR) goto try_again; -#ifdef DEBUG - if (debug) - printf("handle_replies: recvfrom failed (%s)\n", - strerror(errno)); -#endif + DPRINTF("handle_replies: recvfrom failed (%s)\n", + strerror(errno)); return RPC_CANTRECV; } - if ((size_t)inlen < sizeof(u_int32_t)) + if ((size_t)inlen < sizeof(uint32_t)) goto recv_again; /* * see if reply transaction id matches sent id. * If so, decode the results. */ - xdrmem_create(&xdr, buf, (u_int)inlen, XDR_DECODE); + xdrmem_create(&xdr, buf, (unsigned)inlen, XDR_DECODE); if (xdr_replymsg(&xdr, &msg)) { if ((msg.rm_reply.rp_stat == MSG_ACCEPTED) && (msg.acpted_rply.ar_stat == SUCCESS)) { - raddr.sin_port = htons((u_short)rmtcr_port); - ypdb = xid2ypdb(msg.rm_xid); - if (ypdb != NULL) - rpc_received(ypdb->dom_domain, &raddr, 0); + raddr.sin_port = htons((uint16_t)rmtcr_port); + dom = domain_find(msg.rm_xid); + if (dom != NULL) + rpc_received(dom->dom_name, &raddr, 0, 0); } } xdr.x_op = XDR_FREE; - msg.acpted_rply.ar_results.proc = xdr_void; + msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void; xdr_destroy(&xdr); return RPC_SUCCESS; } +/* + * Receive and dispatch packets on the ping socket. + */ static enum clnt_stat handle_ping(void) { char buf[BUFSIZE]; socklen_t fromlen; ssize_t inlen; - struct _dom_binding *ypdb; + struct domain *dom; struct sockaddr_in raddr; struct rpc_msg msg; XDR xdr; bool_t res; recv_again: -#ifdef DEBUG - if (debug) - printf("handle_ping receiving\n"); -#endif + DPRINTF("handle_ping receiving\n"); (void)memset(&xdr, 0, sizeof(xdr)); (void)memset(&msg, 0, sizeof(msg)); msg.acpted_rply.ar_verf = _null_auth; msg.acpted_rply.ar_results.where = (caddr_t)(void *)&res; - msg.acpted_rply.ar_results.proc = xdr_bool; + msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_bool; try_again: fromlen = sizeof (struct sockaddr); @@ -1123,146 +1284,511 @@ try_again: if (inlen < 0) { if (errno == EINTR) goto try_again; -#ifdef DEBUG - if (debug) - printf("handle_ping: recvfrom failed (%s)\n", - strerror(errno)); -#endif + DPRINTF("handle_ping: recvfrom failed (%s)\n", + strerror(errno)); return RPC_CANTRECV; } - if ((size_t)inlen < sizeof(u_int32_t)) + if ((size_t)inlen < sizeof(uint32_t)) goto recv_again; /* * see if reply transaction id matches sent id. * If so, decode the results. */ - xdrmem_create(&xdr, buf, (u_int)inlen, XDR_DECODE); + xdrmem_create(&xdr, buf, (unsigned)inlen, XDR_DECODE); if (xdr_replymsg(&xdr, &msg)) { if ((msg.rm_reply.rp_stat == MSG_ACCEPTED) && (msg.acpted_rply.ar_stat == SUCCESS)) { - ypdb = xid2ypdb(msg.rm_xid); - if (ypdb != NULL) - rpc_received(ypdb->dom_domain, &raddr, 0); + dom = domain_find(msg.rm_xid); + if (dom != NULL) + rpc_received(dom->dom_name, &raddr, 0, 0); } } xdr.x_op = XDR_FREE; - msg.acpted_rply.ar_results.proc = xdr_void; + msg.acpted_rply.ar_results.proc = (xdrproc_t)xdr_void; xdr_destroy(&xdr); return RPC_SUCCESS; } /* - * LOOPBACK IS MORE IMPORTANT: PUT IN HACK + * Contact all known servers for a domain in the hopes that one of + * them's awake. Also, if we previously had a binding but it timed + * out, try the portmapper on that host in case ypserv moved ports for + * some reason. + * + * As a side effect, wipe out any existing binding file. */ -void -rpc_received(char *dom, struct sockaddr_in *raddrp, int force) +static int +nag_servers(struct domain *dom) { - struct _dom_binding *ypdb; - struct iovec iov[2]; - struct ypbind_resp ybr; - int fd; + char *dom_name = dom->dom_name; + struct rpc_msg msg; + char buf[BUFSIZE]; + enum clnt_stat st; + int outlen; + AUTH *rpcua; + XDR xdr; -#ifdef DEBUG - if (debug) - (void)printf("returned from %s about %s\n", - inet_ntoa(raddrp->sin_addr), dom); -#endif + DPRINTF("nag_servers\n"); + rmtca.xdr_args = (xdrproc_t)xdr_ypdomain_wrap_string; + rmtca.args_ptr = (caddr_t)(void *)&dom_name; - if (dom == NULL) - return; + (void)memset(&xdr, 0, sizeof xdr); + (void)memset(&msg, 0, sizeof msg); - if (_yp_invalid_domain(dom)) - return; + rpcua = authunix_create_default(); + if (rpcua == NULL) { + DPRINTF("cannot get unix auth\n"); + return RPC_SYSTEMERROR; + } + msg.rm_direction = CALL; + msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; + msg.rm_call.cb_prog = PMAPPROG; + msg.rm_call.cb_vers = PMAPVERS; + msg.rm_call.cb_proc = PMAPPROC_CALLIT; + msg.rm_call.cb_cred = rpcua->ah_cred; + msg.rm_call.cb_verf = rpcua->ah_verf; - /* don't support insecure servers by default */ - if (!insecure && ntohs(raddrp->sin_port) >= IPPORT_RESERVED) - return; + msg.rm_xid = dom->dom_xid; + xdrmem_create(&xdr, buf, (unsigned)sizeof(buf), XDR_ENCODE); + if (!xdr_callmsg(&xdr, &msg)) { + st = RPC_CANTENCODEARGS; + AUTH_DESTROY(rpcua); + return st; + } + if (!xdr_rmtcall_args(&xdr, &rmtca)) { + st = RPC_CANTENCODEARGS; + AUTH_DESTROY(rpcua); + return st; + } + outlen = (int)xdr_getpos(&xdr); + xdr_destroy(&xdr); + if (outlen < 1) { + st = RPC_CANTENCODEARGS; + AUTH_DESTROY(rpcua); + return st; + } + AUTH_DESTROY(rpcua); - for (ypdb = ypbindlist; ypdb; ypdb = ypdb->dom_pnext) - if (!strcmp(ypdb->dom_domain, dom)) - break; + if (dom->dom_lockfd != -1) { + (void)close(dom->dom_lockfd); + dom->dom_lockfd = -1; + removelock(dom); + } - if (ypdb == NULL) { - if (force == 0) - return; - ypdb = makebinding(dom); - ypdb->dom_lockfd = -1; - ypdb->dom_pnext = ypbindlist; - ypbindlist = ypdb; + if (dom->dom_state == DOM_PINGING || dom->dom_state == DOM_LOST) { + /* + * This resolves the following situation: + * ypserver on other subnet was once bound, + * but rebooted and is now using a different port + */ + struct sockaddr_in bindsin; + + (void)memset(&bindsin, 0, sizeof bindsin); + bindsin.sin_family = AF_INET; + bindsin.sin_len = sizeof(bindsin); + bindsin.sin_port = htons(PMAPPORT); + bindsin.sin_addr = dom->dom_server_addr.sin_addr; + + if (sendto(rpcsock, buf, outlen, 0, + (struct sockaddr *)(void *)&bindsin, + (socklen_t)sizeof bindsin) == -1) + yp_log(LOG_WARNING, "nag_servers: sendto: %s", + strerror(errno)); } - /* soft update, alive */ - if (ypdb->dom_alive == 1 && force == 0) { - if (!memcmp(&ypdb->dom_server_addr, raddrp, - sizeof ypdb->dom_server_addr)) { - ypdb->dom_alive = 1; - /* recheck binding in 60 sec */ - ypdb->dom_check_t = time(NULL) + 60; + switch (dom->dom_ypbindmode) { + case YPBIND_BROADCAST: + if (dom->dom_been_ypset) { + return direct_set(buf, outlen, dom); } - return; - } - - (void)memcpy(&ypdb->dom_server_addr, raddrp, - sizeof ypdb->dom_server_addr); - /* recheck binding in 60 seconds */ - ypdb->dom_check_t = time(NULL) + 60; - ypdb->dom_vers = YPVERS; - ypdb->dom_alive = 1; + return broadcast(buf, outlen); - if (ypdb->dom_lockfd != -1) - (void)close(ypdb->dom_lockfd); + case YPBIND_DIRECT: + return direct(buf, outlen, dom); + } + /*NOTREACHED*/ + return -1; +} - if ((fd = makelock(ypdb)) == -1) - return; +/* + * Send a ping message to a domain's current ypserver. + */ +static int +ping(struct domain *dom) +{ + char *dom_name = dom->dom_name; + struct rpc_msg msg; + char buf[BUFSIZE]; + enum clnt_stat st; + int outlen; + AUTH *rpcua; + XDR xdr; - /* - * ok, if BINDINGDIR exists, and we can create the binding file, - * then write to it.. - */ - ypdb->dom_lockfd = fd; + (void)memset(&xdr, 0, sizeof xdr); + (void)memset(&msg, 0, sizeof msg); - iov[0].iov_base = &(udptransp->xp_port); - iov[0].iov_len = sizeof udptransp->xp_port; - iov[1].iov_base = &ybr; - iov[1].iov_len = sizeof ybr; + rpcua = authunix_create_default(); + if (rpcua == NULL) { + DPRINTF("cannot get unix auth\n"); + return RPC_SYSTEMERROR; + } - (void)memset(&ybr, 0, sizeof ybr); - ybr.ypbind_status = YPBIND_SUCC_VAL; - ybr.ypbind_respbody.ypbind_bindinfo.ypbind_binding_addr = - raddrp->sin_addr; - ybr.ypbind_respbody.ypbind_bindinfo.ypbind_binding_port = - raddrp->sin_port; + msg.rm_direction = CALL; + msg.rm_call.cb_rpcvers = RPC_MSG_VERSION; + msg.rm_call.cb_prog = YPPROG; + msg.rm_call.cb_vers = YPVERS; + msg.rm_call.cb_proc = YPPROC_DOMAIN_NONACK; + msg.rm_call.cb_cred = rpcua->ah_cred; + msg.rm_call.cb_verf = rpcua->ah_verf; - if ((size_t)writev(ypdb->dom_lockfd, iov, 2) != - iov[0].iov_len + iov[1].iov_len) { - yp_log(LOG_WARNING, "writev: %m"); - (void)close(ypdb->dom_lockfd); - removelock(ypdb); - ypdb->dom_lockfd = -1; + msg.rm_xid = dom->dom_xid; + xdrmem_create(&xdr, buf, (unsigned)sizeof(buf), XDR_ENCODE); + if (!xdr_callmsg(&xdr, &msg)) { + st = RPC_CANTENCODEARGS; + AUTH_DESTROY(rpcua); + return st; + } + if (!xdr_ypdomain_wrap_string(&xdr, &dom_name)) { + st = RPC_CANTENCODEARGS; + AUTH_DESTROY(rpcua); + return st; + } + outlen = (int)xdr_getpos(&xdr); + xdr_destroy(&xdr); + if (outlen < 1) { + st = RPC_CANTENCODEARGS; + AUTH_DESTROY(rpcua); + return st; } + AUTH_DESTROY(rpcua); + + DPRINTF("ping %x\n", dom->dom_server_addr.sin_addr.s_addr); + + if (sendto(pingsock, buf, outlen, 0, + (struct sockaddr *)(void *)&dom->dom_server_addr, + (socklen_t)(sizeof dom->dom_server_addr)) == -1) + yp_log(LOG_WARNING, "ping: sendto: %s", strerror(errno)); + return 0; + } -static struct _dom_binding * -xid2ypdb(u_int32_t xid) +/* + * Scan for timer-based work to do. + * + * If the domain is currently alive, ping the server we're currently + * bound to. Otherwise, try all known servers and/or broadcast for a + * server via nag_servers. + * + * Try again in five seconds. + * + * If we get back here and the state is still DOM_PINGING, it means + * we didn't receive a ping response within five seconds. Declare the + * binding lost. If the binding is already lost, and it's been lost + * for 60 seconds, switch to DOM_DEAD and begin exponential backoff. + * The exponential backoff starts at 10 seconds and tops out at one + * hour; see above. + */ +static void +checkwork(void) { - struct _dom_binding *ypdb; + struct domain *dom; + time_t t; - for (ypdb = ypbindlist; ypdb; ypdb = ypdb->dom_pnext) - if (ypdb->dom_xid == xid) + check = 0; + + (void)time(&t); + for (dom = domains; dom != NULL; dom = dom->dom_next) { + if (dom->dom_checktime >= t) { + continue; + } + switch (dom->dom_state) { + case DOM_NEW: + /* XXX should be a timeout for this state */ + dom->dom_checktime = t + 5; + (void)nag_servers(dom); + break; + + case DOM_ALIVE: + dom->dom_state = DOM_PINGING; + dom->dom_checktime = t + 5; + (void)ping(dom); + break; + + case DOM_PINGING: + dom->dom_state = DOM_LOST; + dom->dom_losttime = t; + dom->dom_checktime = t + 5; + yp_log(LOG_NOTICE, "Domain %s lost its binding to " + "server %s", dom->dom_name, + inet_ntoa(dom->dom_server_addr.sin_addr)); + (void)nag_servers(dom); break; - return (ypdb); + + case DOM_LOST: + if (t > dom->dom_losttime + 60) { + dom->dom_state = DOM_DEAD; + dom->dom_backofftime = 10; + yp_log(LOG_NOTICE, "Domain %s dead; " + "going to exponential backoff", + dom->dom_name); + } + dom->dom_checktime = t + 5; + (void)nag_servers(dom); + break; + + case DOM_DEAD: + dom->dom_checktime = t + dom->dom_backofftime; + backoff(&dom->dom_backofftime); + (void)nag_servers(dom); + break; + } + /* re-fetch the time in case we hung sending packets */ + (void)time(&t); + } } -static u_int32_t -unique_xid(struct _dom_binding *ypdb) +/* + * Process a hangup signal. + * + * Do an extra nag_servers() for any domains that are DEAD. This way + * if you know things are back up you can restore service by sending + * ypbind a SIGHUP rather than waiting for the timeout period. + */ +static void +dohup(void) { - u_int32_t tmp_xid; + struct domain *dom; - tmp_xid = ((u_int32_t)(u_long)ypdb) & 0xffffffff; - while (xid2ypdb(tmp_xid) != NULL) - tmp_xid++; + hupped = 0; + for (dom = domains; dom != NULL; dom = dom->dom_next) { + if (dom->dom_state == DOM_DEAD) { + (void)nag_servers(dom); + } + } +} - return tmp_xid; +/* + * Receive a hangup signal. + */ +static void +hup(int __unused sig) +{ + hupped = 1; +} + +/* + * Initialize hangup processing. + */ +static void +starthup(void) +{ + struct sigaction sa; + + sa.sa_handler = hup; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction(SIGHUP, &sa, NULL) == -1) { + err(1, "sigaction"); + } +} + +//////////////////////////////////////////////////////////// +// main + +/* + * Usage message. + */ +__dead static void +usage(void) +{ + const char *opt = ""; +#ifdef DEBUG + opt = " [-d]"; +#endif + + (void)fprintf(stderr, + "Usage: %s [-broadcast] [-insecure] [-ypset] [-ypsetme]%s\n", + getprogname(), opt); + exit(1); +} + +/* + * Main. + */ +int +main(int argc, char *argv[]) +{ + struct timeval tv; + fd_set fdsr; + int width, lockfd; + int started = 0; + char *domainname; + + setprogname(argv[0]); + + /* + * Process arguments. + */ + + default_ypbindmode = YPBIND_DIRECT; + while (--argc) { + ++argv; + if (!strcmp("-insecure", *argv)) { + insecure = 1; + } else if (!strcmp("-ypset", *argv)) { + allow_any_ypset = 1; + allow_local_ypset = 1; + } else if (!strcmp("-ypsetme", *argv)) { + allow_any_ypset = 0; + allow_local_ypset = 1; + } else if (!strcmp("-broadcast", *argv)) { + default_ypbindmode = YPBIND_BROADCAST; +#ifdef DEBUG + } else if (!strcmp("-d", *argv)) { + debug = 1; +#endif + } else { + usage(); + } + } + + /* + * Look up the name of the default domain. + */ + + (void)yp_get_default_domain(&domainname); + if (domainname[0] == '\0') + errx(1, "Domainname not set. Aborting."); + if (_yp_invalid_domain(domainname)) + errx(1, "Invalid domainname: %s", domainname); + + /* + * Start things up. + */ + + /* Open the system log. */ + openlog("ypbind", LOG_PERROR | LOG_PID, LOG_DAEMON); + + /* Acquire /var/run/ypbind.lock. */ + lockfd = open_locked(_PATH_YPBIND_LOCK, O_CREAT|O_RDWR|O_TRUNC, 0644); + if (lockfd == -1) + err(1, "Cannot create %s", _PATH_YPBIND_LOCK); + + /* Accept hangups. */ + starthup(); + + /* Initialize sunrpc stuff. */ + sunrpc_setup(); + + /* Clean out BINDINGDIR, deleting all existing (now stale) bindings */ + if (purge_bindingdir(BINDINGDIR) < 0) + errx(1, "Unable to purge old bindings from %s", BINDINGDIR); + + /* + * We start with one binding, for the default domain. It starts + * out "unsuccessful". + * + * XXX: domain_create adds the new domain to 'domains' (the + * global linked list) and therefore we shouldn't assign + * 'domains' again on return. + */ + + domains = domain_create(domainname); + + /* + * Delete the lock for the default domain again, just in case something + * magically caused it to appear since purge_bindingdir() was called. + * XXX: this is useless and redundant; remove it. + */ + removelock(domains); + + /* + * Main loop. Wake up at least once a second and check for + * timer-based work to do (checkwork) and also handle incoming + * responses from ypservers and any RPCs made to the ypbind + * service. + * + * There are two sockets used for ypserver traffic: one for + * pings and one for everything else. These call XDR manually + * for encoding and are *not* dispatched via the sunrpc + * libraries. + * + * The ypbind serivce *is* dispatched via the sunrpc libraries. + * svc_getreqset() does whatever internal muck and ultimately + * ypbind service calls arrive at ypbindprog_2(). + */ + checkwork(); + for (;;) { + width = svc_maxfd; + if (rpcsock > width) + width = rpcsock; + if (pingsock > width) + width = pingsock; + width++; + fdsr = svc_fdset; + FD_SET(rpcsock, &fdsr); + FD_SET(pingsock, &fdsr); + tv.tv_sec = 1; + tv.tv_usec = 0; + + switch (select(width, &fdsr, NULL, NULL, &tv)) { + case 0: + /* select timed out - check for timer-based work */ + if (hupped) { + dohup(); + } + checkwork(); + break; + case -1: + if (hupped) { + dohup(); + } + if (errno != EINTR) { + yp_log(LOG_WARNING, "select: %s", + strerror(errno)); + } + break; + default: + if (hupped) { + dohup(); + } + /* incoming of our own; read it */ + if (FD_ISSET(rpcsock, &fdsr)) + (void)handle_replies(); + if (FD_ISSET(pingsock, &fdsr)) + (void)handle_ping(); + + /* read any incoming packets for the ypbind service */ + svc_getreqset(&fdsr); + + /* + * Only check for timer-based work if + * something in the incoming RPC logic said + * to. This might be just a hack to avoid + * scanning the list unnecessarily, but I + * suspect it's also a hack to cover wrong + * state logic. - dholland 20140609 + */ + if (check) + checkwork(); + break; + } + + /* + * Defer daemonizing until the default domain binds + * successfully. XXX: there seems to be no timeout + * on this, which means that if the default domain + * is dead upstream boot will hang indefinitely. + */ + if (!started && domains->dom_state == DOM_ALIVE) { + started = 1; +#ifdef DEBUG + if (!debug) +#endif + (void)daemon(0, 0); + (void)pidfile(NULL); + } + } }