/* $NetBSD: arlib.c,v 1.2 2003/12/04 16:23:34 drochner Exp $ */ /* * arlib.c (C)opyright 1993 Darren Reed. All rights reserved. * This file may not be distributed without the author's permission in any * shape or form. The author takes no responsibility for any damage or loss * of property which results from the use of this software. */ #ifndef lint static char sccsid[] = "@(#)arlib.c 1.9 6/5/93 (C)opyright 1992 Darren \ Reed. ASYNC DNS"; #endif #include #include #include #include #include #include #include #include "netdb.h" #include "arpa/nameser.h" #include #include "arlib.h" #include "arplib.h" extern int errno, h_errno; static char ar_hostbuf[65], ar_domainname[65]; static char ar_dot[] = "."; static int ar_resfd = -1, ar_vc = 0; static struct reslist *ar_last, *ar_first; /* * Statistics structure. */ static struct resstats { int re_errors; int re_nu_look; int re_na_look; int re_replies; int re_requests; int re_resends; int re_sent; int re_timeouts; } ar_reinfo; static int do_query_name(/* struct resinfo *, char *, struct reslist * */); static int do_query_number(/* struct resinfo *, char *, struct reslist * */); static int ar_resend_query(/* struct reslist * */); /* * ar_init * * Initializes the various ARLIB internal varilables and related DNS * options for res_init(). * * Returns 0 or the socket opened for use with talking to name servers * if 0 is passed or ARES_INITSOCK is set. */ int ar_init(op) int op; { int ret = 0; if (op & ARES_INITLIST) { bzero(&ar_reinfo, sizeof(ar_reinfo)); ar_first = ar_last = NULL; } if (op & ARES_CALLINIT && !(_res.options & RES_INIT)) { ret = res_init(); (void)strcpy(ar_domainname, ar_dot); (void)strncat(ar_domainname, _res.defdname, sizeof(ar_domainname)-2); } if (op & ARES_INITSOCK) ret = ar_resfd = ar_open(); if (op & ARES_INITDEBG) _res.options |= RES_DEBUG; if (op == 0) ret = ar_resfd; return ret; } /* * ar_open * * Open a socket to talk to a name server with. * Check _res.options to see if we use a TCP or UDP socket. */ int ar_open() { if (ar_resfd == -1) { if (_res.options & RES_USEVC) { struct sockaddr_in *sip; int i; sip = _res.NS_ADDR_LIST; /* was _res.nsaddr_list */ ar_vc = 1; ar_resfd = socket(AF_INET, SOCK_STREAM, 0); /* * Try each name server listed in sequence until we * succeed or run out. */ while (connect(ar_resfd, (struct sockaddr *)sip++, sizeof(struct sockaddr))) { (void)close(ar_resfd); ar_resfd = -1; if (i >= _res.nscount) break; ar_resfd = socket(AF_INET, SOCK_STREAM, 0); } } else ar_resfd = socket(AF_INET, SOCK_DGRAM, 0); } if (ar_resfd >= 0) { /* Need one of these two here - and it MUST work!! */ int flags; if ((flags = fcntl(ar_resfd, F_GETFL, 0)) != -1) #ifdef O_NONBLOCK if (fcntl(ar_resfd, F_SETFL, flags|O_NONBLOCK) == -1) #else # ifdef O_NDELAY if (fcntl(ar_resfd, F_SETFL, flags|O_NDELAY) == -1) # else # ifdef FNDELAY if (fcntl(ar_resfd, F_SETFL, flags|FNDELAY) == -1) # endif # endif #endif { (void)close(ar_resfd); ar_resfd = -1; } } return ar_resfd; } /* * ar_close * * Closes and flags the ARLIB socket as closed. */ void ar_close() { (void)close(ar_resfd); ar_resfd = -1; return; } /* * ar_add_request * * Add a new DNS query to the end of the query list. */ static int ar_add_request(new) struct reslist *new; { if (!new) return -1; if (!ar_first) ar_first = ar_last = new; else { ar_last->re_next = new; ar_last = new; } new->re_next = NULL; ar_reinfo.re_requests++; return 0; } /* * ar_remrequest * * Remove a request from the list. This must also free any memory that has * been allocated for temporary storage of DNS results. * * Returns -1 if there are anyy problems removing the requested structure * or 0 if the remove is successful. */ static int ar_remrequest(old) struct reslist *old; { register struct reslist *rptr, *r2ptr; register char **s; if (!old) return -1; for (rptr = ar_first, r2ptr = NULL; rptr; rptr = rptr->re_next) { if (rptr == old) break; r2ptr = rptr; } if (!rptr) return -1; if (rptr == ar_first) ar_first = ar_first->re_next; else if (rptr == ar_last) { if (ar_last = r2ptr) ar_last->re_next = NULL; } else r2ptr->re_next = rptr->re_next; if (!ar_first) ar_last = ar_first; #ifdef ARLIB_DEBUG ar_dump_hostent("ar_remrequest:", rptr->re_he); #endif if (rptr->re_he.h_name) (void)free(rptr->re_he.h_name); if (s = rptr->re_he.h_aliases) for (; *s; s++) (void)free(*s); if (rptr->re_rinfo.ri_ptr) (void)free(rptr->re_rinfo.ri_ptr); (void)free(rptr); return 0; } /* * ar_make_request * * Create a DNS query recorded for the request being made and place it on the * current list awaiting replies. Initialization of the record with set * values should also be done. */ static struct reslist *ar_make_request(resi) register struct resinfo *resi; { register struct reslist *rptr; register struct resinfo *rp; rptr = (struct reslist *)calloc(1, sizeof(struct reslist)); rp = &rptr->re_rinfo; rptr->re_next = NULL; /* where NULL is non-zero ;) */ rptr->re_sentat = time(NULL); rptr->re_retries = _res.retry; rptr->re_sends = 1; rptr->re_resend = 1; rptr->re_timeout = rptr->re_sentat + _res.retrans; rptr->re_he.h_name = NULL; rptr->re_he.h_addrtype = AF_INET; rptr->re_he.h_aliases[0] = NULL; rp->ri_ptr = resi->ri_ptr; rp->ri_size = resi->ri_size; (void)ar_add_request(rptr); return rptr; } /* * ar_timeout * * Remove queries from the list which have been there too long without * being resolved. */ long ar_timeout(now, info, size) time_t now; char *info; int size; { register struct reslist *rptr, *r2ptr; register long next = 0; for (rptr = ar_first, r2ptr = NULL; rptr; rptr = r2ptr) { r2ptr = rptr->re_next; if (now >= rptr->re_timeout) { /* * If the timeout for the query has been exceeded, * then resend the query if we still have some * 'retry credit' and reset the timeout. If we have * used it all up, then remove the request. */ if (--rptr->re_retries <= 0) { ar_reinfo.re_timeouts++; if (info && rptr->re_rinfo.ri_ptr) bcopy(rptr->re_rinfo.ri_ptr, info, MIN(rptr->re_rinfo.ri_size, size)); (void)ar_remrequest(rptr); return now; } else { rptr->re_sends++; rptr->re_sentat = now; rptr->re_timeout = now + _res.retrans; (void)ar_resend_query(rptr); } } if (!next || rptr->re_timeout < next) next = rptr->re_timeout; } return next; } /* * ar_send_res_msg * * When sending queries to nameservers listed in the resolv.conf file, * don't send a query to every one, but increase the number sent linearly * to match the number of resends. This increase only occurs if there are * multiple nameserver entries in the resolv.conf file. * The return value is the number of messages successfully sent to * nameservers or -1 if no successful sends. */ static int ar_send_res_msg(msg, len, rcount) char *msg; int len, rcount; { register int i; int sent = 0; if (!msg) return -1; rcount = (_res.nscount > rcount) ? rcount : _res.nscount; if (_res.options & RES_PRIMARY) rcount = 1; if (ar_vc) { ar_reinfo.re_sent++; sent++; if (write(ar_resfd, msg, len) == -1) { int errtmp = errno; (void)close(ar_resfd); errno = errtmp; ar_resfd = -1; } } else for (i = 0; i < rcount; i++) { if (sendto(ar_resfd, msg, len, 0, (struct sockaddr *)&(_res.NS_ADDR_LIST[i]), sizeof(struct sockaddr_in)) == len) { ar_reinfo.re_sent++; sent++; } } return (sent) ? sent : -1; } /* * ar_find_id * * find a dns query record by the id (id is determined by dn_mkquery) */ static struct reslist *ar_find_id(id) int id; { register struct reslist *rptr; for (rptr = ar_first; rptr; rptr = rptr->re_next) if (rptr->re_id == id) return rptr; return NULL; } /* * ar_delete * * Delete a request from the waiting list if it has a data pointer which * matches the one passed. */ int ar_delete(ptr, size) char *ptr; int size; { register struct reslist *rptr; register struct reslist *r2ptr; int removed = 0; for (rptr = ar_first; rptr; rptr = r2ptr) { r2ptr = rptr->re_next; if (rptr->re_rinfo.ri_ptr && ptr && size && bcmp(rptr->re_rinfo.ri_ptr, ptr, size) == 0) { (void)ar_remrequest(rptr); removed++; } } return removed; } /* * ar_query_name * * generate a query based on class, type and name. */ static int ar_query_name(name, class, type, rptr) char *name; int class, type; struct reslist *rptr; { static char buf[MAXPACKET]; int r,s,a; HEADER *hptr; bzero(buf, sizeof(buf)); r = res_mkquery(QUERY, name, class, type, NULL, 0, NULL, buf, sizeof(buf)); if (r <= 0) { h_errno = NO_RECOVERY; return r; } hptr = (HEADER *)buf; rptr->re_id = ntohs(hptr->id); s = ar_send_res_msg(buf, r, rptr->re_sends); if (s == -1) { h_errno = TRY_AGAIN; return -1; } else rptr->re_sent += s; return 0; } /* * ar_gethostbyname * * Replacement library function call to gethostbyname(). This one, however, * doesn't return the record being looked up but just places the query in the * queue to await answers. */ int ar_gethostbyname(name, info, size) char *name; char *info; int size; { char host[65]; struct resinfo resi; register struct resinfo *rp = &resi; if (size && info) { rp->ri_ptr = (char *)malloc(size); bcopy(info, rp->ri_ptr, size); rp->ri_size = size; } else bzero((char *)rp, sizeof(resi)); ar_reinfo.re_na_look++; (void)strncpy(host, name, 64); host[64] = '\0'; return (do_query_name(rp, host, NULL)); } static int do_query_name(resi, name, rptr) struct resinfo *resi; char *name; register struct reslist *rptr; { char hname[65]; int len; len = strlen((char *)strncpy(hname, name, sizeof(hname)-1)); if (rptr && (hname[len-1] != '.')) { (void)strncat(hname, ar_dot, sizeof(hname)-len-1); /* * NOTE: The logical relationship between DNSRCH and DEFNAMES * is implies. ie no DEFNAES, no DNSRCH. */ if (_res.options & (RES_DEFNAMES|RES_DNSRCH) == (RES_DEFNAMES|RES_DNSRCH)) { if (_res.dnsrch[rptr->re_srch]) (void)strncat(hname, _res.dnsrch[rptr->re_srch], sizeof(hname) - ++len -1); } else if (_res.options & RES_DEFNAMES) (void)strncat(hname, ar_domainname, sizeof(hname) - len -1); } /* * Store the name passed as the one to lookup and generate other host * names to pass onto the nameserver(s) for lookups. */ if (!rptr) { rptr = ar_make_request(resi); rptr->re_type = T_A; (void)strncpy(rptr->re_name, name, sizeof(rptr->re_name)-1); } return (ar_query_name(hname, C_IN, T_A, rptr)); } /* * ar_gethostbyaddr * * Generates a query for a given IP address. */ int ar_gethostbyaddr(addr, info, size) char *addr; char *info; int size; { struct resinfo resi; register struct resinfo *rp = &resi; if (size && info) { rp->ri_ptr = (char *)malloc(size); bcopy(info, rp->ri_ptr, size); rp->ri_size = size; } else bzero((char *)rp, sizeof(resi)); ar_reinfo.re_nu_look++; return (do_query_number(rp, addr, NULL)); } /* * do_query_number * * Use this to do reverse IP# lookups. */ static int do_query_number(resi, numb, rptr) struct resinfo *resi; char *numb; register struct reslist *rptr; { register unsigned char *cp; static char ipbuf[32]; /* * Generate name in the "in-addr.arpa" domain. No addings bits to this * name to get more names to query!. */ cp = (unsigned char *)numb; (void)sprintf(ipbuf,"%u.%u.%u.%u.in-addr.arpa.", (unsigned int)(cp[3]), (unsigned int)(cp[2]), (unsigned int)(cp[1]), (unsigned int)(cp[0])); if (!rptr) { rptr = ar_make_request(resi); rptr->re_type = T_PTR; rptr->re_he.h_length = sizeof(struct in_addr); bcopy(numb, (char *)&rptr->re_addr, rptr->re_he.h_length); bcopy(numb, (char *)&rptr->re_he.h_addr_list[0].s_addr, rptr->re_he.h_length); } return (ar_query_name(ipbuf, C_IN, T_PTR, rptr)); } /* * ar_resent_query * * resends a query. */ static int ar_resend_query(rptr) struct reslist *rptr; { if (!rptr->re_resend) return -1; switch(rptr->re_type) { case T_PTR: ar_reinfo.re_resends++; return do_query_number(NULL, &rptr->re_addr, rptr); case T_A: ar_reinfo.re_resends++; return do_query_name(NULL, rptr->re_name, rptr); default: break; } return -1; } /* * ar_procanswer * * process an answer received from a nameserver. */ static int ar_procanswer(rptr, hptr, buf, eob) struct reslist *rptr; char *buf, *eob; HEADER *hptr; { char *cp, **alias, *s; int class, type, dlen, len, ans = 0, n, i; u_int32_t ttl, dr, *adr; struct hent *hp; cp = buf + sizeof(HEADER); adr = (u_int32_t *)rptr->re_he.h_addr_list; while (*adr) adr++; alias = rptr->re_he.h_aliases; while (*alias) alias++; hp = &rptr->re_he; /* * Skip over the original question. */ while (hptr->qdcount-- > 0) cp += dn_skipname(cp, eob) + QFIXEDSZ; /* * proccess each answer sent to us. blech. */ while (hptr->ancount-- > 0 && cp < eob) { n = dn_expand(buf, eob, cp, ar_hostbuf, sizeof(ar_hostbuf)); cp += n; if (n <= 0) return ans; ans++; /* * 'skip' past the general dns crap (ttl, class, etc) to get * the pointer to the right spot. Some of thse are actually * useful so its not a good idea to skip past in one big jump. */ type = (int)_getshort(cp); cp += sizeof(short); class = (int)_getshort(cp); cp += sizeof(short); ttl = (u_int32_t)_getlong(cp); cp += sizeof(u_int32_t); dlen = (int)_getshort(cp); cp += sizeof(short); rptr->re_type = type; switch(type) { case T_A : rptr->re_he.h_length = dlen; if (ans == 1) rptr->re_he.h_addrtype=(class == C_IN) ? AF_INET : AF_UNSPEC; if (dlen != sizeof(dr)) { h_errno = TRY_AGAIN; continue; } bcopy(cp, &dr, dlen); *adr++ = dr; *adr = 0; cp += dlen; len = strlen(ar_hostbuf); if (!rptr->re_he.h_name) { rptr->re_he.h_name = (char *)malloc(len+1); if (!rptr->re_he.h_name) break; (void)strcpy(rptr->re_he.h_name, ar_hostbuf); } break; case T_PTR : if ((n = dn_expand(buf, eob, cp, ar_hostbuf, sizeof(ar_hostbuf) )) < 0) { cp += n; continue; } cp += n; len = strlen(ar_hostbuf)+1; /* * copy the returned hostname into the host name * or alias field if there is a known hostname * already. */ if (!rptr->re_he.h_name) { rptr->re_he.h_name = (char *)malloc(len); if (!rptr->re_he.h_name) break; (void)strcpy(rptr->re_he.h_name, ar_hostbuf); } else { *alias = (char *)malloc(len); if (!*alias) return -1; (void)strcpy(*alias++, ar_hostbuf); *alias = NULL; } break; case T_CNAME : cp += dlen; if (alias >= &(rptr->re_he.h_aliases[MAXALIASES-1])) continue; n = strlen(ar_hostbuf)+1; *alias = (char *)malloc(n); if (!*alias) return -1; (void)strcpy(*alias++, ar_hostbuf); *alias = NULL; break; default : break; } } return ans; } /* * ar_answer * * Get an answer from a DNS server and process it. If a query is found to * which no answer has been given to yet, copy its 'info' structure back * to where "reip" points and return a pointer to the hostent structure. */ struct hostent *ar_answer(reip, size) char *reip; int size; { static char ar_rcvbuf[sizeof(HEADER) + MAXPACKET]; static struct hostent ar_host; register HEADER *hptr; register struct reslist *rptr = NULL; register struct hostent *hp; register char **s; unsigned long *adr; int rc, i, n, a; rc = recv(ar_resfd, ar_rcvbuf, sizeof(ar_rcvbuf), 0); if (rc <= 0) goto getres_err; ar_reinfo.re_replies++; hptr = (HEADER *)ar_rcvbuf; /* * convert things to be in the right order. */ hptr->id = ntohs(hptr->id); hptr->ancount = ntohs(hptr->ancount); hptr->arcount = ntohs(hptr->arcount); hptr->nscount = ntohs(hptr->nscount); hptr->qdcount = ntohs(hptr->qdcount); /* * response for an id which we have already received an answer for * just ignore this response. */ rptr = ar_find_id(hptr->id); if (!rptr) goto getres_err; if ((hptr->rcode != NOERROR) || (hptr->ancount == 0)) { switch (hptr->rcode) { case NXDOMAIN: h_errno = HOST_NOT_FOUND; break; case SERVFAIL: h_errno = TRY_AGAIN; break; case NOERROR: h_errno = NO_DATA; break; case FORMERR: case NOTIMP: case REFUSED: default: h_errno = NO_RECOVERY; break; } ar_reinfo.re_errors++; /* ** If a bad error was returned, we stop here and dont send ** send any more (no retries granted). */ if (h_errno != TRY_AGAIN) { rptr->re_resend = 0; rptr->re_retries = 0; } goto getres_err; } a = ar_procanswer(rptr, hptr, ar_rcvbuf, ar_rcvbuf+rc); if ((rptr->re_type == T_PTR) && (_res.options & RES_CHECKPTR)) { /* * For reverse lookups on IP#'s, lookup the name that is given * for the ip# and return with that as the official result. * -avalon */ rptr->re_type = T_A; /* * Clean out the list of addresses already set, even though * there should only be one :) */ adr = (unsigned long *)rptr->re_he.h_addr_list; while (*adr) *adr++ = 0L; /* * Lookup the name that we were given for the ip# */ ar_reinfo.re_na_look++; (void)strncpy(rptr->re_name, rptr->re_he.h_name, sizeof(rptr->re_name)-1); rptr->re_he.h_name = NULL; rptr->re_retries = _res.retry; rptr->re_sends = 1; rptr->re_resend = 1; rptr->re_he.h_name = NULL; ar_reinfo.re_na_look++; (void)ar_query_name(rptr->re_name, C_IN, T_A, rptr); return NULL; } if (reip && rptr->re_rinfo.ri_ptr && size) bcopy(rptr->re_rinfo.ri_ptr, reip, MIN(rptr->re_rinfo.ri_size, size)); /* * Clean up structure from previous usage. */ hp = &ar_host; #ifdef ARLIB_DEBUG ar_dump_hostent("ar_answer: previous usage", hp); #endif if (hp->h_name) (void)free(hp->h_name); if (s = hp->h_aliases) { while (*s) (void)free(*s++); (void)free(hp->h_aliases); } if (s = hp->h_addr_list) { /* * Only free once since we allocated space for * address in one big chunk. */ (void)free(*s); (void)free(hp->h_addr_list); } bzero((char *)hp, sizeof(*hp)); /* * Setup and copy details for the structure we return a pointer to. */ hp->h_addrtype = AF_INET; hp->h_length = sizeof(struct in_addr); if(rptr->re_he.h_name) { hp->h_name = (char *)malloc(strlen(rptr->re_he.h_name)+1); if(!hp->h_name) { #ifdef ARLIB_DEBUG fprintf(stderr, "no memory for hostname\n"); #endif h_errno = TRY_AGAIN; goto getres_err; } (void)strcpy(hp->h_name, rptr->re_he.h_name); } #ifdef ARLIB_DEBUG ar_dump_hostent("ar_answer: (snap) store name", hp); #endif /* * Count IP#'s. */ for (i = 0, n = 0; i < MAXADDRS; i++, n++) if (!rptr->re_he.h_addr_list[i].s_addr) break; s = hp->h_addr_list = (char **)malloc((n + 1) * sizeof(char *)); if (n) { *s = (char *)malloc(n * sizeof(struct in_addr)); if(!*s) { #ifdef ARLIB_DEBUG fprintf(stderr, "no memory for IP#'s (%d)\n", n); #endif h_errno = TRY_AGAIN; goto getres_err; } bcopy((char *)&rptr->re_he.h_addr_list[0].s_addr, *s, sizeof(struct in_addr)); s++; for (i = 1; i < n; i++, s++) { *s = hp->h_addr + i * sizeof(struct in_addr); bcopy((char *)&rptr->re_he.h_addr_list[i].s_addr, *s, sizeof(struct in_addr)); } } *s = NULL; #ifdef ARLIB_DEBUG ar_dump_hostent("ar_answer: (snap) store IP#'s", hp); #endif /* * Count CNAMEs */ for (i = 0, n = 0; i < MAXADDRS; i++, n++) if (!rptr->re_he.h_aliases[i]) break; s = hp->h_aliases = (char **)malloc((n + 1) * sizeof(char *)); if (!s) { #ifdef ARLIB_DEBUG fprintf(stderr, "no memory for aliases (%d)\n", n); #endif h_errno = TRY_AGAIN; goto getres_err; } for (i = 0; i < n; i++) { *s++ = rptr->re_he.h_aliases[i]; rptr->re_he.h_aliases[i] = NULL; } *s = NULL; #ifdef ARLIB_DEBUG ar_dump_hostent("ar_answer: (snap) store CNAMEs", hp); ar_dump_hostent("ar_answer: new one", hp); #endif if (a > 0) (void)ar_remrequest(rptr); else if (!rptr->re_sent) (void)ar_remrequest(rptr); return hp; getres_err: if (rptr) { if (reip && rptr->re_rinfo.ri_ptr && size) bcopy(rptr->re_rinfo.ri_ptr, reip, MIN(rptr->re_rinfo.ri_size, size)); if ((h_errno != TRY_AGAIN) && (_res.options & (RES_DNSRCH|RES_DEFNAMES) == (RES_DNSRCH|RES_DEFNAMES) )) if (_res.dnsrch[rptr->re_srch]) { rptr->re_retries = _res.retry; rptr->re_sends = 1; rptr->re_resend = 1; (void)ar_resend_query(rptr); rptr->re_srch++; } return NULL; } return NULL; } #ifdef ARLIB_DEBUG void ar_dump_hostent(prefix, hp) char *prefix; struct hostent *hp; { register char **s; fflush(stdout); fprintf(stderr, "%s\n", prefix); fprintf(stderr, " hp %p\n", hp); fprintf(stderr, " h_name %p '%s'\n", hp->h_name, hp->h_name); if (s = hp->h_aliases) { fprintf(stderr, " h_aliases %p\n", hp->h_aliases); while (*s) { fprintf(stderr, " element %p\n", *s); s++; } } if (s = hp->h_addr_list) { fprintf(stderr, " h_addr_list %p\n", hp->h_addr_list); while (*s) { fprintf(stderr, " element %p\n", *s); s++; } } fflush(stderr); } void ar_dump_reslist(FILE* fp) { register struct reslist *rptr; int c; c = 0; for (rptr = ar_first; rptr; rptr = rptr->re_next) { fprintf(fp, "%4d [%p] %4d [%p]: %s\n", rptr->re_id, rptr, *(rptr->re_rinfo.ri_ptr), rptr->re_rinfo.ri_ptr, rptr->re_name); } } #endif