/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley * under license from the Regents of the University of * California. */ #include "ypsym.h" #include #include "yp_b.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NULL #define NULL 0 #endif #define YPSERVERS "ypservers" void ypbind_init_default(); static int ypbind_pipe_setdom(); static bool firsttime = TRUE; static struct domain *known_domains; extern struct netconfig *__rpc_getconf(); extern void *__rpc_setconf(), *__rpc_endconf(); extern CLIENT *__clnt_tp_create_bootstrap(); extern char *inet_ntoa(); extern int __rpc_get_local_uid(); extern listofnames *names(); extern void free_listofnames(); #define PINGTIME 10 /* Timeout for the ypservers list */ #define PINGTOTTIM 5 /* Total seconds for ping timeout */ static void broadcast_setup(); static void sigcld_handler(); static struct ypbind_binding *dup_ypbind_binding(); static struct netbuf *dup_netbuf(); static void free_ypbind_binding(); static void enable_exit(); static void ypbind_ping(); static struct domain *ypbind_point_to_domain(); static bool ypbind_broadcast_ack(); static int pong_servers(); void cache_binding(); void uncache_binding(); extern int setok; extern int broadcast; extern int cache_okay; /* * Need to differentiate between RPC_UNKNOWNHOST returned by the RPC * library, and the same error caused by a local lookup failure in * /etc/hosts and/or /etc/inet/ipnodes. */ int hostNotKnownLocally; /*ARGSUSED*/ void * ypbindproc_null_3(argp, clnt) void *argp; CLIENT *clnt; { static char res; return ((void *) & res); } static void enable_exit() { static bool done = FALSE; if (!done) { done = TRUE; sigset(SIGCHLD, (void (*)())sigcld_handler); } } int sigcld_event = 0; static void sigcld_handler() { sigcld_event++; #ifdef DEBUG fprintf(stderr, "ypbind sighandler: got SIGCLD signal (event=%d)\n", sigcld_event); #endif } /* * This is a Unix SIGCHILD handler that notices when a broadcaster child * process has exited, and retrieves the exit status. The broadcaster pid * is set to 0. If the broadcaster succeeded, dom_report_success will be * be set to -1. */ void broadcast_proc_exit() { int pid, ret; siginfo_t infop; register struct domain *pdom; bool succeeded = FALSE; sigcld_event = 0; /* ==== Why WEXITED? */ while ((ret = waitid(P_ALL, 0, &infop, WNOHANG | WEXITED)) != -1) { switch (infop.si_code) { case CLD_EXITED: succeeded = infop.si_status == 0; break; case CLD_KILLED: case CLD_DUMPED: succeeded = FALSE; break; case CLD_TRAPPED: case CLD_STOPPED: case CLD_CONTINUED: enable_exit(); return; } pid = infop.si_pid; #ifdef DEBUG fprintf(stderr, "ypbind event_handler: got wait from %d status = %d\n", pid, infop.si_status); #endif /* to aid the progeny print the infamous "not responding" message */ firsttime = FALSE; for (pdom = known_domains; pdom != (struct domain *)NULL; pdom = pdom->dom_pnext) { if (pdom->dom_broadcaster_pid == pid) { #ifdef DEBUG fprintf(stderr, "ypbind event_handler: got match %s\n", pdom->dom_name); #endif if (succeeded) { broadcast_setup(pdom); } if (pdom->broadcaster_pipe != 0) { xdr_destroy(&(pdom->broadcaster_xdr)); fclose(pdom->broadcaster_pipe); pdom->broadcaster_pipe = 0; pdom->broadcaster_fd = -1; } pdom->dom_broadcaster_pid = 0; break; } } } /* while loop */ enable_exit(); } static void broadcast_setup(pdom) struct domain *pdom; { ypbind_setdom req; memset(&req, 0, sizeof (req)); if (pdom->broadcaster_pipe) { pdom->dom_report_success = -1; if (xdr_ypbind_setdom(&(pdom->broadcaster_xdr), &req)) { #ifdef DEBUG fprintf(stderr, "parent: broadcast_setup: got xdr ok \n"); #endif ypbindproc_setdom_3(&req, (struct svc_req *)NULL, (SVCXPRT *)NULL); xdr_free(xdr_ypbind_setdom, (char *)&req); gettimeofday(&(pdom->lastping), NULL); } #ifdef DEBUG else { fprintf(stderr, "ypbind parent: xdr_ypbind_setdom failed\n"); } #endif } #ifdef DEBUG else { fprintf(stderr, "ypbind: internal error -- no broadcaster pipe\n"); } #endif } #define YPBIND_PINGHOLD_DOWN 5 /* Same as the ypbind_get_binding() routine in SunOS */ /*ARGSUSED*/ ypbind_resp * ypbindproc_domain_3(argp, clnt) ypbind_domain *argp; CLIENT *clnt; { static ypbind_resp resp; struct domain *cur_domain; int bpid; int fildes[2]; memset((char *)&resp, 0, sizeof (resp)); #ifdef DEBUG fprintf(stderr, "\nypbindproc_domain_3: domain: %s\n", argp->ypbind_domainname); #endif if ((int)strlen(argp->ypbind_domainname) > YPMAXDOMAIN) { resp.ypbind_status = YPBIND_FAIL_VAL; resp.ypbind_resp_u.ypbind_error = YPBIND_ERR_NOSERV; return (&resp); } if ((cur_domain = ypbind_point_to_domain(argp->ypbind_domainname)) != (struct domain *)NULL) { if (cur_domain->dom_boundp) { struct timeval tp; (void) gettimeofday(&tp, NULL); if ((tp.tv_sec - cur_domain->lastping.tv_sec) > YPBIND_PINGHOLD_DOWN) { #ifdef DEBUG fprintf(stderr, "domain is bound pinging: %s\n", argp->ypbind_domainname); #endif (void) ypbind_ping(cur_domain); } } /* * Bound or not, return the current state of the binding. */ if (cur_domain->dom_boundp) { #ifdef DEBUG fprintf(stderr, "server is up for domain: %s\n", argp->ypbind_domainname); #endif resp.ypbind_status = YPBIND_SUCC_VAL; resp.ypbind_resp_u.ypbind_bindinfo = cur_domain->dom_binding; } else { #ifdef DEBUG fprintf(stderr, "domain is NOT bound returning: %s %d\n", argp->ypbind_domainname, cur_domain->dom_error); #endif resp.ypbind_status = YPBIND_FAIL_VAL; resp.ypbind_resp_u.ypbind_error = cur_domain->dom_error; } } else { resp.ypbind_status = YPBIND_FAIL_VAL; resp.ypbind_resp_u.ypbind_error = YPBIND_ERR_RESC; } /* * RETURN NOW: if successful, otherwise * RETURN LATER: after spawning off a child to do the "broadcast" work. */ if (resp.ypbind_status == YPBIND_SUCC_VAL) { #ifdef DEBUG fprintf(stderr, "yp_b_subr: returning success to yp_b_svc %d\n", resp.ypbind_status); #endif return (&resp); } /* Go about the broadcast (really, pinging here) business */ if ((cur_domain) && (!cur_domain->dom_boundp) && (!cur_domain->dom_broadcaster_pid)) { #ifdef DEBUG fprintf(stderr, "yp_b_subr: fork: boundp=%d broadcast_pid=%d\n", cur_domain->dom_boundp, cur_domain->dom_broadcaster_pid); #endif /* * The current domain is unbound, and there is no child * process active now. Fork off a child who will beg to the * ypservers list one by one or broadcast and accept whoever * commands the right domain. */ if (pipe(fildes) < 0) { #ifdef DEBUG fprintf(stderr, "yp_b_subr: returning pipe failure to yp_b_svc %d\n", resp.ypbind_status); #endif return (&resp); } enable_exit(); sighold(SIGCLD); /* add it to ypbind's signal mask */ cur_domain->dom_report_success++; bpid = fork(); if (bpid != 0) { /* parent */ if (bpid > 0) { /* parent started */ close(fildes[1]); cur_domain->dom_broadcaster_pid = bpid; cur_domain->broadcaster_fd = fildes[0]; cur_domain->broadcaster_pipe = fdopen(fildes[0], "r"); if (cur_domain->broadcaster_pipe) xdrstdio_create(&(cur_domain->broadcaster_xdr), (cur_domain->broadcaster_pipe), XDR_DECODE); #ifdef DEBUG fprintf(stderr, "ypbindproc_domain_3: %s starting pid = %d try = %d\n", cur_domain->dom_name, bpid, cur_domain->dom_report_success); fprintf(stderr, "yp_b_subr: returning after spawning, to yp_b_svc %d\n", resp.ypbind_status); #endif sigrelse(SIGCLD); /* remove it from ypbind's signal mask */ return (&resp); } else { /* fork failed */ perror("fork"); close(fildes[0]); close(fildes[1]); #ifdef DEBUG fprintf(stderr, "yp_b_subr: returning fork failure to yp_b_svc %d\n", resp.ypbind_status); #endif sigrelse(SIGCLD); return (&resp); } } /* end parent */ /* child only code */ sigrelse(SIGCLD); close(fildes[0]); cur_domain->broadcaster_fd = fildes[1]; cur_domain->broadcaster_pipe = fdopen(fildes[1], "w"); if (cur_domain->broadcaster_pipe) xdrstdio_create(&(cur_domain->broadcaster_xdr), (cur_domain->broadcaster_pipe), XDR_ENCODE); else { perror("fdopen-pipe"); exit(-1); } exit(pong_servers(cur_domain)); } #ifdef DEBUG fprintf(stderr, "yp_b_subr: lazy returns failure status yp_b_svc %d\n", resp.ypbind_status); #endif return (&resp); } /* * call ypbindproc_domain_3 and convert results * * This adds support for YP clients that send requests on * ypbind version 1 & 2 (i.e. clients before we started * using universal addresses and netbufs). This is supported * for binary compatibility for static 4.x programs. The * assumption used to be that clients coming in with ypbind vers 1 * should be given the address of a server serving ypserv version 1. * However, since yp_bind routines in 4.x YP library try * to connect with ypserv version 2, even if they requested * binding using ypbind version 1, the ypbind process will * "always" look for only ypserv version 2 servers for all * (ypbind vers 1, 2, & 3) clients. */ ypbind_resp_2 * ypbindproc_domain_2(argp, clnt) domainname_2 *argp; CLIENT *clnt; { ypbind_domain arg_3; ypbind_resp *resp_3; static ypbind_resp_2 resp; arg_3.ypbind_domainname = *argp; resp_3 = ypbindproc_domain_3(&arg_3, clnt); if (resp_3 == NULL) return (NULL); resp.ypbind_status = resp_3->ypbind_status; if (resp_3->ypbind_status == YPBIND_SUCC_VAL) { struct sockaddr_in *sin; struct ypbind_binding_2 *bi; sin = (struct sockaddr_in *) resp_3->ypbind_resp_u.ypbind_bindinfo->ypbind_svcaddr->buf; if (sin->sin_family == AF_INET) { bi = &resp.ypbind_respbody_2.ypbind_bindinfo; memcpy(&(bi->ypbind_binding_port), &sin->sin_port, 2); memcpy(&(bi->ypbind_binding_addr), &sin->sin_addr, 4); } else { resp.ypbind_respbody_2.ypbind_error = YPBIND_ERR_NOSERV; } } else { resp.ypbind_respbody_2.ypbind_error = resp_3->ypbind_resp_u.ypbind_error; } return (&resp); } /* used to exchange information between pong_servers and ypbind_broadcast_ack */ struct domain *process_current_domain; int pong_servers(domain_struct) struct domain *domain_struct; /* to pass back */ { char *domain = domain_struct->dom_name; CLIENT *clnt2; char *servername; listofnames *list, *lin; char serverfile[MAXNAMLEN]; struct timeval timeout; int isok = 0, res = -1; struct netconfig *nconf; void *handle; int nconf_count; char rpcdomain[YPMAXDOMAIN+1]; long inforet; /* * If the ``domain'' name passed in is not the same as the RPC * domain set from /etc/defaultdomain. Then we set ``firsttime'' * to TRUE so no error messages are ever syslog()-ed this * prevents a possible Denial of Service attack. */ inforet = sysinfo(SI_SRPC_DOMAIN, &(rpcdomain[0]), YPMAXDOMAIN); if ((inforet > 0) && (strcmp(domain, rpcdomain) != 0)) firsttime = TRUE; if (broadcast) { enum clnt_stat stat = RPC_SUCCESS; #ifdef DEBUG fprintf(stderr, "pong_servers: doing an rpc_broadcast\n"); #endif /* * Here we do the real SunOS thing that users love. Do a * broadcast on the network and find out the ypserv. No need * to do "ypinit -c", no setting up /etc/hosts file, and no * recursion looking up the server's IP address. */ process_current_domain = domain_struct; stat = rpc_broadcast(YPPROG, YPVERS, YPPROC_DOMAIN_NONACK, (xdrproc_t)xdr_ypdomain_wrap_string, (caddr_t)&domain, xdr_int, (caddr_t)&isok, (resultproc_t)ypbind_broadcast_ack, "udp"); if (stat == RPC_SYSTEMERROR || stat == RPC_UNKNOWNPROTO || stat == RPC_CANTRECV || stat == RPC_CANTSEND || stat == RPC_NOBROADCAST || stat == RPC_N2AXLATEFAILURE) { syslog(LOG_ERR, "RPC/Transport subsystem failure %s\n", clnt_sperrno(stat)); exit(-1); } if (domain_struct->broadcaster_pipe == 0) /* init binding case */ return (domain_struct->dom_boundp - 1); if (domain_struct->dom_boundp) { res = ypbind_pipe_setdom(NULL, domain, NULL, domain_struct); if (domain_struct->dom_report_success > 0) syslog(LOG_ERR, "NIS server for domain \"%s\" OK", domain); } else if (firsttime == FALSE) syslog(LOG_ERR, "NIS server not responding for domain \"%s\"; still trying", domain); return (res); } #ifdef DEBUG fprintf(stderr, "pong_servers: ponging servers one by one\n"); #endif /* * Do the politically correct thing.. transport independent and * secure (trusts only listed servers). */ /* * get list of possible servers for this domain */ /* * get alias for domain: Things of the past.. * sysvconfig(); * (void) yp_getalias(domain, domain_alias, NAME_MAX); */ sprintf(serverfile, "%s/%s/%s", BINDING, domain, YPSERVERS); #ifdef DEBUG fprintf(stderr, "pong_servers: serverfile %s\n", serverfile); #endif list = names(serverfile); if (list == NULL) { if (firsttime == FALSE) syslog(LOG_ERR, "service not installed, use /usr/sbin/ypinit -c"); return (-1); } lin = list; for (list = lin; list; list = list->nextname) { servername = strtok(list->name, " \t\n"); if (servername == NULL) continue; /* Check all datagram_v transports for this server */ if ((handle = __rpc_setconf("datagram_v")) == NULL) { syslog(LOG_ERR, "ypbind: RPC operation on /etc/netconfig failed"); free_listofnames(lin); return (-1); } nconf_count = 0; clnt2 = 0; while (clnt2 == 0 && (nconf = __rpc_getconf(handle)) != 0) { nconf_count++; /* * We use only datagram here. It is expected to be udp. * VERY IMPORTANT: __clnt_tp_create_bootstrap is a * hacked up version that does not do netdir_getbyname. */ hostNotKnownLocally = 0; clnt2 = __clnt_tp_create_bootstrap(servername, YPPROG, YPVERS, nconf); } if (nconf_count == 0) { syslog(LOG_ERR, "ypbind: RPC operation on /etc/netconfig failed"); free_listofnames(lin); return (-1); } if (clnt2 == 0) { if (rpc_createerr.cf_stat == RPC_UNKNOWNHOST && hostNotKnownLocally) { syslog(LOG_ERR, "NIS server %s is not in local host files !", servername); } perror(servername); clnt_pcreateerror("ypbind"); continue; } timeout.tv_sec = PINGTIME; timeout.tv_usec = 0; if ((enum clnt_stat) clnt_call(clnt2, YPPROC_DOMAIN, (xdrproc_t)xdr_ypdomain_wrap_string, (char *)&domain, xdr_int, (char *)&isok, timeout) == RPC_SUCCESS) { if (isok) { if (domain_struct->dom_report_success > 0) { syslog(LOG_ERR, "NIS server for domain \"%s\" OK", domain); } if (domain_struct->broadcaster_pipe == 0) { /* init binding case --parent */ struct netconfig *setnc; struct netbuf setua; struct ypbind_binding *b = domain_struct->dom_binding; setnc = getnetconfigent(clnt2->cl_netid); if (b == NULL) { /* ASSERT: This shouldn't happen ! */ b = (struct ypbind_binding *)calloc(1, sizeof (*b)); domain_struct->dom_binding = b; if (b == NULL) { __rpc_endconf(handle); clnt_destroy(clnt2); free_listofnames(lin); return (-2); } } b->ypbind_nconf = setnc; clnt_control(clnt2, CLGET_SVC_ADDR, (char *)&setua); if (b->ypbind_svcaddr) { if (b->ypbind_svcaddr->buf) free(b->ypbind_svcaddr->buf); free(b->ypbind_svcaddr); } b->ypbind_svcaddr = dup_netbuf(&setua); if (b->ypbind_servername) free(b->ypbind_servername); b->ypbind_servername = strdup(servername); b->ypbind_hi_vers = YPVERS; b->ypbind_lo_vers = YPVERS; __rpc_endconf(handle); domain_struct->dom_boundp = TRUE; clnt_destroy(clnt2); free_listofnames(lin); return (0); } res = ypbind_pipe_setdom(clnt2, domain, servername, domain_struct); __rpc_endconf(handle); clnt_destroy(clnt2); free_listofnames(lin); return (res); } else { syslog(LOG_ERR, "server %s doesn't serve domain %s\n", servername, domain); } } else { clnt_perror(clnt2, servername); } clnt_destroy(clnt2); } /* * We tried all servers, none obliged ! * After ypbind is started up it will not be bound * immediately. This is normal, no error message * is needed. Although, with the ypbind_init_default * it will be bound immediately. */ if (firsttime == FALSE) { syslog(LOG_ERR, "NIS server not responding for domain \"%s\"; still trying", domain); } free_listofnames(lin); __rpc_endconf(handle); return (-2); } struct netbuf * dup_netbuf(inbuf) struct netbuf *inbuf; { struct netbuf *outbuf; if (inbuf == NULL) return (NULL); if ((outbuf = (struct netbuf *)calloc(1, sizeof (struct netbuf))) == NULL) return (NULL); if ((outbuf->buf = malloc(inbuf->len)) == NULL) { free(outbuf); return (NULL); } outbuf->len = inbuf->len; outbuf->maxlen = inbuf->len; (void) memcpy(outbuf->buf, inbuf->buf, inbuf->len); return (outbuf); } /* * This is called by the broadcast rpc routines to process the responses * coming back from the broadcast request. Since the form of the request * which is used in ypbind_broadcast_bind is "respond only in the positive * case", we know that we have a server. * The internet address of the responding server will be picked up from * the saddr parameter, and stuffed into the domain. The domain's boundp * field will be set TRUE. The first responding server (or the first one * which is on a reserved port) will be the bound server for the domain. */ bool ypbind_broadcast_ack(ptrue, nbuf, nconf) bool *ptrue; struct netbuf *nbuf; struct netconfig *nconf; { struct ypbind_binding b; process_current_domain->dom_boundp = TRUE; b.ypbind_nconf = nconf; b.ypbind_svcaddr = nbuf; b.ypbind_servername = "\000"; b.ypbind_hi_vers = YPVERS; b.ypbind_lo_vers = YPVERS; free_ypbind_binding(process_current_domain->dom_binding); process_current_domain->dom_binding = dup_ypbind_binding(&b); return (TRUE); } /* * WARNING: This routine is entered only by the child process. * Called if it pongs/broadcasts okay. */ static int ypbind_pipe_setdom(client, domain, servername, opaque_domain) CLIENT *client; char *servername; char *domain; struct domain *opaque_domain; { struct netconfig *setnc; struct netbuf setua; ypbind_binding setb; ypbind_setdom setd; int retval; setd.ypsetdom_domain = domain; if (client == NULL && opaque_domain->dom_binding) { #ifdef DEBUG fprintf(stderr, "ypbind_pipe_setdom: child broadcast case "); #endif /* ypbind_broadcast_ack already setup dom_binding for us */ setd.ypsetdom_bindinfo = opaque_domain->dom_binding; } else if (client) { #ifdef DEBUG fprintf(stderr, "ypbind_pipe_setdom: child unicast case "); #endif setnc = getnetconfigent(client->cl_netid); if (setnc == NULL) { #ifdef DEBUG fprintf(stderr, "PANIC: shouldn't happen\n"); #endif fclose(opaque_domain->broadcaster_pipe); close(opaque_domain->broadcaster_fd); return (-2); } clnt_control(client, CLGET_SVC_ADDR, (char *)&setua); setb.ypbind_nconf = setnc; setb.ypbind_svcaddr = &setua; setb.ypbind_servername = servername; setb.ypbind_hi_vers = YPVERS; setb.ypbind_lo_vers = YPVERS; setd.ypsetdom_bindinfo = &setb; /* * Let's hardcode versions, that is the only ypserv we support anyway. * Avoid the song and dance of recursively calling ypbind_ping * for no reason. Consistent with the 4.1 policy, that if ypbind gets * a request on new binder protocol, the requestor is looking for the * new ypserv. And, we have even higher binder protocol version i.e. 3. */ } else return (-1); #ifdef DEBUG fprintf(stderr, " saving server settings, \nsupports versions %d thru %d\n", setd.ypsetdom_bindinfo->ypbind_lo_vers, setd.ypsetdom_bindinfo->ypbind_hi_vers); #endif if (opaque_domain->broadcaster_pipe == 0) { #ifdef DEBUG fprintf(stderr, "PANIC: shouldn't be in this function\n"); #endif return (-2); } #ifdef DEBUG fprintf(stderr, "child: doing xdr_ypbind_setdom\n"); #endif retval = xdr_ypbind_setdom(&(opaque_domain->broadcaster_xdr), &setd); xdr_destroy(&(opaque_domain->broadcaster_xdr)); fclose(opaque_domain->broadcaster_pipe); close(opaque_domain->broadcaster_fd); /* * This child process is about to exit. Don't bother freeing memory. */ if (!retval) { #ifdef DEBUG fprintf(stderr, "YPBIND pipe_setdom failed \n(xdr failure) to server %s\n", servername ? servername : ""); #endif return (-3); } #ifdef DEBUG fprintf(stderr, "ypbind_pipe_setdom: YPBIND OK-set to server %s\n", servername ? servername : ""); #endif return (0); } /* Same as ypbind_set_binding in SunOS */ /* * We use a trick from SunOS to return an error to the ypset command * when we are not allowing the domain to be set. We do a svcerr_noprog() * to send RPC_PROGUNAVAIL to ypset. We also return NULL so that * our caller (ypbindprog_3) won't try to return a result. This * hack is necessary because the YPBINDPROC_SETDOM procedure is defined * in the protocol to return xdr_void, so we don't have a direct way to * return an error to the client. */ /*ARGSUSED*/ void * ypbindproc_setdom_3(argp, rqstp, transp) ypbind_setdom *argp; struct svc_req *rqstp; SVCXPRT *transp; { struct domain *a_domain; struct netbuf *who; static char res; /* dummy for void * return */ uid_t caller_uid; if ((int)strlen(argp->ypsetdom_domain) > YPMAXDOMAIN) { if (transp) { svcerr_systemerr(transp); return (0); } return (&res); } if (transp != NULL) { /* find out who originated the request */ char *uaddr; struct netconfig *nconf; who = svc_getrpccaller(transp); if ((nconf = getnetconfigent(transp->xp_netid)) == (struct netconfig *)NULL) { svcerr_systemerr(transp); return (0); } if (strcmp(nconf->nc_protofmly, NC_LOOPBACK) == 0) { uaddr = strdup("local host"); } else { uaddr = taddr2uaddr(nconf, who); } if (setok != YPSETALL) { /* for -ypset, it falls through and let anybody do a setdom ! */ if (strcmp(nconf->nc_protofmly, NC_LOOPBACK) != 0) { syslog(LOG_ERR, "ypset request from %s not on loopback, \ cannot set ypbind to %s", uaddr ? uaddr : "unknown source", argp->ypsetdom_bindinfo->ypbind_servername); if (uaddr) free(uaddr); freenetconfigent(nconf); svcerr_noprog(transp); return (0); } switch (setok) { case YPSETNONE: if (strcmp(nconf->nc_protofmly, NC_LOOPBACK) == 0) syslog(LOG_ERR, "ypset request to %s from %s failed - ypset not allowed", argp->ypsetdom_bindinfo->ypbind_servername, uaddr); if (uaddr) free(uaddr); freenetconfigent(nconf); svcerr_noprog(transp); return (0); case YPSETLOCAL: if (__rpc_get_local_uid(transp, &caller_uid) < 0) { syslog(LOG_ERR, "ypset request from \ unidentified local user on %s - ypset not allowed", transp->xp_netid); if (uaddr) free(uaddr); freenetconfigent(nconf); svcerr_noprog(transp); return (0); } if (caller_uid != 0) { syslog(LOG_ERR, "Set domain request to host %s \ from local non-root user %ld failed - ypset not allowed", argp->ypsetdom_bindinfo->ypbind_servername, caller_uid); if (uaddr) free(uaddr); freenetconfigent(nconf); svcerr_noprog(transp); return (0); } } } syslog(LOG_ERR, "Set domain request from %s : \ setting server for domain %s to %s", uaddr ? uaddr : "UNKNOWN SOURCE", argp->ypsetdom_domain, argp->ypsetdom_bindinfo->ypbind_servername); if (uaddr) free(uaddr); freenetconfigent(nconf); } if ((a_domain = ypbind_point_to_domain(argp->ypsetdom_domain)) != (struct domain *)NULL) { /* setting binding; old may be invalid */ uncache_binding(a_domain); /* this does the set -- should copy the structure */ free_ypbind_binding(a_domain->dom_binding); if ((a_domain->dom_binding = dup_ypbind_binding(argp->ypsetdom_bindinfo)) == NULL) { syslog(LOG_ERR, "ypbindproc_setdom_3: out of memory, ", "dup_ypbind_binding failed\n"); if (transp) { svcerr_noprog(transp); return (0); } return (&res); } gettimeofday(&(a_domain->lastping), NULL); a_domain->dom_boundp = TRUE; cache_binding(a_domain); #ifdef DEBUG fprintf(stderr, "ypbindproc_setdom_3: setting domain %s to server %s\n", argp->ypsetdom_domain, argp->ypsetdom_bindinfo->ypbind_servername); #endif } return (&res); } /* * This returns a pointer to a domain entry. If no such domain existed on * the list previously, an entry will be allocated, initialized, and linked * to the list. Note: If no memory can be malloc-ed for the domain structure, * the functional value will be (struct domain *) NULL. */ static struct domain * ypbind_point_to_domain(pname) register char *pname; { register struct domain *pdom; char buf[300]; for (pdom = known_domains; pdom != (struct domain *)NULL; pdom = pdom->dom_pnext) { if (strcmp(pname, pdom->dom_name) == 0) return (pdom); } /* Not found. Add it to the list */ if (pdom = (struct domain *)calloc(1, sizeof (struct domain))) { pdom->dom_name = strdup(pname); if (pdom->dom_name == NULL) { free((char *)pdom); syslog(LOG_ERR, "ypbind_point_to_domain: strdup failed\n"); return (NULL); } pdom->dom_pnext = known_domains; known_domains = pdom; pdom->dom_boundp = FALSE; pdom->dom_vers = YPVERS; /* This doesn't talk to old ypserv */ pdom->dom_binding = NULL; pdom->dom_error = YPBIND_ERR_NOSERV; pdom->ping_clnt = (CLIENT *)NULL; pdom->dom_report_success = -1; pdom->dom_broadcaster_pid = 0; pdom->broadcaster_pipe = 0; pdom->bindfile = -1; pdom->lastping.tv_sec = 0; pdom->lastping.tv_usec = 0; /* require ping */ pdom->cache_fp = 0; sprintf(buf, "%s/%s/cache_binding", BINDING, pdom->dom_name); pdom->cache_file = strdup(buf); /* * We don't give an error if pdom->cache_file is not set. * If we got null (out of memory), then we just won't use * the cache file in cache_binding() (assuming the * application gets that far. */ } else syslog(LOG_ERR, "ypbind_point_to_domain: malloc failed\n"); return (pdom); } static void ypbind_ping(pdom) struct domain *pdom; { struct timeval timeout; int vers; int isok; if (pdom->dom_boundp == FALSE) return; vers = pdom->dom_vers; if (pdom->ping_clnt == (CLIENT *) NULL) { pdom->ping_clnt = __nis_clnt_create(RPC_ANYFD, pdom->dom_binding->ypbind_nconf, 0, pdom->dom_binding->ypbind_svcaddr, 0, YPPROG, vers, 0, 0); } if (pdom->ping_clnt == (CLIENT *) NULL) { perror("clnt_tli_create"); clnt_pcreateerror("ypbind_ping()"); pdom->dom_boundp = FALSE; pdom->dom_error = YPBIND_ERR_NOSERV; return; } #ifdef DEBUG fprintf(stderr, "ypbind: ypbind_ping()\n"); #endif timeout.tv_sec = PINGTOTTIM; timeout.tv_usec = 0; if (clnt_call(pdom->ping_clnt, YPPROC_DOMAIN, (xdrproc_t)xdr_ypdomain_wrap_string, (char *)&pdom->dom_name, xdr_int, (char *)&isok, timeout) == RPC_SUCCESS) { pdom->dom_boundp = isok; pdom->dom_binding->ypbind_lo_vers = vers; pdom->dom_binding->ypbind_hi_vers = vers; #ifdef DEBUG fprintf(stderr, "Server pinged successfully, supports versions %d thru %d\n", pdom->dom_binding->ypbind_lo_vers, pdom->dom_binding->ypbind_hi_vers); #endif } else { clnt_perror(pdom->ping_clnt, "ping"); pdom->dom_boundp = FALSE; pdom->dom_error = YPBIND_ERR_NOSERV; } (void) gettimeofday(&(pdom->lastping), NULL); if (pdom->ping_clnt) clnt_destroy(pdom->ping_clnt); pdom->ping_clnt = (CLIENT *)NULL; if (pdom->dom_boundp) cache_binding(pdom); } static struct ypbind_binding * dup_ypbind_binding(a) struct ypbind_binding *a; { struct ypbind_binding *b; struct netconfig *nca, *ncb; struct netbuf *nxa, *nxb; int i; b = (struct ypbind_binding *)calloc(1, sizeof (*b)); if (b == NULL) return (b); b->ypbind_hi_vers = a->ypbind_hi_vers; b->ypbind_lo_vers = a->ypbind_lo_vers; b->ypbind_servername = a->ypbind_servername ? strdup(a->ypbind_servername) : NULL; ncb = (b->ypbind_nconf = (struct netconfig *)calloc(1, sizeof (struct netconfig))); nxb = (b->ypbind_svcaddr = (struct netbuf *)calloc(1, sizeof (struct netbuf))); nca = a->ypbind_nconf; nxa = a->ypbind_svcaddr; ncb->nc_flag = nca->nc_flag; ncb->nc_protofmly = nca->nc_protofmly ? strdup(nca->nc_protofmly) : NULL; ncb->nc_proto = nca->nc_proto ? strdup(nca->nc_proto) : NULL; ncb->nc_semantics = nca->nc_semantics; ncb->nc_netid = nca->nc_netid ? strdup(nca->nc_netid) : NULL; ncb->nc_device = nca->nc_device ? strdup(nca->nc_device) : NULL; ncb->nc_nlookups = nca->nc_nlookups; ncb->nc_lookups = (char **)calloc(nca->nc_nlookups, sizeof (char *)); if (ncb->nc_lookups == NULL) { if (ncb->nc_device) free(ncb->nc_device); if (ncb->nc_netid) free(ncb->nc_netid); if (ncb->nc_proto) free(ncb->nc_proto); if (ncb->nc_protofmly) free(ncb->nc_protofmly); if (nxb) free(nxb); if (ncb) free(ncb); if (b->ypbind_servername) free(b->ypbind_servername); if (b) free(b); return (NULL); } for (i = 0; i < nca->nc_nlookups; i++) ncb->nc_lookups[i] = nca->nc_lookups[i] ? strdup(nca->nc_lookups[i]) : NULL; for (i = 0; i < 8; i++) ncb->nc_unused[i] = nca->nc_unused[i]; nxb->maxlen = nxa->maxlen; nxb->len = nxa->len; nxb->buf = malloc(nxa->maxlen); if (nxb->buf == NULL) { for (i = 0; i < nca->nc_nlookups; i++) if (ncb->nc_lookups[i]) free(ncb->nc_lookups[i]); free(ncb->nc_lookups); if (ncb->nc_device) free(ncb->nc_device); if (ncb->nc_netid) free(ncb->nc_netid); if (ncb->nc_proto) free(ncb->nc_proto); if (ncb->nc_protofmly) free(ncb->nc_protofmly); if (nxb) free(nxb); if (ncb) free(ncb); if (b->ypbind_servername) free(b->ypbind_servername); if (b) free(b); return (NULL); } memcpy(nxb->buf, nxa->buf, nxb->len); return (b); } static void free_ypbind_binding(b) struct ypbind_binding *b; { if (b == NULL) return; netdir_free((char *)b->ypbind_svcaddr, ND_ADDR); free(b->ypbind_servername); freenetconfigent(b->ypbind_nconf); free(b); } /* * Preloads teh default domain's domain binding. Domain binding for the * local node's default domain for ypserv version 2 (YPVERS) will be * set up. This may make it a little slower to start ypbind during * boot time, but would make it easy on other domains that rely on * this binding. */ void ypbind_init_default() { char domain[256]; struct domain *cur_domain; if (getdomainname(domain, 256) == 0) { cur_domain = ypbind_point_to_domain(domain); if (cur_domain == (struct domain *)NULL) { abort(); } (void) pong_servers(cur_domain); } } bool_t xdr_ypbind_binding_2(xdrs, objp) register XDR *xdrs; ypbind_binding_2 *objp; { if (!xdr_opaque(xdrs, (char *)&(objp->ypbind_binding_addr), 4)) return (FALSE); if (!xdr_opaque(xdrs, (char *)&(objp->ypbind_binding_port), 2)) return (FALSE); return (TRUE); } bool_t xdr_ypbind_resp_2(xdrs, objp) register XDR *xdrs; ypbind_resp_2 *objp; { if (!xdr_ypbind_resptype(xdrs, &objp->ypbind_status)) return (FALSE); switch (objp->ypbind_status) { case YPBIND_FAIL_VAL: if (!xdr_u_long(xdrs, &objp->ypbind_respbody_2.ypbind_error)) return (FALSE); break; case YPBIND_SUCC_VAL: if (!xdr_ypbind_binding_2(xdrs, &objp->ypbind_respbody_2.ypbind_bindinfo)) return (FALSE); break; default: return (FALSE); } return (TRUE); } /* * The following is some caching code to improve the performance of * yp clients. In the days of yore, a client would talk to rpcbind * to get the address for ypbind, then talk to ypbind to get the * address of the server. If a lot of clients are doing this at * the same time, then rpcbind and ypbind get bogged down and clients * start to time out. * * We cache two things: the current address for ypserv, and the * transport addresses for talking to ypbind. These are saved in * files in /var/yp. To get the address of ypserv, the client opens * a file and reads the address. It does not have to talk to rpcbind * or ypbind. If this file is not available, then it can read the * the transport address for talking to ypbind without bothering * rpcbind. If this also fails, then it uses the old method of * talking to rpcbind and then ypbind. * * We lock the first byte of the cache files after writing to them. * This indicates to the client that they contents are valid. The * client should test the lock. If the lock is held, then it can * use the contents. If the lock test fails, then the contents should * be ignored. */ /* * Cache new binding information for a domain in a file. If the * new binding is the same as the old, then we skip it. We xdr * a 'ypbind_resp', which is what would be returned by a call to * the YBINDPROCP_DOMAIN service. We xdr the data because it is * easier than writing the data out field by field. It would be * nice if there were an xdrfd_create() that was similar to * xdrstdio_create(). Instead, we do an fdopen and use xdrstdio_create(). */ void cache_binding(pdom) struct domain *pdom; { int st; int fd; XDR xdrs; struct ypbind_resp resp; if (!cache_okay) return; /* if the domain doesn't have a cache file, then skip it */ if (pdom->cache_file == 0) return; /* * If we already had a cache file for this domain, remove it. If * a client just started accessing it, then it will either find * it unlocked (and not use it), or continue to use it with * old information. This is not a problem, the client will * either fail to talk to ypserv and try to bind again, or * will continue to use the old server. */ if (pdom->cache_fp) { fclose(pdom->cache_fp); /* automatically unlocks */ unlink(pdom->cache_file); pdom->cache_fp = 0; } fd = open(pdom->cache_file, O_CREAT|O_WRONLY, 0444); if (fd == -1) return; pdom->cache_fp = fdopen(fd, "w"); if (pdom->cache_fp == 0) { close(fd); return; } xdrstdio_create(&xdrs, pdom->cache_fp, XDR_ENCODE); resp.ypbind_status = YPBIND_SUCC_VAL; resp.ypbind_resp_u.ypbind_bindinfo = pdom->dom_binding; if (!xdr_ypbind_resp(&xdrs, &resp)) { xdr_destroy(&xdrs); unlink(pdom->cache_file); fclose(pdom->cache_fp); pdom->cache_fp = 0; return; } xdr_destroy(&xdrs); /* flushes xdr but leaves fp open */ /* we lock the first byte to indicate that the file is valid */ lseek(fd, 0L, SEEK_SET); st = lockf(fd, F_LOCK, 1); if (st == -1) { unlink(pdom->cache_file); fclose(pdom->cache_fp); pdom->cache_fp = 0; } } void uncache_binding(pdom) struct domain *pdom; { if (!cache_okay) return; if (pdom->cache_fp != 0) { unlink(pdom->cache_file); fclose(pdom->cache_fp); pdom->cache_fp = 0; } } /* * Cache a transport address for talking to ypbind. We convert the * transport address to a universal address and save that in a file. * The file lives in the binding directory because it does not depend * on the domain. */ void cache_transport(nconf, xprt, vers) struct netconfig *nconf; SVCXPRT *xprt; int vers; { char filename[300]; char *uaddr; int fd; int st; int len; if (!cache_okay) return; sprintf(filename, "%s/xprt.%s.%d", BINDING, nconf->nc_netid, vers); unlink(filename); /* remove any old version */ uaddr = taddr2uaddr(nconf, &xprt->xp_ltaddr); if (uaddr == 0) return; fd = open(filename, O_CREAT|O_WRONLY, 0444); /* readable by all */ if (fd == -1) { free(uaddr); return; } len = strlen(uaddr) + 1; /* include terminating null */ st = write(fd, uaddr, len); if (st != len) { close(fd); unlink(filename); free(uaddr); return; } free(uaddr); /* we lock the first byte to indicate that the file is valid */ lseek(fd, 0L, SEEK_SET); st = lockf(fd, F_LOCK, 1); if (st == -1) { close(fd); unlink(filename); } } /* * Create a file that clients can check to see if we are running. */ void cache_pid() { char filename[300]; char spid[15]; int fd; int st; int len; if (!cache_okay) return; sprintf(filename, "%s/ypbind.pid", BINDING); unlink(filename); /* remove any old version */ fd = open(filename, O_CREAT|O_WRONLY, 0444); /* readable by all */ if (fd == -1) { return; } sprintf(spid, "%d\n", getpid()); len = strlen(spid); st = write(fd, spid, len); if (st != len) { close(fd); unlink(filename); return; } /* we lock the first byte to indicate that the file is valid */ lseek(fd, 0L, SEEK_SET); st = lockf(fd, F_LOCK, 1); if (st == -1) { close(fd); unlink(filename); } /* we keep 'fd' open so that the lock will continue to be held */ } /* * We are called once at startup (when the known_domains list is empty) * to clean up left-over files. We are also called right before * exiting. In the latter case case we don't bother closing descriptors * in the entries in the domain list because they will be closed * automatically (and unlocked) when we exit. * * We ignore the cache_okay flag because it is important that we remove * all cache files (left-over files can temporarily confuse clients). */ void clean_cache() { struct domain *pdom; DIR *dir; struct dirent *dirent; char filename[300]; /* close and unlink cache files for each domain */ for (pdom = known_domains; pdom != (struct domain *)NULL; pdom = pdom->dom_pnext) { if (pdom->cache_file) unlink(pdom->cache_file); } sprintf(filename, "%s/ypbind.pid", BINDING); unlink(filename); dir = opendir(BINDING); if (dir == NULL) { /* Directory could not be opened. */ syslog(LOG_ERR, "opendir failed with [%s]", strerror(errno)); return; } while ((dirent = readdir(dir)) != 0) { if (strncmp(dirent->d_name, "xprt.", 5) == 0) { sprintf(filename, "%s/%s", BINDING, dirent->d_name); unlink(filename); rewinddir(dir); /* removing file may harm iteration */ } } closedir(dir); } /* * We only want to use the cache stuff on local file systems. * For remote file systems (e.g., NFS, the locking overhead is * worse than the overhead of loopback RPC, so the caching * wouldn't buy us anything. In addition, if the remote locking * software isn't configured before we start, then we would * block when we try to lock. * * We don't have a direct way to tell if a file system is local * or remote, so we assume it is local unless it is NFS. */ int cache_check() { int st; struct statvfs stbuf; st = statvfs(BINDING, &stbuf); if (st == -1) { syslog(LOG_ERR, "statvfs failed with [%s]", strerror(errno)); return (0); } /* we use strncasecmp to get NFS, NFS3, nfs, nfs3, etc. */ if (strncasecmp(stbuf.f_basetype, "NFS", 3) == 0) return (0); return (1); }