/* * Copyright (c) 1992/3 Theo de Raadt * 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. The name of the author may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef LINT static char rcsid[] = "$Id: ypbind.c,v 1.9 1995/05/03 18:34:22 wpaul Exp $"; #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef BINDINGDIR #define BINDINGDIR "/var/yp/binding" #endif struct _dom_binding { struct _dom_binding *dom_pnext; char dom_domain[YPMAXDOMAIN + 1]; struct sockaddr_in dom_server_addr; CLIENT *client_handle; long int dom_vers; int dom_lockfd; int dom_alive; int dom_broadcasting; int dom_default; }; extern bool_t xdr_domainname(), xdr_ypbind_resp(); extern bool_t xdr_ypreq_key(), xdr_ypresp_val(); extern bool_t xdr_ypbind_setdom(); void checkwork __P((void)); void *ypbindproc_null_2 __P((SVCXPRT *, void *, CLIENT *)); bool_t *ypbindproc_setdom_2 __P((SVCXPRT *, struct ypbind_setdom *, CLIENT *)); void rpc_received __P((char *, struct sockaddr_in *, int )); void broadcast __P((struct _dom_binding *)); int ping __P((struct _dom_binding *)); void handle_children __P(( int )); void reaper __P((int)); void terminate __P((int)); static char *broad_domain; char *domainname; struct _dom_binding *ypbindlist; #define YPSET_NO 0 #define YPSET_LOCAL 1 #define YPSET_ALL 2 int ypsetmode = YPSET_NO; int ypsecuremode = 0; /* No more than MAX_CHILDREN child broadcasters at a time. */ #define MAX_CHILDREN 5 /* No more than MAX_DOMAINS simultaneous domains */ #define MAX_DOMAINS 200 #ifndef FAIL_THRESHOLD #define FAIL_THRESHOLD 20 #endif #define KILLME_MAGIC 0xdeaddead int child_fds[FD_SETSIZE]; static int fd[2]; int children = 0; int domains = 0; SVCXPRT *udptransp, *tcptransp; void * ypbindproc_null_2(transp, argp, clnt) SVCXPRT *transp; void *argp; CLIENT *clnt; { static char res; bzero((char *)&res, sizeof(res)); return (void *)&res; } struct ypbind_resp * ypbindproc_domain_2(transp, argp, clnt) SVCXPRT *transp; char *argp; CLIENT *clnt; { static struct ypbind_resp res; struct _dom_binding *ypdb; char path[MAXPATHLEN]; bzero((char *)&res, sizeof res); res.ypbind_status = YPBIND_FAIL_VAL; res.ypbind_respbody.ypbind_error = YPBIND_ERR_NOSERV; for(ypdb=ypbindlist; ypdb; ypdb=ypdb->dom_pnext) if( strcmp(ypdb->dom_domain, argp) == 0) break; if(ypdb==NULL) { if (domains > MAX_DOMAINS) { syslog(LOG_WARNING, "domain limit (%d) exceeded", MAX_DOMAINS); res.ypbind_respbody.ypbind_error = YPBIND_ERR_RESC; return; } ypdb = (struct _dom_binding *)malloc(sizeof *ypdb); if (ypdb == NULL) { syslog(LOG_WARNING, "malloc: %s", strerror(errno)); res.ypbind_respbody.ypbind_error = YPBIND_ERR_RESC; return; } bzero((char *)ypdb, sizeof *ypdb); strncpy(ypdb->dom_domain, argp, sizeof ypdb->dom_domain); ypdb->dom_vers = YPVERS; ypdb->dom_alive = 0; ypdb->dom_default = 0; ypdb->dom_lockfd = -1; sprintf(path, "%s/%s.%ld", BINDINGDIR, ypdb->dom_domain, ypdb->dom_vers); unlink(path); ypdb->dom_pnext = ypbindlist; ypbindlist = ypdb; domains++; } if (ping(ypdb)) return &res; res.ypbind_status = YPBIND_SUCC_VAL; res.ypbind_respbody.ypbind_error = 0; /* Success */ res.ypbind_respbody.ypbind_bindinfo.ypbind_binding_addr.s_addr = ypdb->dom_server_addr.sin_addr.s_addr; res.ypbind_respbody.ypbind_bindinfo.ypbind_binding_port = ypdb->dom_server_addr.sin_port; /*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));*/ return &res; } bool_t * ypbindproc_setdom_2(transp, argp, clnt) SVCXPRT *transp; struct ypbind_setdom *argp; CLIENT *clnt; { struct sockaddr_in *fromsin, bindsin; static char res; bzero((char *)&res, sizeof(res)); fromsin = svc_getcaller(transp); switch(ypsetmode) { case YPSET_LOCAL: if( fromsin->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) return (void *)NULL; break; case YPSET_ALL: break; case YPSET_NO: default: return (void *)NULL; } if(ntohs(fromsin->sin_port) >= IPPORT_RESERVED) return (void *)&res; if(argp->ypsetdom_vers != YPVERS) return (void *)&res; bzero((char *)&bindsin, sizeof bindsin); bindsin.sin_family = AF_INET; bindsin.sin_addr.s_addr = argp->ypsetdom_addr.s_addr; bindsin.sin_port = argp->ypsetdom_port; rpc_received(argp->ypsetdom_domain, &bindsin, 1); res = 1; return (void *)&res; } static void ypbindprog_2(rqstp, transp) struct svc_req *rqstp; register SVCXPRT *transp; { union { char ypbindproc_domain_2_arg[MAXHOSTNAMELEN]; struct ypbind_setdom ypbindproc_setdom_2_arg; } argument; struct authunix_parms *creds; char *result; bool_t (*xdr_argument)(), (*xdr_result)(); char *(*local)(); switch (rqstp->rq_proc) { case YPBINDPROC_NULL: xdr_argument = xdr_void; xdr_result = xdr_void; local = (char *(*)()) ypbindproc_null_2; break; case YPBINDPROC_DOMAIN: xdr_argument = xdr_domainname; xdr_result = xdr_ypbind_resp; local = (char *(*)()) ypbindproc_domain_2; break; case YPBINDPROC_SETDOM: switch(rqstp->rq_cred.oa_flavor) { case AUTH_UNIX: creds = (struct authunix_parms *)rqstp->rq_clntcred; if( creds->aup_uid != 0) { svcerr_auth(transp, AUTH_BADCRED); return; } break; default: svcerr_auth(transp, AUTH_TOOWEAK); return; } xdr_argument = xdr_ypbind_setdom; xdr_result = xdr_void; local = (char *(*)()) ypbindproc_setdom_2; break; default: svcerr_noproc(transp); return; } bzero((char *)&argument, sizeof(argument)); if (!svc_getargs(transp, xdr_argument, &argument)) { svcerr_decode(transp); return; } result = (*local)(transp, &argument, rqstp); if (result != NULL && !svc_sendreply(transp, xdr_result, result)) { svcerr_systemerr(transp); } return; } /* Jack the reaper */ void reaper(sig) int sig; { int st; wait3(&st, WNOHANG, NULL); } void terminate(sig) int sig; { struct _dom_binding *ypdb; char path[MAXPATHLEN]; for(ypdb=ypbindlist; ypdb; ypdb=ypdb->dom_pnext) { close(ypdb->dom_lockfd); sprintf(path, "%s/%s.%ld", BINDINGDIR, ypdb->dom_domain, ypdb->dom_vers); unlink(path); } pmap_unset(YPBINDPROG, YPBINDVERS); exit(0); } void main(argc, argv) int argc; char **argv; { char path[MAXPATHLEN]; struct timeval tv; fd_set fdsr; int i; DIR *dird; struct dirent *dirp; yp_get_default_domain(&domainname); if( domainname[0] == '\0') { fprintf(stderr, "domainname not set. Aborting.\n"); exit(1); } for(i=1; id_name, ".") && strcmp(dirp->d_name, "..")) { sprintf(path,"%s/%s",BINDINGDIR,dirp->d_name); unlink(path); } closedir(dird); } #ifdef DAEMON if (daemon(0,0)) { perror("fork"); exit(1); } #endif pmap_unset(YPBINDPROG, YPBINDVERS); udptransp = svcudp_create(RPC_ANYSOCK); if (udptransp == NULL) { fprintf(stderr, "cannot create udp service.\n"); exit(1); } if (!svc_register(udptransp, YPBINDPROG, YPBINDVERS, ypbindprog_2, IPPROTO_UDP)) { fprintf(stderr, "unable to register (YPBINDPROG, YPBINDVERS, udp).\n"); exit(1); } tcptransp = svctcp_create(RPC_ANYSOCK, 0, 0); if (tcptransp == NULL) { fprintf(stderr, "cannot create tcp service.\n"); exit(1); } if (!svc_register(tcptransp, YPBINDPROG, YPBINDVERS, ypbindprog_2, IPPROTO_TCP)) { fprintf(stderr, "unable to register (YPBINDPROG, YPBINDVERS, tcp).\n"); exit(1); } /* build initial domain binding, make it "unsuccessful" */ ypbindlist = (struct _dom_binding *)malloc(sizeof *ypbindlist); if (ypbindlist == NULL) { perror("malloc"); exit(1); } bzero((char *)ypbindlist, sizeof *ypbindlist); strncpy(ypbindlist->dom_domain, domainname, sizeof ypbindlist->dom_domain); ypbindlist->dom_vers = YPVERS; ypbindlist->dom_alive = 0; ypbindlist->dom_lockfd = -1; ypbindlist->client_handle = NULL; ypbindlist->dom_default = 1; domains++; signal(SIGCHLD, reaper); signal(SIGTERM, terminate); /* Initialize children fds. */ for (i = 0; i < FD_SETSIZE; i++) child_fds[i] = -1; openlog(argv[0], LOG_PID, LOG_DAEMON); /* Kick off the default domain */ broadcast(ypbindlist); while(1) { fdsr = svc_fdset; for (i = 0; i < FD_SETSIZE; i++) if (child_fds[i] > 0 ) FD_SET(child_fds[i], &fdsr); tv.tv_sec = 60; tv.tv_usec = 0; switch(select(_rpc_dtablesize(), &fdsr, NULL, NULL, &tv)) { case 0: checkwork(); break; case -1: if (errno == EINTR) continue; else { syslog(LOG_WARNING, "select: %s", strerror(errno)); break; } default: for(i = 0; i < FD_SETSIZE; i++) { if (child_fds[i] > 0 && FD_ISSET(child_fds[i],&fdsr)) { handle_children(child_fds[i]); close(child_fds[i]); FD_CLR(child_fds[i], &fdsr); child_fds[i] = -1; children--; } } svc_getreqset(&fdsr); break; } } } void checkwork() { struct _dom_binding *ypdb; for(ypdb=ypbindlist; ypdb; ypdb=ypdb->dom_pnext) ping(ypdb); } /* The clnt_broadcast() callback mechanism sucks. */ /* * Receive results from broadcaster. Don't worry about passing * bogus info to rpc_received() -- it can handle it. */ void handle_children(i) int i; { char buf[YPMAXDOMAIN + 1]; struct sockaddr_in addr; if (read(i, &buf, sizeof(buf)) < 0) syslog(LOG_WARNING, "could not read from child: %s", strerror(errno)); if (read(i, &addr, sizeof(struct sockaddr_in)) < 0) syslog(LOG_WARNING, "could not read from child: %s", strerror(errno)); rpc_received((char *)&buf, &addr, 0); } /* * Send our dying words back to our parent before we perish. */ int tell_parent(dom, addr) char *dom; struct sockaddr_in *addr; { char buf[YPMAXDOMAIN + 1]; struct timeval timeout; fd_set fds; timeout.tv_sec = 5; timeout.tv_usec = 0; sprintf (buf, "%s", broad_domain); if (write(fd[1], &buf, sizeof(buf)) < 0) return(1); /* * Stay in sync with parent: wait for it to read our first * message before sending the second. */ FD_ZERO(&fds); FD_SET(fd[1], &fds); if (select(FD_SETSIZE, NULL, &fds, NULL, &timeout) == -1) return(1); if (FD_ISSET(fd[1], &fds)) { if (write(fd[1], addr, sizeof(struct sockaddr_in)) < 0) return(1); } else { return(1); } close(fd[1]); return (0); } bool_t broadcast_result(out, addr) bool_t *out; struct sockaddr_in *addr; { if (tell_parent(&broad_domain, addr)) syslog(LOG_WARNING, "lost connection to parent"); return TRUE; } /* * The right way to send RPC broadcasts. * Use the clnt_broadcast() RPC service. Unfortunately, clnt_broadcast() * blocks while waiting for replies, so we have to fork off seperate * broadcaster processes that do the waiting and then transmit their * results back to the parent for processing. We also have to remember * to save the name of the domain we're trying to bind in a global * variable since clnt_broadcast() provides no way to pass things to * the 'eachresult' callback function. */ void broadcast(ypdb) struct _dom_binding *ypdb; { bool_t out = FALSE; enum clnt_stat stat; int i; if (children > MAX_CHILDREN || ypdb->dom_broadcasting) return; if (pipe(fd) < 0) { syslog(LOG_WARNING, "pipe: %s",strerror(errno)); return; } if (ypdb->dom_vers = -1 && (long)ypdb->dom_server_addr.sin_addr.s_addr) syslog(LOG_WARNING, "NIS server [%s] for domain %s not responding", inet_ntoa(ypdb->dom_server_addr.sin_addr), ypdb->dom_domain); broad_domain = ypdb->dom_domain; ypdb->dom_broadcasting = 1; flock(ypdb->dom_lockfd, LOCK_UN); switch(fork()) { case 0: close(fd[0]); break; case -1: syslog(LOG_WARNING, "fork: %s", strerror(errno)); close(fd[1]); close(fd[0]); return; default: for (i = 0; i < FD_SETSIZE; i++) { if (child_fds[i] < 0) { child_fds[i] = fd[0]; break; } } close(fd[1]); children++; return; } close(ypdb->dom_lockfd); stat = clnt_broadcast(YPPROG, YPVERS, YPPROC_DOMAIN_NONACK, xdr_domainname, (char *)ypdb->dom_domain, xdr_bool, (char *)&out, broadcast_result); if (stat != RPC_SUCCESS) { bzero((char *)&ypdb->dom_server_addr, sizeof(struct sockaddr_in)); if (!ypdb->dom_default) ypdb->dom_server_addr.sin_addr.s_addr = KILLME_MAGIC; if (tell_parent(&ypdb->dom_domain, &ypdb->dom_server_addr)) syslog(LOG_WARNING, "lost connection to parent"); } exit(0); } /* * The right way to check if a server is alive. * Attempt to get a client handle pointing to the server and send a * YPPROC_DOMAIN_NONACK. If we don't get a response, we invalidate * this binding entry and send out a broadcast to try to establish * a new binding. Note that we treat non-default domains * specially: once bound, we keep tabs on our server, but if it * goes away and fails to respond after one round of broadcasting, we * abandon it until a client specifically references it again. We make * every effort to keep our default domain bound, however, since we * need it to keep the system on its feet. */ int ping(ypdb) struct _dom_binding *ypdb; { bool_t out; struct timeval interval, timeout; enum clnt_stat stat; int rpcsock = RPC_ANYSOCK; time_t t; interval.tv_sec = 5; interval.tv_usec = 0; timeout.tv_sec = FAIL_THRESHOLD; timeout.tv_usec = 0; if (ypdb->dom_broadcasting) return(1); if (ypdb->client_handle == NULL) { if ((ypdb->client_handle = clntudp_bufcreate( &ypdb->dom_server_addr, YPPROG, YPVERS, interval, &rpcsock, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE)) == (CLIENT *)NULL) { /* Can't get a handle: we're dead. */ ypdb->client_handle = NULL; ypdb->dom_alive = 0; ypdb->dom_vers = -1; broadcast(ypdb); return(1); } } if ((stat = clnt_call(ypdb->client_handle, YPPROC_DOMAIN_NONACK, xdr_domainname, (char *)ypdb->dom_domain, xdr_bool, (char *)&out, timeout)) != RPC_SUCCESS) { ypdb->client_handle = NULL; ypdb->dom_alive = 0; ypdb->dom_vers = -1; broadcast(ypdb); return(1); } return(0); } void rpc_received(dom, raddrp, force) char *dom; struct sockaddr_in *raddrp; int force; { struct _dom_binding *ypdb, *prev = NULL; struct iovec iov[2]; struct ypbind_resp ybr; char path[MAXPATHLEN]; int fd; /*printf("returned from %s/%d about %s\n", inet_ntoa(raddrp->sin_addr), ntohs(raddrp->sin_port), dom);*/ if(dom==NULL) return; for(ypdb=ypbindlist; ypdb; ypdb=ypdb->dom_pnext) { if( strcmp(ypdb->dom_domain, dom) == 0) break; prev = ypdb; } /* if in securemode, check originating port number */ if (ypsecuremode && (ntohs(raddrp->sin_port) >= IPPORT_RESERVED)) { syslog(LOG_WARNING, "Rejected NIS server on [%s/%d] for domain %s.", inet_ntoa(raddrp->sin_addr), ntohs(raddrp->sin_port), dom); if (ypdb != NULL) { ypdb->dom_broadcasting = 0; ypdb->dom_alive = 0; } return; } if (raddrp->sin_addr.s_addr == (long)0) { ypdb->dom_broadcasting = 0; ypdb->dom_alive = 0; broadcast(ypdb); return; } if (raddrp->sin_addr.s_addr == KILLME_MAGIC) { if (prev == NULL) ypbindlist = ypdb->dom_pnext; else prev->dom_pnext = ypdb->dom_pnext; sprintf(path, "%s/%s.%ld", BINDINGDIR, ypdb->dom_domain, YPVERS); /* XXX dom_vers can't */ close(ypdb->dom_lockfd); /* be trusted here */ unlink(path); free(ypdb); domains--; return; } if(ypdb==NULL) { if (force == 0) return; ypdb = (struct _dom_binding *)malloc(sizeof *ypdb); if (ypdb == NULL) { syslog(LOG_WARNING, "malloc: %s", strerror(errno)); return; } bzero((char *)ypdb, sizeof *ypdb); strncpy(ypdb->dom_domain, dom, sizeof ypdb->dom_domain); ypdb->dom_lockfd = -1; ypdb->dom_default = 0; ypdb->dom_alive = 0; ypdb->dom_broadcasting = 0; ypdb->dom_pnext = ypbindlist; ypbindlist = ypdb; } /* We've recovered from a crash: inform the world. */ if (ypdb->dom_vers = -1 && ypdb->dom_server_addr.sin_addr.s_addr) syslog(LOG_WARNING, "NIS server [%s] for domain %s OK", inet_ntoa(ypdb->dom_server_addr.sin_addr), ypdb->dom_domain); bcopy((char *)raddrp, (char *)&ypdb->dom_server_addr, sizeof ypdb->dom_server_addr); ypdb->dom_vers = YPVERS; ypdb->dom_alive = 1; ypdb->dom_broadcasting = 0; if(ypdb->dom_lockfd != -1) close(ypdb->dom_lockfd); sprintf(path, "%s/%s.%ld", BINDINGDIR, ypdb->dom_domain, ypdb->dom_vers); #ifdef O_SHLOCK if( (fd=open(path, O_CREAT|O_SHLOCK|O_RDWR|O_TRUNC, 0644)) == -1) { (void)mkdir(BINDINGDIR, 0755); if( (fd=open(path, O_CREAT|O_SHLOCK|O_RDWR|O_TRUNC, 0644)) == -1) return; } #else if( (fd=open(path, O_CREAT|O_RDWR|O_TRUNC, 0644)) == -1) { (void)mkdir(BINDINGDIR, 0755); if( (fd=open(path, O_CREAT|O_RDWR|O_TRUNC, 0644)) == -1) return; } flock(fd, LOCK_SH); #endif /* * ok, if BINDINGDIR exists, and we can create the binding file, * then write to it.. */ ypdb->dom_lockfd = fd; iov[0].iov_base = (caddr_t)&(udptransp->xp_port); iov[0].iov_len = sizeof udptransp->xp_port; iov[1].iov_base = (caddr_t)&ybr; iov[1].iov_len = sizeof ybr; bzero(&ybr, 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; if( writev(ypdb->dom_lockfd, iov, 2) != iov[0].iov_len + iov[1].iov_len) { syslog(LOG_WARNING, "write: %s", strerror(errno)); close(ypdb->dom_lockfd); ypdb->dom_lockfd = -1; return; } }