#if !defined(lint) && defined(SCCSIDS) static char sccsid[] = "@(#)yp_bind.c 1.1 94/10/31 Copyr 1990 Sun Micro"; #endif #ifdef sun #define MAP_STUFF 1 #endif #include #include #include #include #include #include #include #include #include #include #include "yp_prot.h" #include "ypv1_prot.h" #include "ypclnt.h" #define BFSIZE (YPMAXDOMAIN + 32) /* size of binding file */ /* This should match the one in ypbind.c */ #define CACHE_DIR "/var/yp/binding" extern int errno; extern int sleep(); extern malloc_t malloc(); extern char *strcpy(); static void _yp_unbind(); enum bind_status { BS_BAGIT, BS_RETRY, BS_OK }; bool check_pmap_up(); bool check_binder_up(); enum bind_status talk2_pmap(); enum bind_status talk2_binder(); void talk2_server(); enum bind_status get_binder_port(); bool check_binding(); void newborn(); struct dom_binding *load_dom_binding(); /* * Time parameters when talking to the ypbind and pmap processes */ #define YPSLEEPTIME 5 /* Time to sleep between tries */ unsigned int _ypsleeptime = YPSLEEPTIME; #define YPBIND_TIMEOUT 30 /* Total seconds for timeout */ #define YPBIND_INTER_TRY 30 /* Seconds between tries */ static struct timeval bind_intertry = { YPBIND_INTER_TRY, /* Seconds */ 0 /* Microseconds */ }; static struct timeval bind_timeout = { YPBIND_TIMEOUT, /* Seconds */ 0 /* Microseconds */ }; /* * Time parameters when talking to the ypserv process */ #ifdef DEBUG #define YPTIMEOUT 120 /* Total seconds for timeout */ #define YPINTER_TRY 60 /* Seconds between tries */ #else #define YPTIMEOUT 90 /* Total seconds for timeout */ #define YPINTER_TRY 5 /* Seconds between tries */ #endif #define MAX_TRIES_FOR_NEW_YP 2 /* Number of times we'll try to get * a new YP server before we'll * settle for an old one. */ static struct timeval ypserv_intertry = { YPINTER_TRY, /* Seconds */ 0 /* Microseconds */ }; struct timeval _ypserv_timeout = { YPTIMEOUT, /* Seconds */ 0 /* Microseconds */ }; static struct in_addr my_addr; /* Local internet addr */ static struct dom_binding *bound_domains; /* List of bound domains */ static char *default_domain; static char bfinvalid; /* Binding file invalid */ static char *bfname; #ifdef MAP_STUFF #include static u_short *binderfilemap; /* Mapped version of binder */ #define MAP_LEN (2+sizeof(struct ypbind_resp)) /* just for current udp only*/ #endif static int bf; /* Binding file fd */ /* * binder_port holds what we believe to be the local port for ypbind. It is * set only by talk2_pmap. It is cleared (set to 0) by: * 1. talk2_pmap: always upon entry. * 2. check_binder_up if: * - It can't create a socket to speak to the binder. * - If it fails to bind to the port. * 3. talk2_binder if there are RPC errors when trying to use the port. */ static unsigned long binder_port; /* Initialize to "no port" */ /* * Attempts to locate a NIS server that serves a passed domain. If * one is found, an entry is created on the static list of domain-server pairs * pointed to by cell bound_domains, a udp path to the server is created and * the function returns 0. Otherwise, the function returns a defined errorcode * YPERR_xxxx. */ int _yp_dobind(domain, binding) char *domain; struct dom_binding **binding; /* if result == 0, ptr to dom_binding */ { return (_yp_dobind_soft(domain,binding, -1)); } int _yp_dobind_soft(domain, binding,usertries) char *domain; struct dom_binding **binding; /* if result == 0, ptr to dom_binding */ int usertries; { struct dom_binding *pdomb; /* Ptr to new domain binding */ struct sockaddr_in ypbinder; /* To talk with ypbinder */ char *pdomain; /* For xdr interface */ struct ypbind_resp ypbind_resp; /* Response from local ypbinder */ int vers; /* ypbind program version number */ int tries; /* Number of times we've tried with * the current protocol */ int gbtries; /* Number of times we've tried to * get the binder */ int status; u_short b_port; enum bind_status loopctl; bool bound; bool oldport = FALSE; #ifdef MAP_STUFF char tmpbfname[BFSIZE]; #endif if ( (domain == NULL) ||(strlen(domain) == 0) ) { return (YPERR_BADARGS); } newborn(); if (bfname==0){ bfname=(char *)calloc(1,BFSIZE); } if (bfname==0){ bfinvalid=TRUE; } if (!bfinvalid) { #ifdef MAP_STUFF sprintf(tmpbfname,"%s/%s.%d",CACHE_DIR,domain,YPVERS); if (strcmp(bfname,tmpbfname)==0){ /*same name*/ if (binderfilemap){ binder_port = binderfilemap[0]; } } else { bfname[0]=0; if (binderfilemap){ munmap(binderfilemap,MAP_LEN); binderfilemap=0; } if ((bf = open(tmpbfname,O_RDONLY)) != -1) { if (flock(bf,LOCK_EX+LOCK_NB) == 0) { close(bf); bf = -1; /* Not valid if we would lock it */ } else { binderfilemap=(u_short *)mmap(bfname,MAP_LEN,PROT_READ,MAP_SHARED,bf,0); if ((int)binderfilemap == -1) binderfilemap=0; if (binderfilemap){ strcpy(bfname,tmpbfname); binder_port = binderfilemap[0]; close(bf); } else { close(bf); bf= -1; /*map failed*/ } } } } #else sprintf(bfname,"%s/%s.%d",CACHE_DIR,domain,YPVERS); if ((bf = open(bfname,O_RDONLY)) != -1) { if (flock(bf,LOCK_EX+LOCK_NB) == 0) { close(bf); bf = -1; /* Not valid if we would lock it */ } else { read(bf,&b_port,sizeof(u_short)); binder_port = b_port; close(bf); } } #endif } if (check_binding(domain, binding) ) return (0); /* We are bound */ /* * Use loopback address. */ my_addr.s_addr = INADDR_LOOPBACK; pdomain = domain; /* * Try to get the binder's port, using the current program version. * The version may be changed to the old version, deep in the bowels * of talk2_binder. */ for (bound = FALSE, vers = YPBINDVERS; !bound; ) { if (binder_port) { oldport = TRUE; } else { oldport = FALSE; /* * Get the binder's port. We'll loop as long as * get_binder_port returns BS_RETRY. */ for (gbtries=0,loopctl = BS_RETRY; loopctl != BS_OK;gbtries++ ) { if ((usertries>=0) && gbtries >=usertries) return (status); switch (loopctl = get_binder_port(vers, &status) ) { case BS_BAGIT: return (status); case BS_OK: break; } } } /* * See whether ypbind is up. If no, bag it if it's a * resource error, or if we are using a port we just got * from the port mapper. Otherwise loop around to try to * get a valid port. */ if (!check_binder_up(&ypbinder, &status)) { if (status == YPERR_RESRC) { return (status); } if (!oldport && status == YPERR_YPBIND) { return (status); } continue; } /* * At this point, we think we know how to talk to the * binder, and the binder is apparently alive. Until we * succeed in binding the domain, or we know we can't ever * bind the domain, we will try forever. This loops when * talk2_binder returns BS_RETRY, and terminates when * talk2_binder returns BS_BAGIT, or BS_OK. If binder_port * gets cleared, we will not execute this loop again, but * will go to the top of the enclosing loop to try to get * the binder's port again. It is never the case that both * talk2_binder returns BS_OK and that it clears the * binder_port. */ for (loopctl = BS_RETRY, tries = 1; binder_port && (loopctl != BS_OK); tries++) { switch (loopctl = talk2_binder(&ypbinder, &vers, tries, &pdomain, &ypbind_resp, &status) ) { case BS_BAGIT: return (status); case BS_OK: bound = TRUE; case BS_RETRY: /* switch back to VERS after 1 try to OLDVERS */ if (tries > MAX_TRIES_FOR_NEW_YP) { vers = YPBINDVERS; tries = 0; } } if ((usertries>=0) && tries >=usertries) return (status); } } if ( (pdomb = load_dom_binding(&ypbind_resp, vers, domain, &status) ) == (struct dom_binding *) NULL) { return (status); } if (vers == YPBINDOLDVERS) { talk2_server(pdomb); } *binding = pdomb; /* Return ptr to the binding * entry */ return (0); /* This is the go path */ } /* * This is a "wrapper" function for _yp_dobind_soft for vanilla user-level * functions which neither know nor care about struct dom_bindings. */ int yp_bind(domain) char *domain; { struct dom_binding *binding; return (_yp_dobind(domain, &binding) ); } /* * This is a "wrapper" function for _yp_dobind_soft for vanilla user-level * functions which neither know nor care about struct dom_bindings. */ int yp_softbind(domain,tries) char *domain; int tries; { struct dom_binding *binding; return (_yp_dobind_soft(domain, &binding, tries) ); } /* * Attempts to find a dom_binding in the list at bound_domains having the * domain name field equal to the passed domain name, and removes it if found. * The domain-server binding will not exist after the call to this function. * All resources associated with the binding will be freed. */ void yp_unbind (domain) char *domain; { _yp_unbind(domain, TRUE); } static void _yp_unbind (domain, invalidate) char *domain; bool_t invalidate; /*the binding file*/ { struct dom_binding *pdomb; struct dom_binding *ptrail = 0; struct sockaddr_in local_name; int local_name_len; if ( (domain == NULL) ||(strlen(domain) == 0) ) { return; } if (invalidate) bfinvalid = TRUE; /* Don't use binding file again */ for (pdomb = bound_domains; pdomb != NULL; ptrail = pdomb, pdomb = pdomb->dom_pnext) { if (strcmp(domain, pdomb->dom_domain) == 0) { clnt_destroy(pdomb->dom_client); if (pdomb == bound_domains) { bound_domains = pdomb->dom_pnext; } else { ptrail->dom_pnext = pdomb->dom_pnext; } free((char *) pdomb); break; } } } static char * _default_domain() { char temp[256]; if (default_domain) return (default_domain); if (getdomainname(temp, sizeof(temp))) return (0); if (strlen(temp) > 0) { default_domain = (char *)malloc(strlen(temp)+1); if (default_domain == 0) return (0); strcpy(default_domain, temp); return (default_domain); } return (0); } /* * This is a wrapper for the system call getdomainname which returns a * ypclnt.h error code in the failure case. It also checks to see that * the domain name is non-null, knowing that the null string is going to * get rejected elsewhere in the NIS client package. */ int yp_get_default_domain(domain) char **domain; { if ((*domain = _default_domain()) != 0) return (0); return (YPERR_YPERR); } /* * This checks to see if this is a new process incarnation which has * inherited bindings from a parent, and unbinds the world if so. */ static void newborn() { static long int mypid; /* Cached to detect forks */ long int testpid; if ((testpid = getpid() ) != mypid) { mypid = testpid; while (bound_domains) { _yp_unbind(bound_domains->dom_domain, FALSE); /*do not unmap the file*/ } } } /* * This checks that the socket for a domain which has already been bound * hasn't been closed or changed under us. If it has, unbind the domain * without closing the socket, which may be in use by some higher level * code. This returns TRUE and points the binding parameter at the found * dom_binding if the binding is found and the socket looks OK, and FALSE * otherwise. */ static bool check_binding(domain, binding) char *domain; struct dom_binding **binding; { struct dom_binding *pdomb; struct sockaddr_in local_name; int local_name_len; for (pdomb = bound_domains; pdomb != NULL; pdomb = pdomb->dom_pnext) { if (strcmp(domain, pdomb->dom_domain) == 0) { local_name_len = sizeof(local_name); if ((getsockname(pdomb->dom_socket, (struct sockaddr *) &local_name, &local_name_len) != 0) || (local_name.sin_family != AF_INET) || (local_name.sin_port != pdomb->dom_local_port) ) { yp_unbind(domain); break; } else { *binding = pdomb; return (TRUE); } } } return (FALSE); } /* * This checks whether the portmapper is up. If the connect fails, the * portmapper is dead. As a side effect, the pmapper sockaddr_in is * initialized. The connection is not used for anything else, so it is * immediately closed. * * If there is a valid binding file we assume pmap is up. */ static bool check_pmap_up(pmapper, err) struct sockaddr_in *pmapper; int *err; { int sokt; int status; pmapper->sin_addr = my_addr; pmapper->sin_family = AF_INET; pmapper->sin_port = PMAPPORT; bzero(pmapper->sin_zero, 8); if (bf == -1) { /* No valid binding */ sokt = socket(AF_INET, SOCK_STREAM, 0); if (sokt == -1) { *err = YPERR_RESRC; return (FALSE); } if ((status = connect(sokt, (struct sockaddr *) pmapper, sizeof(struct sockaddr_in))) < 0) { (void) close(sokt); *err = YPERR_PMAP; return (FALSE); } (void) close(sokt); } return (TRUE); } /* * This checks whether ypbind is up. If the bind succeeds, ypbind is dead, * because the binder port returned from portmap should already be in use. * There are two side effects. The ypbind sockaddr_in is initialized. If * the function returns FALSE, the global binder_port will be set to 0. */ static bool check_binder_up(ypbinder, err) struct sockaddr_in *ypbinder; int *err; { int sokt; int status; if (binder_port == 0) { return (FALSE); } ypbinder->sin_addr = my_addr; ypbinder->sin_family = AF_INET; ypbinder->sin_port = htons(binder_port); bzero(ypbinder->sin_zero, 8); sokt = socket(AF_INET, SOCK_DGRAM, 0); /* Throw-away socket */ if (sokt == -1) { binder_port = 0; *err = YPERR_RESRC; return (FALSE); } errno = 0; status = bind(sokt, (struct sockaddr *) ypbinder, sizeof(struct sockaddr_in)); (void) close(sokt); if (status == -1) { if (errno == EADDRINUSE) { /* * since we cannot grab the port, ypbind must be * up and healthy. */ return (TRUE); } if ((errno == EACCES) && (binder_port < IPPORT_RESERVED) && (geteuid() != 0)) { /* * The port is priviledged. This must be "secure" * unix where have to use the yelow pages. */ return (TRUE); } } binder_port = 0; *err = YPERR_YPBIND; return (FALSE); } /* * This asks the portmapper for addressing info for ypbind speaking a passed * program version number. If it gets that info, the port number is stashed * in binder_port, but binder_port will be set to 0 when talk2_pmap returns * anything except BS_OK. If the RPC call to the portmapper failed, the * current process will be put to sleep for _ypsleeptime seconds before * this function returns. */ static enum bind_status talk2_pmap(pmapper, vers, err) struct sockaddr_in *pmapper; int vers; int *err; { int sokt; CLIENT *client; struct pmap portmap; enum clnt_stat clnt_stat; binder_port = 0; portmap.pm_prog = YPBINDPROG; portmap.pm_vers = vers; portmap.pm_prot = IPPROTO_UDP; portmap.pm_port = 0; /* Don't care */ sokt = RPC_ANYSOCK; if ((client = clntudp_bufcreate(pmapper, PMAPPROG, PMAPVERS, bind_intertry, &sokt, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE)) == NULL) { *err = YPERR_RPC; return (BS_BAGIT); } clnt_stat = (enum clnt_stat) clnt_call(client, PMAPPROC_GETPORT, xdr_pmap, &portmap, xdr_u_long, &binder_port, bind_timeout); clnt_destroy(client); (void) close(sokt); if (clnt_stat == RPC_SUCCESS) { if (binder_port != 0) { return (BS_OK); } else { *err = YPERR_YPBIND; return (BS_BAGIT); } } else { (void) sleep(_ypsleeptime); *err = YPERR_RPC; return (BS_RETRY); } } /* * This talks to the local ypbind process, and asks for a binding for the * passed domain. As a side effect, if a version mismatch is detected, the * ypbind program version number may be changed to the old version. In the * success case, the ypbind response will be returned as it was loaded by * ypbind - that is, containing a valid binding. If the RPC call to ypbind * failed, the current process will be put to sleep for _ypsleeptime seconds * before this function returns. */ static enum bind_status talk2_binder(ypbinder, vers, tries, ppdomain, ypbind_resp, err) struct sockaddr_in *ypbinder; int *vers; int tries; char **ppdomain; struct ypbind_resp *ypbind_resp; int *err; { int sokt; CLIENT *client; enum clnt_stat clnt_stat; if (bfname==0) bfinvalid=TRUE; if ((bf != -1) && !bfinvalid) { /* this should work but we check anyway */ #ifdef MAP_STUFF if (binderfilemap){ bcopy( &binderfilemap[1],ypbind_resp,sizeof(struct ypbind_resp)); clnt_stat = RPC_SUCCESS; } else{ #endif bf = open(bfname,O_RDONLY); if (bf != -1) { if (flock(bf,LOCK_EX+LOCK_NB) != 0) { lseek(bf,(long)(sizeof(u_short)),L_SET); read(bf,ypbind_resp,sizeof(struct ypbind_resp)); close(bf); clnt_stat = RPC_SUCCESS; } } #ifdef MAP_STUFF } #endif } else { sokt = RPC_ANYSOCK; if ((*vers == YPBINDVERS) && (tries > MAX_TRIES_FOR_NEW_YP) ) *vers = YPBINDOLDVERS; if ((client = clntudp_bufcreate(ypbinder, YPBINDPROG, *vers, bind_intertry, &sokt, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE)) == NULL) { *err = YPERR_RPC; return (BS_BAGIT); } clnt_stat = (enum clnt_stat)clnt_call(client, YPBINDPROC_DOMAIN, xdr_ypdomain_wrap_string, ppdomain, xdr_ypbind_resp, ypbind_resp, bind_timeout); clnt_destroy(client); (void) close(sokt); bfinvalid = !(clnt_stat == RPC_SUCCESS); } if (clnt_stat == RPC_SUCCESS) { if (ypbind_resp->ypbind_status == YPBIND_SUCC_VAL) { /* Binding successfully returned from ypbind */ return (BS_OK); } else { if ( ((*vers == YPBINDVERS) && (tries < MAX_TRIES_FOR_NEW_YP)) || (*vers == YPBINDOLDVERS) ) { (void) sleep(_ypsleeptime); } bfinvalid = TRUE; *err = YPERR_DOMAIN; return (BS_RETRY); } } else { bfinvalid = TRUE; if (clnt_stat == RPC_PROGVERSMISMATCH) { if (*vers == YPBINDOLDVERS) { *err = YPERR_YPBIND; return (BS_BAGIT); } else { *vers = YPBINDOLDVERS; } } else { (void) sleep(_ypsleeptime); binder_port = 0; } *err = YPERR_RPC; return (BS_RETRY); } } /* * This handles all the conversation with the portmapper to find the port * ypbind is listening on. If binder_port is already non-zero, this returns * BS_OK immediately without changing anything. */ static enum bind_status get_binder_port(vers, err) int vers; /* !ypbind! program version number */ int *err; { struct sockaddr_in pmapper; if (binder_port) { return (BS_OK); } if (!check_pmap_up(&pmapper, err) ) { return (BS_BAGIT); } return (talk2_pmap(&pmapper, vers, err) ); } /* * This allocates some memory for a domain binding, initialize it, and * returns a pointer to it. Based on the program version we ended up * talking to ypbind with, fill out an opvector of appropriate protocol * modules. */ static struct dom_binding * load_dom_binding(ypbind_resp, vers, domain, err) struct ypbind_resp *ypbind_resp; int vers; char *domain; int *err; { struct dom_binding *pdomb; struct sockaddr_in dummy; /* To get a port bound to socket */ struct sockaddr_in local_name; int local_name_len = sizeof(struct sockaddr_in); pdomb = (struct dom_binding *) NULL; if ((pdomb = (struct dom_binding *) malloc(sizeof(struct dom_binding))) == NULL) { (void) syslog(LOG_ERR, "load_dom_binding: malloc failure."); *err = YPERR_RESRC; return (struct dom_binding *) (NULL); } pdomb->dom_server_addr.sin_addr = ypbind_resp->ypbind_respbody.ypbind_bindinfo.ypbind_binding_addr; pdomb->dom_server_addr.sin_family = AF_INET; pdomb->dom_server_addr.sin_port = ypbind_resp->ypbind_respbody.ypbind_bindinfo.ypbind_binding_port; bzero(pdomb->dom_server_addr.sin_zero, 8); pdomb->dom_server_port = ypbind_resp->ypbind_respbody.ypbind_bindinfo.ypbind_binding_port; pdomb->dom_socket = RPC_ANYSOCK; pdomb->dom_vers = (vers == YPBINDOLDVERS) ? YPOLDVERS : YPVERS; /* * Open up a udp path to the server, which will remain active globally. */ if ((pdomb->dom_client = clntudp_bufcreate(&(pdomb->dom_server_addr), YPPROG, ((vers == YPBINDVERS) ? YPVERS : YPOLDVERS) , ypserv_intertry, &(pdomb->dom_socket), RPCSMALLMSGSIZE, YPMSGSZ)) == NULL) { free((char *) pdomb); *err = YPERR_RPC; return (struct dom_binding *) (NULL); } /* * Bind the socket to a bogus address so a port gets allocated for * the socket, but so that sendto will still work. */ (void) fcntl(pdomb->dom_socket, F_SETFD, 1); bzero((char *)&dummy, sizeof (dummy)); dummy.sin_family = AF_INET; (void)bind(pdomb->dom_socket, (struct sockaddr *)&dummy, sizeof(dummy)); /* * Remember the bound port number */ if (getsockname(pdomb->dom_socket, (struct sockaddr *) &local_name, &local_name_len) == 0) { pdomb->dom_local_port = local_name.sin_port; } else { free((char *) pdomb); *err = YPERR_YPERR; return (struct dom_binding *) (NULL); } (void) strcpy(pdomb->dom_domain, domain);/* Remember the domain name */ pdomb->dom_pnext = bound_domains; /* Link this to the list as */ bound_domains = pdomb; /* ... the head entry */ return (pdomb); } /* * This checks to see if a ypserv which we know speaks v1 NIS program number * also speaks v2 version. This makes the assumption that the NIS service at * the node is supplied by a single process, and that RPC will deliver a * message for a different program version number than that which the server * regestered. */ static void talk2_server(pdomb) struct dom_binding *pdomb; { int sokt; CLIENT *client; enum clnt_stat clnt_stat; sokt = RPC_ANYSOCK; if ((client = clntudp_bufcreate(&(pdomb->dom_server_addr), YPPROG, YPVERS, ypserv_intertry, &sokt, RPCSMALLMSGSIZE, YPMSGSZ)) == NULL) { return; } clnt_stat = (enum clnt_stat) clnt_call(client, YPBINDPROC_NULL, xdr_void, 0, xdr_void, 0, _ypserv_timeout); if (clnt_stat == RPC_SUCCESS) { clnt_destroy(pdomb->dom_client); (void) close(pdomb->dom_socket); pdomb->dom_client = client; pdomb->dom_socket = sokt; pdomb->dom_vers = YPVERS; } else { clnt_destroy(client); (void) close(sokt); } } /* * Awful utility function that other libc routines no longer call to * see that are getting data from a NIS map. * using it tends to double the NIS traffic on the average * changed to not call yp_match any more. */ int usingypmap(ddn, map) char **ddn; /* the default domainname set by this routine */ char *map; /* the map we are interested in. */ { char *key = NULL, *outval = NULL; int keylen, outvallen, stat; if (_default_domain() == 0) return (FALSE); /* does the map exist ? */ stat = yp_first(*ddn=default_domain, map, &key, &keylen, &outval, &outvallen); if (outval != NULL) free(outval); if (key != NULL) free(key); return (yp_ismapthere(stat)); } /* * Handy utility function that other libc routines can call to * see that are getting data from a NIS map. * e.g. if a yp_match fails and the stat is checked by this * routine FALSE is a fatal error and you should stop using NIS * TRUE means that the map exists and you should return * the error. */ int yp_ismapthere(stat) int stat; { switch (stat) { case 0: /* it actually succeeded! */ case YPERR_KEY: /* no such key in map */ case YPERR_NOMORE: case YPERR_BUSY: return (TRUE); } return (FALSE); }