ypbind.c revision 24782
11927Swollman/* 21927Swollman * Copyright (c) 1992/3 Theo de Raadt <deraadt@fsa.ca> 31927Swollman * All rights reserved. 41927Swollman * 51927Swollman * Redistribution and use in source and binary forms, with or without 61927Swollman * modification, are permitted provided that the following conditions 71927Swollman * are met: 81927Swollman * 1. Redistributions of source code must retain the above copyright 91927Swollman * notice, this list of conditions and the following disclaimer. 101927Swollman * 2. Redistributions in binary form must reproduce the above copyright 111927Swollman * notice, this list of conditions and the following disclaimer in the 121927Swollman * documentation and/or other materials provided with the distribution. 131927Swollman * 3. The name of the author may not be used to endorse or promote 141927Swollman * products derived from this software without specific prior written 151927Swollman * permission. 161927Swollman * 171927Swollman * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 181927Swollman * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 191927Swollman * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 201927Swollman * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 211927Swollman * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 221927Swollman * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 231927Swollman * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 241927Swollman * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 251927Swollman * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 261927Swollman * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 271927Swollman * SUCH DAMAGE. 281927Swollman */ 291927Swollman 301927Swollman#ifndef LINT 3124782Swpaulstatic char rcsid[] = "$Id: ypbind.c,v 1.23 1997/02/22 16:14:56 peter Exp $"; 321927Swollman#endif 331927Swollman 341927Swollman#include <sys/param.h> 351927Swollman#include <sys/types.h> 368091Swpaul#include <sys/wait.h> 371927Swollman#include <sys/ioctl.h> 381927Swollman#include <sys/signal.h> 391927Swollman#include <sys/socket.h> 401927Swollman#include <sys/file.h> 411927Swollman#include <sys/fcntl.h> 428091Swpaul#include <sys/stat.h> 431927Swollman#include <sys/uio.h> 446478Swpaul#include <syslog.h> 451927Swollman#include <stdio.h> 461927Swollman#include <errno.h> 471927Swollman#include <ctype.h> 481927Swollman#include <dirent.h> 491927Swollman#include <netdb.h> 501927Swollman#include <string.h> 511927Swollman#include <rpc/rpc.h> 521927Swollman#include <rpc/xdr.h> 531927Swollman#include <net/if.h> 546732Swpaul#include <netinet/in.h> 551927Swollman#include <arpa/inet.h> 561927Swollman#include <rpc/pmap_clnt.h> 571927Swollman#include <rpc/pmap_prot.h> 581927Swollman#include <rpc/pmap_rmt.h> 591927Swollman#include <unistd.h> 608091Swpaul#include <stdlib.h> 6112862Swpaul#include <rpcsvc/yp.h> 6212862Swpaulstruct dom_binding{}; 631927Swollman#include <rpcsvc/ypclnt.h> 641927Swollman 651927Swollman#ifndef BINDINGDIR 661927Swollman#define BINDINGDIR "/var/yp/binding" 671927Swollman#endif 681927Swollman 698474Swpaul#ifndef YPBINDLOCK 708474Swpaul#define YPBINDLOCK "/var/run/ypbind.lock" 718474Swpaul#endif 728474Swpaul 731927Swollmanstruct _dom_binding { 741927Swollman struct _dom_binding *dom_pnext; 751927Swollman char dom_domain[YPMAXDOMAIN + 1]; 761927Swollman struct sockaddr_in dom_server_addr; 771927Swollman long int dom_vers; 781927Swollman int dom_lockfd; 791927Swollman int dom_alive; 808755Swpaul int dom_broadcast_pid; 818755Swpaul int dom_pipe_fds[2]; 828091Swpaul int dom_default; 831927Swollman}; 841927Swollman 858755Swpaul#define READFD ypdb->dom_pipe_fds[0] 868755Swpaul#define WRITEFD ypdb->dom_pipe_fds[1] 878755Swpaul#define BROADFD broad_domain->dom_pipe_fds[1] 888755Swpaul 891927Swollmanextern bool_t xdr_domainname(), xdr_ypbind_resp(); 901927Swollmanextern bool_t xdr_ypreq_key(), xdr_ypresp_val(); 911927Swollmanextern bool_t xdr_ypbind_setdom(); 921927Swollman 938091Swpaulvoid checkwork __P((void)); 948091Swpaulvoid *ypbindproc_null_2 __P((SVCXPRT *, void *, CLIENT *)); 958755Swpaulvoid *ypbindproc_setdom_2 __P((SVCXPRT *, struct ypbind_setdom *, CLIENT *)); 968091Swpaulvoid rpc_received __P((char *, struct sockaddr_in *, int )); 978091Swpaulvoid broadcast __P((struct _dom_binding *)); 988425Swpaulint ping __P((struct _dom_binding *)); 998755Swpaulint tell_parent __P((char *, struct sockaddr_in *)); 1008853Swpaulvoid handle_children __P(( struct _dom_binding * )); 1018425Swpaulvoid reaper __P((int)); 1028425Swpaulvoid terminate __P((int)); 1039600Swpaulvoid yp_restricted_mode __P((char *)); 1049600Swpaulint verify __P((struct in_addr)); 1058091Swpaul 10612862Swpaulchar *domain_name; 1071927Swollmanstruct _dom_binding *ypbindlist; 1088755Swpaulstatic struct _dom_binding *broad_domain; 1091927Swollman 1101927Swollman#define YPSET_NO 0 1111927Swollman#define YPSET_LOCAL 1 1121927Swollman#define YPSET_ALL 2 1131927Swollmanint ypsetmode = YPSET_NO; 1146732Swpaulint ypsecuremode = 0; 11521581Swpaulint ppid; 1166732Swpaul 1179600Swpaul/* 1189600Swpaul * Special restricted mode variables: when in restricted mode, only the 1199600Swpaul * specified restricted_domain will be bound, and only the servers listed 1209600Swpaul * in restricted_addrs will be used for binding. 1219600Swpaul */ 1229600Swpaul#define RESTRICTED_SERVERS 10 1239600Swpaulint yp_restricted = 0; 1249600Swpaulstruct in_addr restricted_addrs[RESTRICTED_SERVERS]; 1259600Swpaul 1268091Swpaul/* No more than MAX_CHILDREN child broadcasters at a time. */ 1278755Swpaul#ifndef MAX_CHILDREN 1288091Swpaul#define MAX_CHILDREN 5 1298755Swpaul#endif 1308425Swpaul/* No more than MAX_DOMAINS simultaneous domains */ 1318755Swpaul#ifndef MAX_DOMAINS 1328425Swpaul#define MAX_DOMAINS 200 1338755Swpaul#endif 1348755Swpaul/* RPC timeout value */ 1358425Swpaul#ifndef FAIL_THRESHOLD 13621539Swpaul#define FAIL_THRESHOLD 20 1378425Swpaul#endif 1388425Swpaul 1399600Swpaul/* Number of times to fish for a response froma particular set of hosts */ 1409600Swpaul#ifndef MAX_RETRIES 1419600Swpaul#define MAX_RETRIES 30 1429600Swpaul#endif 1439600Swpaul 1449600Swpaulint retries = 0; 1458091Swpaulint children = 0; 1468425Swpaulint domains = 0; 1478474Swpaulint yplockfd; 1488755Swpaulfd_set fdsr; 1498091Swpaul 1501927SwollmanSVCXPRT *udptransp, *tcptransp; 1511927Swollman 1521927Swollmanvoid * 1531927Swollmanypbindproc_null_2(transp, argp, clnt) 1541927SwollmanSVCXPRT *transp; 1551927Swollmanvoid *argp; 1561927SwollmanCLIENT *clnt; 1571927Swollman{ 1581927Swollman static char res; 1591927Swollman 1601927Swollman bzero((char *)&res, sizeof(res)); 1611927Swollman return (void *)&res; 1621927Swollman} 1631927Swollman 1641927Swollmanstruct ypbind_resp * 1651927Swollmanypbindproc_domain_2(transp, argp, clnt) 1661927SwollmanSVCXPRT *transp; 16712862Swpauldomainname *argp; 1681927SwollmanCLIENT *clnt; 1691927Swollman{ 1701927Swollman static struct ypbind_resp res; 1711927Swollman struct _dom_binding *ypdb; 1721927Swollman char path[MAXPATHLEN]; 1731927Swollman 1741927Swollman bzero((char *)&res, sizeof res); 1751927Swollman res.ypbind_status = YPBIND_FAIL_VAL; 17612862Swpaul res.ypbind_resp_u.ypbind_error = YPBIND_ERR_NOSERV; 1771927Swollman 17824782Swpaul if (strchr(*argp, '/')) { 17924782Swpaul syslog(LOG_WARNING, "Domain name '%s' has embedded slash -- \ 18024782Swpaulrejecting.", *argp); 18124782Swpaul return(&res); 18224782Swpaul } 18324782Swpaul 1848755Swpaul for(ypdb=ypbindlist; ypdb; ypdb=ypdb->dom_pnext) { 18512862Swpaul if( strcmp(ypdb->dom_domain, *argp) == 0) 1861927Swollman break; 1878755Swpaul } 1881927Swollman 1891927Swollman if(ypdb==NULL) { 1909600Swpaul if (yp_restricted) { 19112862Swpaul syslog(LOG_NOTICE, "Running in restricted mode -- request to bind domain \"%s\" rejected.\n", *argp); 1929600Swpaul return &res; 1939600Swpaul } 1949600Swpaul 1958474Swpaul if (domains >= MAX_DOMAINS) { 1968425Swpaul syslog(LOG_WARNING, "domain limit (%d) exceeded", 1978425Swpaul MAX_DOMAINS); 19812862Swpaul res.ypbind_resp_u.ypbind_error = YPBIND_ERR_RESC; 1998428Swpaul return &res; 2008425Swpaul } 2011927Swollman ypdb = (struct _dom_binding *)malloc(sizeof *ypdb); 2028857Srgrimes if (ypdb == NULL) { 20321539Swpaul syslog(LOG_WARNING, "malloc: %m"); 20412862Swpaul res.ypbind_resp_u.ypbind_error = YPBIND_ERR_RESC; 2058428Swpaul return &res; 2068246Swpaul } 2071927Swollman bzero((char *)ypdb, sizeof *ypdb); 20812862Swpaul strncpy(ypdb->dom_domain, *argp, sizeof ypdb->dom_domain); 2091927Swollman ypdb->dom_vers = YPVERS; 2101927Swollman ypdb->dom_alive = 0; 2118091Swpaul ypdb->dom_default = 0; 2121927Swollman ypdb->dom_lockfd = -1; 2138425Swpaul sprintf(path, "%s/%s.%ld", BINDINGDIR, 2148425Swpaul ypdb->dom_domain, ypdb->dom_vers); 2151927Swollman unlink(path); 2161927Swollman ypdb->dom_pnext = ypbindlist; 2171927Swollman ypbindlist = ypdb; 2188425Swpaul domains++; 2191927Swollman } 2201927Swollman 2218755Swpaul if (ping(ypdb)) { 2227982Swpaul return &res; 2238755Swpaul } 2241927Swollman 2251927Swollman res.ypbind_status = YPBIND_SUCC_VAL; 22612862Swpaul res.ypbind_resp_u.ypbind_error = 0; /* Success */ 22712862Swpaul *(u_long *)&res.ypbind_resp_u.ypbind_bindinfo.ypbind_binding_addr = 2281927Swollman ypdb->dom_server_addr.sin_addr.s_addr; 22912862Swpaul *(u_short *)&res.ypbind_resp_u.ypbind_bindinfo.ypbind_binding_port = 2308091Swpaul ypdb->dom_server_addr.sin_port; 2311927Swollman /*printf("domain %s at %s/%d\n", ypdb->dom_domain, 2321927Swollman inet_ntoa(ypdb->dom_server_addr.sin_addr), 2331927Swollman ntohs(ypdb->dom_server_addr.sin_port));*/ 2341927Swollman return &res; 2351927Swollman} 2361927Swollman 2378755Swpaulvoid * 2381927Swollmanypbindproc_setdom_2(transp, argp, clnt) 2391927SwollmanSVCXPRT *transp; 24012862Swpaulypbind_setdom *argp; 2411927SwollmanCLIENT *clnt; 2421927Swollman{ 2431927Swollman struct sockaddr_in *fromsin, bindsin; 2441927Swollman 24524782Swpaul if (strchr(argp->ypsetdom_domain, '/')) { 24624782Swpaul syslog(LOG_WARNING, "Domain name '%s' has embedded slash -- \ 24724782Swpaulrejecting.", argp->ypsetdom_domain); 24824782Swpaul return; 24924782Swpaul } 2501927Swollman fromsin = svc_getcaller(transp); 2511927Swollman 2521927Swollman switch(ypsetmode) { 2531927Swollman case YPSET_LOCAL: 2548755Swpaul if( fromsin->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) { 2558755Swpaul svcerr_noprog(transp); 2568755Swpaul return; 2578755Swpaul } 2581927Swollman break; 2591927Swollman case YPSET_ALL: 2601927Swollman break; 2611927Swollman case YPSET_NO: 2621927Swollman default: 2638755Swpaul svcerr_noprog(transp); 2648755Swpaul return; 2651927Swollman } 2661927Swollman 2678755Swpaul if(ntohs(fromsin->sin_port) >= IPPORT_RESERVED) { 2688755Swpaul svcerr_noprog(transp); 2698755Swpaul return; 2708755Swpaul } 2711927Swollman 2728755Swpaul if(argp->ypsetdom_vers != YPVERS) { 2738755Swpaul svcerr_noprog(transp); 2748755Swpaul return; 2758755Swpaul } 2761927Swollman 2771927Swollman bzero((char *)&bindsin, sizeof bindsin); 2781927Swollman bindsin.sin_family = AF_INET; 27912862Swpaul bindsin.sin_addr.s_addr = *(u_long *)argp->ypsetdom_binding.ypbind_binding_addr; 28012862Swpaul bindsin.sin_port = *(u_short *)argp->ypsetdom_binding.ypbind_binding_port; 2811927Swollman rpc_received(argp->ypsetdom_domain, &bindsin, 1); 2821927Swollman 2838755Swpaul return; 2841927Swollman} 2851927Swollman 2861927Swollmanstatic void 2871927Swollmanypbindprog_2(rqstp, transp) 2881927Swollmanstruct svc_req *rqstp; 2891927Swollmanregister SVCXPRT *transp; 2901927Swollman{ 2911927Swollman union { 29212862Swpaul domainname ypbindproc_domain_2_arg; 2931927Swollman struct ypbind_setdom ypbindproc_setdom_2_arg; 2941927Swollman } argument; 2951927Swollman struct authunix_parms *creds; 2961927Swollman char *result; 2971927Swollman bool_t (*xdr_argument)(), (*xdr_result)(); 2981927Swollman char *(*local)(); 2991927Swollman 3001927Swollman switch (rqstp->rq_proc) { 3011927Swollman case YPBINDPROC_NULL: 3021927Swollman xdr_argument = xdr_void; 3031927Swollman xdr_result = xdr_void; 3041927Swollman local = (char *(*)()) ypbindproc_null_2; 3051927Swollman break; 3061927Swollman 3071927Swollman case YPBINDPROC_DOMAIN: 3081927Swollman xdr_argument = xdr_domainname; 3091927Swollman xdr_result = xdr_ypbind_resp; 3101927Swollman local = (char *(*)()) ypbindproc_domain_2; 3111927Swollman break; 3121927Swollman 3131927Swollman case YPBINDPROC_SETDOM: 3141927Swollman switch(rqstp->rq_cred.oa_flavor) { 3151927Swollman case AUTH_UNIX: 3161927Swollman creds = (struct authunix_parms *)rqstp->rq_clntcred; 3171927Swollman if( creds->aup_uid != 0) { 3181927Swollman svcerr_auth(transp, AUTH_BADCRED); 3191927Swollman return; 3201927Swollman } 3211927Swollman break; 3221927Swollman default: 3231927Swollman svcerr_auth(transp, AUTH_TOOWEAK); 3241927Swollman return; 3251927Swollman } 3261927Swollman 3271927Swollman xdr_argument = xdr_ypbind_setdom; 3281927Swollman xdr_result = xdr_void; 3291927Swollman local = (char *(*)()) ypbindproc_setdom_2; 3301927Swollman break; 3311927Swollman 3321927Swollman default: 3331927Swollman svcerr_noproc(transp); 3341927Swollman return; 3351927Swollman } 3361927Swollman bzero((char *)&argument, sizeof(argument)); 33721096Speter if (!svc_getargs(transp, xdr_argument, (caddr_t)&argument)) { 3381927Swollman svcerr_decode(transp); 3391927Swollman return; 3401927Swollman } 3411927Swollman result = (*local)(transp, &argument, rqstp); 3421927Swollman if (result != NULL && !svc_sendreply(transp, xdr_result, result)) { 3431927Swollman svcerr_systemerr(transp); 3441927Swollman } 3451927Swollman return; 3461927Swollman} 3471927Swollman 3488091Swpaul/* Jack the reaper */ 3498091Swpaulvoid reaper(sig) 3508091Swpaulint sig; 3518091Swpaul{ 3528091Swpaul int st; 3538091Swpaul 3549532Swpaul while(wait3(&st, WNOHANG, NULL) > 0) 3559532Swpaul children--; 3568091Swpaul} 3578091Swpaul 3588425Swpaulvoid terminate(sig) 3598425Swpaulint sig; 3608425Swpaul{ 3618425Swpaul struct _dom_binding *ypdb; 3628425Swpaul char path[MAXPATHLEN]; 3638425Swpaul 36421581Swpaul if (ppid != getpid()) 36521581Swpaul exit(0); 36621581Swpaul 3678425Swpaul for(ypdb=ypbindlist; ypdb; ypdb=ypdb->dom_pnext) { 3688425Swpaul close(ypdb->dom_lockfd); 3698755Swpaul if (ypdb->dom_broadcast_pid) 3708755Swpaul kill(ypdb->dom_broadcast_pid, SIGINT); 3718425Swpaul sprintf(path, "%s/%s.%ld", BINDINGDIR, 3728425Swpaul ypdb->dom_domain, ypdb->dom_vers); 3738425Swpaul unlink(path); 3748425Swpaul } 3758474Swpaul close(yplockfd); 3768474Swpaul unlink(YPBINDLOCK); 3778425Swpaul pmap_unset(YPBINDPROG, YPBINDVERS); 3788425Swpaul exit(0); 3798425Swpaul} 3808857Srgrimes 38121581Swpaulint 3821927Swollmanmain(argc, argv) 3838091Swpaulint argc; 3841927Swollmanchar **argv; 3851927Swollman{ 3861927Swollman char path[MAXPATHLEN]; 3871927Swollman struct timeval tv; 3881927Swollman int i; 3898425Swpaul DIR *dird; 3908425Swpaul struct dirent *dirp; 3918755Swpaul struct _dom_binding *ypdb; 3921927Swollman 3938474Swpaul /* Check that another ypbind isn't already running. */ 3948474Swpaul if ((yplockfd = (open(YPBINDLOCK, O_RDONLY|O_CREAT, 0444))) == -1) { 3958474Swpaul perror(YPBINDLOCK); 3968474Swpaul exit(1); 3978474Swpaul } 3988474Swpaul 3998474Swpaul if(flock(yplockfd, LOCK_EX|LOCK_NB) == -1 && errno == EWOULDBLOCK) { 4008474Swpaul fprintf (stderr, "Another ypbind is already running. Aborting.\n"); 4018474Swpaul exit(1); 4028474Swpaul } 4038474Swpaul 4049600Swpaul /* XXX domainname will be overriden if we use restricted mode */ 40512862Swpaul yp_get_default_domain(&domain_name); 40612862Swpaul if( domain_name[0] == '\0') { 4071927Swollman fprintf(stderr, "domainname not set. Aborting.\n"); 4081927Swollman exit(1); 4091927Swollman } 4101927Swollman 4111927Swollman for(i=1; i<argc; i++) { 4121927Swollman if( strcmp("-ypset", argv[i]) == 0) 4131927Swollman ypsetmode = YPSET_ALL; 4141927Swollman else if (strcmp("-ypsetme", argv[i]) == 0) 4156732Swpaul ypsetmode = YPSET_LOCAL; 4166732Swpaul else if (strcmp("-s", argv[i]) == 0) 4176732Swpaul ypsecuremode++; 4189600Swpaul else if (strcmp("-S", argv[i]) == 0 && argc > i) 4199600Swpaul yp_restricted_mode(argv[i+1]); 4201927Swollman } 4211927Swollman 4228425Swpaul /* blow away everything in BINDINGDIR (if it exists) */ 4231927Swollman 4248425Swpaul if ((dird = opendir(BINDINGDIR)) != NULL) { 4258425Swpaul char path[MAXPATHLEN]; 4268425Swpaul while ((dirp = readdir(dird)) != NULL) 4278425Swpaul if (strcmp(dirp->d_name, ".") && 4288425Swpaul strcmp(dirp->d_name, "..")) { 4298425Swpaul sprintf(path,"%s/%s",BINDINGDIR,dirp->d_name); 4308425Swpaul unlink(path); 4318425Swpaul } 4328425Swpaul closedir(dird); 4338425Swpaul } 4341927Swollman 4351927Swollman#ifdef DAEMON 4368425Swpaul if (daemon(0,0)) { 4371927Swollman perror("fork"); 4381927Swollman exit(1); 4391927Swollman } 4401927Swollman#endif 4411927Swollman 4421927Swollman pmap_unset(YPBINDPROG, YPBINDVERS); 4431927Swollman 4441927Swollman udptransp = svcudp_create(RPC_ANYSOCK); 4451927Swollman if (udptransp == NULL) { 4468425Swpaul fprintf(stderr, "cannot create udp service.\n"); 4471927Swollman exit(1); 4481927Swollman } 4491927Swollman if (!svc_register(udptransp, YPBINDPROG, YPBINDVERS, ypbindprog_2, 4501927Swollman IPPROTO_UDP)) { 4518425Swpaul fprintf(stderr, "unable to register (YPBINDPROG, YPBINDVERS, udp).\n"); 4521927Swollman exit(1); 4531927Swollman } 4541927Swollman 4551927Swollman tcptransp = svctcp_create(RPC_ANYSOCK, 0, 0); 4561927Swollman if (tcptransp == NULL) { 4578425Swpaul fprintf(stderr, "cannot create tcp service.\n"); 4581927Swollman exit(1); 4591927Swollman } 4601927Swollman 4611927Swollman if (!svc_register(tcptransp, YPBINDPROG, YPBINDVERS, ypbindprog_2, 4621927Swollman IPPROTO_TCP)) { 4638425Swpaul fprintf(stderr, "unable to register (YPBINDPROG, YPBINDVERS, tcp).\n"); 4641927Swollman exit(1); 4651927Swollman } 4661927Swollman 4671927Swollman /* build initial domain binding, make it "unsuccessful" */ 4681927Swollman ypbindlist = (struct _dom_binding *)malloc(sizeof *ypbindlist); 4698246Swpaul if (ypbindlist == NULL) { 4708246Swpaul perror("malloc"); 4718246Swpaul exit(1); 4728246Swpaul } 4731927Swollman bzero((char *)ypbindlist, sizeof *ypbindlist); 47412862Swpaul strncpy(ypbindlist->dom_domain, domain_name, sizeof ypbindlist->dom_domain); 4751927Swollman ypbindlist->dom_vers = YPVERS; 4761927Swollman ypbindlist->dom_alive = 0; 4771927Swollman ypbindlist->dom_lockfd = -1; 4788091Swpaul ypbindlist->dom_default = 1; 4798425Swpaul domains++; 4801927Swollman 4818425Swpaul signal(SIGCHLD, reaper); 4828425Swpaul signal(SIGTERM, terminate); 4838425Swpaul 48421581Swpaul ppid = getpid(); /* Remember who we are. */ 48521581Swpaul 4868246Swpaul openlog(argv[0], LOG_PID, LOG_DAEMON); 4876478Swpaul 4888425Swpaul /* Kick off the default domain */ 4898425Swpaul broadcast(ypbindlist); 4908425Swpaul 4911927Swollman while(1) { 4921927Swollman fdsr = svc_fdset; 4938091Swpaul 4948425Swpaul tv.tv_sec = 60; 4951927Swollman tv.tv_usec = 0; 4961927Swollman 4978091Swpaul switch(select(_rpc_dtablesize(), &fdsr, NULL, NULL, &tv)) { 4981927Swollman case 0: 4991927Swollman checkwork(); 5001927Swollman break; 5011927Swollman case -1: 5028755Swpaul if (errno != EINTR) 50321539Swpaul syslog(LOG_WARNING, "select: %m"); 5048755Swpaul break; 5051927Swollman default: 5068755Swpaul for(ypdb=ypbindlist; ypdb; ypdb=ypdb->dom_pnext) { 5078755Swpaul if (READFD > 0 && FD_ISSET(READFD, &fdsr)) { 5088853Swpaul handle_children(ypdb); 5098755Swpaul if (children == (MAX_CHILDREN - 1)) 5108755Swpaul checkwork(); 5118091Swpaul } 5121927Swollman } 5131927Swollman svc_getreqset(&fdsr); 5141927Swollman break; 5151927Swollman } 5161927Swollman } 51721581Swpaul 51821581Swpaul /* NOTREACHED */ 51921581Swpaul exit(1); 5201927Swollman} 5211927Swollman 5228091Swpaulvoid 5231927Swollmancheckwork() 5241927Swollman{ 5251927Swollman struct _dom_binding *ypdb; 5261927Swollman 5278425Swpaul for(ypdb=ypbindlist; ypdb; ypdb=ypdb->dom_pnext) 5288425Swpaul ping(ypdb); 5291927Swollman} 5301927Swollman 5318091Swpaul/* The clnt_broadcast() callback mechanism sucks. */ 5328091Swpaul 5338091Swpaul/* 5348091Swpaul * Receive results from broadcaster. Don't worry about passing 5358853Swpaul * bogus info to rpc_received() -- it can handle it. Note that we 5368853Swpaul * must be sure to invalidate the dom_pipe_fds descriptors here: 5378853Swpaul * since descriptors can be re-used, we have to make sure we 5388853Swpaul * don't mistake one of the RPC descriptors for one of the pipes. 5398853Swpaul * What's weird is that forgetting to invalidate the pipe descriptors 5408853Swpaul * doesn't always result in an error (otherwise I would have caught 5418853Swpaul * the mistake much sooner), even though logically it should. 5428091Swpaul */ 5438853Swpaulvoid handle_children(ypdb) 5448853Swpaulstruct _dom_binding *ypdb; 5458091Swpaul{ 5468091Swpaul char buf[YPMAXDOMAIN + 1]; 5478091Swpaul struct sockaddr_in addr; 54821539Swpaul int d = 0, a = 0; 54921539Swpaul struct _dom_binding *y, *prev = NULL; 55021539Swpaul char path[MAXPATHLEN]; 5518091Swpaul 55221539Swpaul if ((d = read(READFD, &buf, sizeof(buf))) <= 0) 55321539Swpaul syslog(LOG_WARNING, "could not read from child: %m"); 5548755Swpaul 55521539Swpaul if ((a = read(READFD, &addr, sizeof(struct sockaddr_in))) < 0) 55621539Swpaul syslog(LOG_WARNING, "could not read from child: %m"); 55721539Swpaul 5588853Swpaul close(READFD); 5598853Swpaul FD_CLR(READFD, &fdsr); 5608853Swpaul FD_CLR(READFD, &svc_fdset); 5618853Swpaul READFD = WRITEFD = -1; 56221539Swpaul if (d > 0 && a > 0) 56321539Swpaul rpc_received((char *)&buf, &addr, 0); 56421539Swpaul else { 56521539Swpaul for(y=ypbindlist; y; y=y->dom_pnext) { 56621539Swpaul if (y == ypdb) 56721539Swpaul break; 56821539Swpaul prev = y; 56921539Swpaul } 57021539Swpaul switch(ypdb->dom_default) { 57121539Swpaul case 0: 57221539Swpaul if (prev == NULL) 57321539Swpaul ypbindlist = y->dom_pnext; 57421539Swpaul else 57521539Swpaul prev->dom_pnext = y->dom_pnext; 57621539Swpaul sprintf(path, "%s/%s.%ld", BINDINGDIR, 57721539Swpaul ypdb->dom_domain, YPVERS); 57821539Swpaul close(ypdb->dom_lockfd); 57921539Swpaul unlink(path); 58021539Swpaul free(ypdb); 58121539Swpaul domains--; 58221539Swpaul return; 58321539Swpaul case 1: 58421539Swpaul ypdb->dom_broadcast_pid = 0; 58521539Swpaul ypdb->dom_alive = 0; 58621539Swpaul broadcast(ypdb); 58721539Swpaul return; 58821539Swpaul default: 58921539Swpaul break; 59021539Swpaul } 59121539Swpaul } 59221539Swpaul 59321539Swpaul return; 5948091Swpaul} 5958091Swpaul 5968091Swpaul/* 5978091Swpaul * Send our dying words back to our parent before we perish. 5988091Swpaul */ 5998091Swpaulint 6008091Swpaultell_parent(dom, addr) 6011927Swollmanchar *dom; 6028091Swpaulstruct sockaddr_in *addr; 6031927Swollman{ 6048091Swpaul char buf[YPMAXDOMAIN + 1]; 6058091Swpaul struct timeval timeout; 6068091Swpaul fd_set fds; 6071927Swollman 6088091Swpaul timeout.tv_sec = 5; 6098091Swpaul timeout.tv_usec = 0; 6101927Swollman 6118755Swpaul sprintf(buf, "%s", broad_domain->dom_domain); 6128755Swpaul if (write(BROADFD, &buf, sizeof(buf)) < 0) 6138091Swpaul return(1); 6141927Swollman 6158091Swpaul /* 6168091Swpaul * Stay in sync with parent: wait for it to read our first 6178091Swpaul * message before sending the second. 6188091Swpaul */ 6191927Swollman 6208091Swpaul FD_ZERO(&fds); 6218755Swpaul FD_SET(BROADFD, &fds); 6228091Swpaul if (select(FD_SETSIZE, NULL, &fds, NULL, &timeout) == -1) 6238091Swpaul return(1); 6248755Swpaul if (FD_ISSET(BROADFD, &fds)) { 6258755Swpaul if (write(BROADFD, addr, sizeof(struct sockaddr_in)) < 0) 6268091Swpaul return(1); 6278091Swpaul } else { 6288091Swpaul return(1); 6291927Swollman } 6301927Swollman 6318755Swpaul close(BROADFD); 6328091Swpaul return (0); 6338091Swpaul} 6341927Swollman 6358091Swpaulbool_t broadcast_result(out, addr) 6368091Swpaulbool_t *out; 6378091Swpaulstruct sockaddr_in *addr; 6388091Swpaul{ 6399600Swpaul if (retries >= MAX_RETRIES) { 6409600Swpaul bzero((char *)addr, sizeof(struct sockaddr_in)); 6419600Swpaul if (tell_parent(broad_domain->dom_domain, addr)) 6429600Swpaul syslog(LOG_WARNING, "lost connection to parent"); 6439600Swpaul return TRUE; 6449600Swpaul } 6459600Swpaul 6469600Swpaul if (yp_restricted && verify(addr->sin_addr)) { 6479600Swpaul retries++; 6489600Swpaul syslog(LOG_NOTICE, "NIS server at %s not in restricted mode access list -- rejecting.\n",inet_ntoa(addr->sin_addr)); 6499600Swpaul return FALSE; 6509600Swpaul } else { 6519600Swpaul if (tell_parent(broad_domain->dom_domain, addr)) 6529600Swpaul syslog(LOG_WARNING, "lost connection to parent"); 6539600Swpaul return TRUE; 6549600Swpaul } 6558091Swpaul} 6568091Swpaul 6578091Swpaul/* 6588091Swpaul * The right way to send RPC broadcasts. 6598091Swpaul * Use the clnt_broadcast() RPC service. Unfortunately, clnt_broadcast() 6608091Swpaul * blocks while waiting for replies, so we have to fork off seperate 6618091Swpaul * broadcaster processes that do the waiting and then transmit their 6628091Swpaul * results back to the parent for processing. We also have to remember 6638091Swpaul * to save the name of the domain we're trying to bind in a global 6648091Swpaul * variable since clnt_broadcast() provides no way to pass things to 6658091Swpaul * the 'eachresult' callback function. 6668091Swpaul */ 6678091Swpaulvoid 6688091Swpaulbroadcast(ypdb) 6698091Swpaulstruct _dom_binding *ypdb; 6708091Swpaul{ 6718091Swpaul bool_t out = FALSE; 6728091Swpaul enum clnt_stat stat; 6738091Swpaul 6748755Swpaul if (children >= MAX_CHILDREN || ypdb->dom_broadcast_pid) 6758091Swpaul return; 6768091Swpaul 6778755Swpaul if (pipe(ypdb->dom_pipe_fds) < 0) { 67821539Swpaul syslog(LOG_WARNING, "pipe: %m"); 6798091Swpaul return; 6801927Swollman } 6811927Swollman 6828425Swpaul if (ypdb->dom_vers = -1 && (long)ypdb->dom_server_addr.sin_addr.s_addr) 6838755Swpaul syslog(LOG_WARNING, "NIS server [%s] for domain \"%s\" not responding", 6848425Swpaul inet_ntoa(ypdb->dom_server_addr.sin_addr), ypdb->dom_domain); 6858425Swpaul 6868755Swpaul broad_domain = ypdb; 6878425Swpaul flock(ypdb->dom_lockfd, LOCK_UN); 6888425Swpaul 6898755Swpaul switch((ypdb->dom_broadcast_pid = fork())) { 6908091Swpaul case 0: 6918755Swpaul close(READFD); 69221539Swpaul signal(SIGCHLD, SIG_DFL); 69321539Swpaul signal(SIGTERM, SIG_DFL); 6948091Swpaul break; 6958091Swpaul case -1: 69621539Swpaul syslog(LOG_WARNING, "fork: %m"); 6978755Swpaul close(READFD); 6988755Swpaul close(WRITEFD); 6998091Swpaul return; 7008091Swpaul default: 7018755Swpaul close(WRITEFD); 7028755Swpaul FD_SET(READFD, &svc_fdset); 7038091Swpaul children++; 7048091Swpaul return; 7051927Swollman } 7066478Swpaul 7078755Swpaul /* Release all locks before doing anything else. */ 7088755Swpaul while(ypbindlist) { 7098755Swpaul close(ypbindlist->dom_lockfd); 7108755Swpaul ypbindlist = ypbindlist->dom_pnext; 7118755Swpaul } 7128755Swpaul close(yplockfd); 7138755Swpaul 7149600Swpaul retries = 0; 7159600Swpaul 71612862Swpaul { 71712862Swpaul char *ptr; 7188091Swpaul 71912862Swpaul ptr = (char *)&ypdb->dom_domain; 72012862Swpaul stat = clnt_broadcast(YPPROG, YPVERS, YPPROC_DOMAIN_NONACK, 72112862Swpaul xdr_domainname, (char *)&ptr, xdr_bool, (char *)&out, 72212862Swpaul broadcast_result); 72312862Swpaul } 72412862Swpaul 7258091Swpaul if (stat != RPC_SUCCESS) { 7268091Swpaul bzero((char *)&ypdb->dom_server_addr, 7278091Swpaul sizeof(struct sockaddr_in)); 7288755Swpaul if (tell_parent(ypdb->dom_domain, &ypdb->dom_server_addr)) 7298091Swpaul syslog(LOG_WARNING, "lost connection to parent"); 7308091Swpaul } 7318755Swpaul 7328091Swpaul exit(0); 7331927Swollman} 7341927Swollman 7358091Swpaul/* 7368091Swpaul * The right way to check if a server is alive. 7378091Swpaul * Attempt to get a client handle pointing to the server and send a 7388755Swpaul * YPPROC_DOMAIN. If we can't get a handle or we get a reply of FALSE, 7398755Swpaul * we invalidate this binding entry and send out a broadcast to try to 7408755Swpaul * establish a new binding. Note that we treat non-default domains 7418091Swpaul * specially: once bound, we keep tabs on our server, but if it 7428091Swpaul * goes away and fails to respond after one round of broadcasting, we 7438091Swpaul * abandon it until a client specifically references it again. We make 7448091Swpaul * every effort to keep our default domain bound, however, since we 7458091Swpaul * need it to keep the system on its feet. 7468091Swpaul */ 7478091Swpaulint 7488425Swpaulping(ypdb) 7498091Swpaulstruct _dom_binding *ypdb; 7501927Swollman{ 7518091Swpaul bool_t out; 7528091Swpaul struct timeval interval, timeout; 7538091Swpaul enum clnt_stat stat; 7548091Swpaul int rpcsock = RPC_ANYSOCK; 7558755Swpaul CLIENT *client_handle; 7568091Swpaul time_t t; 7571927Swollman 7588755Swpaul interval.tv_sec = FAIL_THRESHOLD; 7598091Swpaul interval.tv_usec = 0; 7608091Swpaul timeout.tv_sec = FAIL_THRESHOLD; 7618091Swpaul timeout.tv_usec = 0; 7621927Swollman 7638755Swpaul if (ypdb->dom_broadcast_pid) 7648091Swpaul return(1); 7658091Swpaul 7668755Swpaul if ((client_handle = clntudp_bufcreate(&ypdb->dom_server_addr, 7678755Swpaul YPPROG, YPVERS, interval, &rpcsock, RPCSMALLMSGSIZE, 7688755Swpaul RPCSMALLMSGSIZE)) == (CLIENT *)NULL) { 7698755Swpaul /* Can't get a handle: we're dead. */ 7708755Swpaul ypdb->dom_alive = 0; 7718755Swpaul ypdb->dom_vers = -1; 7728755Swpaul broadcast(ypdb); 7738755Swpaul return(1); 7741927Swollman } 7751927Swollman 77612862Swpaul { 77712862Swpaul char *ptr; 77812862Swpaul 77912862Swpaul ptr = (char *)&ypdb->dom_domain; 78012862Swpaul 78112862Swpaul if ((stat = clnt_call(client_handle, YPPROC_DOMAIN, 78212862Swpaul xdr_domainname, (char *)&ptr, xdr_bool, (char *)&out, 78312862Swpaul timeout)) != RPC_SUCCESS || out == FALSE) { 78412862Swpaul ypdb->dom_alive = 0; 78512862Swpaul ypdb->dom_vers = -1; 78612862Swpaul clnt_destroy(client_handle); 78712862Swpaul broadcast(ypdb); 78812862Swpaul return(1); 78912862Swpaul } 7908091Swpaul } 7911927Swollman 7928755Swpaul clnt_destroy(client_handle); 7938091Swpaul return(0); 7941927Swollman} 7951927Swollman 7968091Swpaulvoid rpc_received(dom, raddrp, force) 7971927Swollmanchar *dom; 7981927Swollmanstruct sockaddr_in *raddrp; 7991927Swollmanint force; 8001927Swollman{ 8018425Swpaul struct _dom_binding *ypdb, *prev = NULL; 8021927Swollman struct iovec iov[2]; 8031927Swollman struct ypbind_resp ybr; 8041927Swollman char path[MAXPATHLEN]; 8051927Swollman int fd; 8061927Swollman 8076732Swpaul /*printf("returned from %s/%d about %s\n", inet_ntoa(raddrp->sin_addr), 8086732Swpaul ntohs(raddrp->sin_port), dom);*/ 8091927Swollman 8101927Swollman if(dom==NULL) 8111927Swollman return; 8121927Swollman 8138425Swpaul for(ypdb=ypbindlist; ypdb; ypdb=ypdb->dom_pnext) { 8148091Swpaul if( strcmp(ypdb->dom_domain, dom) == 0) 8158091Swpaul break; 8168425Swpaul prev = ypdb; 8178425Swpaul } 8188091Swpaul 8198755Swpaul if (ypdb && force) { 8208755Swpaul if (ypdb->dom_broadcast_pid) { 8218755Swpaul kill(ypdb->dom_broadcast_pid, SIGINT); 8228755Swpaul close(READFD); 8238853Swpaul FD_CLR(READFD, &fdsr); 8248853Swpaul FD_CLR(READFD, &svc_fdset); 8258853Swpaul READFD = WRITEFD = -1; 8268755Swpaul } 8278755Swpaul } 8288755Swpaul 8299600Swpaul /* if in secure mode, check originating port number */ 8309600Swpaul if ((ypsecuremode && (ntohs(raddrp->sin_port) >= IPPORT_RESERVED))) { 8316732Swpaul syslog(LOG_WARNING, "Rejected NIS server on [%s/%d] for domain %s.", 8326732Swpaul inet_ntoa(raddrp->sin_addr), ntohs(raddrp->sin_port), 8336732Swpaul dom); 8348246Swpaul if (ypdb != NULL) { 8358755Swpaul ypdb->dom_broadcast_pid = 0; 8368246Swpaul ypdb->dom_alive = 0; 8378246Swpaul } 8386732Swpaul return; 8396732Swpaul } 8406732Swpaul 8418246Swpaul if (raddrp->sin_addr.s_addr == (long)0) { 8428755Swpaul switch(ypdb->dom_default) { 8438755Swpaul case 0: 8448755Swpaul if (prev == NULL) 8458755Swpaul ypbindlist = ypdb->dom_pnext; 8468755Swpaul else 8478755Swpaul prev->dom_pnext = ypdb->dom_pnext; 8488755Swpaul sprintf(path, "%s/%s.%ld", BINDINGDIR, 8498755Swpaul ypdb->dom_domain, YPVERS); 8508755Swpaul close(ypdb->dom_lockfd); 8518755Swpaul unlink(path); 8528755Swpaul free(ypdb); 8538755Swpaul domains--; 8548755Swpaul return; 8558755Swpaul case 1: 8568755Swpaul ypdb->dom_broadcast_pid = 0; 8578755Swpaul ypdb->dom_alive = 0; 8588755Swpaul broadcast(ypdb); 8598755Swpaul return; 8608755Swpaul default: 8618755Swpaul break; 8628755Swpaul } 8638246Swpaul } 8648246Swpaul 8651927Swollman if(ypdb==NULL) { 8668246Swpaul if (force == 0) 8671927Swollman return; 8681927Swollman ypdb = (struct _dom_binding *)malloc(sizeof *ypdb); 8698857Srgrimes if (ypdb == NULL) { 87021539Swpaul syslog(LOG_WARNING, "malloc: %m"); 8718246Swpaul return; 8728246Swpaul } 8731927Swollman bzero((char *)ypdb, sizeof *ypdb); 8741927Swollman strncpy(ypdb->dom_domain, dom, sizeof ypdb->dom_domain); 8751927Swollman ypdb->dom_lockfd = -1; 8768091Swpaul ypdb->dom_default = 0; 8771927Swollman ypdb->dom_pnext = ypbindlist; 8781927Swollman ypbindlist = ypdb; 8791927Swollman } 8801927Swollman 8818091Swpaul /* We've recovered from a crash: inform the world. */ 8828091Swpaul if (ypdb->dom_vers = -1 && ypdb->dom_server_addr.sin_addr.s_addr) 8838755Swpaul syslog(LOG_WARNING, "NIS server [%s] for domain \"%s\" OK", 8848755Swpaul inet_ntoa(raddrp->sin_addr), ypdb->dom_domain); 8856478Swpaul 8861927Swollman bcopy((char *)raddrp, (char *)&ypdb->dom_server_addr, 8871927Swollman sizeof ypdb->dom_server_addr); 8886478Swpaul 8891927Swollman ypdb->dom_vers = YPVERS; 8901927Swollman ypdb->dom_alive = 1; 8918755Swpaul ypdb->dom_broadcast_pid = 0; 8921927Swollman 8931927Swollman if(ypdb->dom_lockfd != -1) 8941927Swollman close(ypdb->dom_lockfd); 8951927Swollman 8968091Swpaul sprintf(path, "%s/%s.%ld", BINDINGDIR, 8971927Swollman ypdb->dom_domain, ypdb->dom_vers); 8981927Swollman#ifdef O_SHLOCK 8997864Swpaul if( (fd=open(path, O_CREAT|O_SHLOCK|O_RDWR|O_TRUNC, 0644)) == -1) { 9001927Swollman (void)mkdir(BINDINGDIR, 0755); 9017864Swpaul if( (fd=open(path, O_CREAT|O_SHLOCK|O_RDWR|O_TRUNC, 0644)) == -1) 9021927Swollman return; 9031927Swollman } 9041927Swollman#else 9057864Swpaul if( (fd=open(path, O_CREAT|O_RDWR|O_TRUNC, 0644)) == -1) { 9061927Swollman (void)mkdir(BINDINGDIR, 0755); 9077864Swpaul if( (fd=open(path, O_CREAT|O_RDWR|O_TRUNC, 0644)) == -1) 9081927Swollman return; 9091927Swollman } 9101927Swollman flock(fd, LOCK_SH); 9111927Swollman#endif 9121927Swollman 9131927Swollman /* 9141927Swollman * ok, if BINDINGDIR exists, and we can create the binding file, 9151927Swollman * then write to it.. 9161927Swollman */ 9171927Swollman ypdb->dom_lockfd = fd; 9181927Swollman 9191927Swollman iov[0].iov_base = (caddr_t)&(udptransp->xp_port); 9201927Swollman iov[0].iov_len = sizeof udptransp->xp_port; 9211927Swollman iov[1].iov_base = (caddr_t)&ybr; 9221927Swollman iov[1].iov_len = sizeof ybr; 9231927Swollman 9241927Swollman bzero(&ybr, sizeof ybr); 9251927Swollman ybr.ypbind_status = YPBIND_SUCC_VAL; 92612862Swpaul *(u_long *)&ybr.ypbind_resp_u.ypbind_bindinfo.ypbind_binding_addr = raddrp->sin_addr.s_addr; 92712862Swpaul *(u_short *)&ybr.ypbind_resp_u.ypbind_bindinfo.ypbind_binding_port = raddrp->sin_port; 9281927Swollman 9291927Swollman if( writev(ypdb->dom_lockfd, iov, 2) != iov[0].iov_len + iov[1].iov_len) { 93021539Swpaul syslog(LOG_WARNING, "write: %m"); 9311927Swollman close(ypdb->dom_lockfd); 9321927Swollman ypdb->dom_lockfd = -1; 9331927Swollman return; 9341927Swollman } 9351927Swollman} 9369600Swpaul 9379600Swpaul/* 9389600Swpaul * Check address against list of allowed servers. Return 0 if okay, 9399600Swpaul * 1 if not matched. 9409600Swpaul */ 9419600Swpaulint 9429600Swpaulverify(addr) 9439600Swpaulstruct in_addr addr; 9449600Swpaul{ 9459600Swpaul int i; 9469600Swpaul 9479600Swpaul for (i = 0; i < RESTRICTED_SERVERS; i++) 9489600Swpaul if (!bcmp((char *)&addr, (char *)&restricted_addrs[i], 9499600Swpaul sizeof(struct in_addr))) 9509600Swpaul return(0); 9519600Swpaul 9529600Swpaul return(1); 9539600Swpaul} 9549600Swpaul 9559600Swpaul/* 9569600Swpaul * Try to set restricted mode. We default to normal mode if we can't 9579600Swpaul * resolve the specified hostnames. 9589600Swpaul */ 9599600Swpaulvoid 9609600Swpaulyp_restricted_mode(args) 9619600Swpaulchar *args; 9629600Swpaul{ 9639600Swpaul struct hostent *h; 9649600Swpaul int i = 0; 9659600Swpaul char *s; 9669600Swpaul 9679600Swpaul /* Find the restricted domain. */ 9689600Swpaul if ((s = strsep(&args, ",")) == NULL) 9699600Swpaul return; 97012862Swpaul domain_name = s; 9719600Swpaul 9729600Swpaul /* Get the addresses of the servers. */ 9739600Swpaul while ((s = strsep(&args, ",")) != NULL && i < RESTRICTED_SERVERS) { 9749600Swpaul if ((h = gethostbyname(s)) == NULL) 9759600Swpaul return; 9769600Swpaul bcopy ((char *)h->h_addr_list[0], (char *)&restricted_addrs[i], 9779600Swpaul sizeof(struct in_addr)); 9789600Swpaul i++; 9799600Swpaul } 9809600Swpaul 9819600Swpaul /* ypset and ypsetme not allowed with restricted mode */ 9829600Swpaul ypsetmode = YPSET_NO; 9839600Swpaul 9849600Swpaul yp_restricted = 1; 9859600Swpaul return; 9869600Swpaul} 987