/* * src/res.c (C)opyright 1992 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. * * $Id: res.c 1490 2006-02-22 14:35:50Z rubin $ * * July 1999 - Rewrote a bunch of stuff here. Change hostent builder code, * added callbacks and reference counting of returned hostents. * --Bleep (Thomas Helvey ) */ #include "config.h" #include "res.h" #include "client.h" #include "ircd.h" #include "ircd_alloc.h" #include "ircd_events.h" #include "ircd_features.h" #include "ircd_log.h" #include "ircd_osdep.h" #include "ircd_reply.h" #include "ircd_snprintf.h" #include "ircd_string.h" #include "msg.h" #include "numeric.h" #include "s_auth.h" #include "s_bsd.h" #include "s_debug.h" #include "s_misc.h" #include "send.h" #include "ircd_struct.h" #include "support.h" #include "sys.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if (CHAR_BIT != 8) #error this code needs to be able to address individual octets #endif /* * Some systems do not define INADDR_NONE (255.255.255.255) * INADDR_NONE is actually a valid address, but it should never * be returned from any nameserver. * NOTE: The bit pattern for INADDR_NONE and INADDR_ANY (0.0.0.0) should be * the same on all hosts so we shouldn't need to use htonl or ntohl to * compare or set the values. */ #ifndef INADDR_NONE #define INADDR_NONE ((unsigned int) 0xffffffff) #endif #define MAXPACKET 1024 /* rfc sez 512 but we expand names so ... */ #define RES_MAXALIASES 35 /* maximum aliases allowed */ #define RES_MAXADDRS 35 /* maximum addresses allowed */ /* * OSF1 doesn't have RES_NOALIASES */ #ifndef RES_NOALIASES #define RES_NOALIASES 0 #endif /* Figure out how to use res_mkquery */ #ifdef RES_PREFIX #define USE_RES_MKQUERY __res_mkquery #define USE_RES_INIT __res_init #else #define USE_RES_MKQUERY res_mkquery #define USE_RES_INIT res_init #endif /* * macros used to calulate offsets into fixed query buffer */ #define ALIAS_BLEN ((RES_MAXALIASES + 1) * sizeof(char*)) #define ADDRS_BLEN ((RES_MAXADDRS + 1) * sizeof(struct in_addr*)) #define ADDRS_OFFSET (ALIAS_BLEN + ADDRS_BLEN) #define ADDRS_DLEN (RES_MAXADDRS * sizeof(struct in_addr)) #define NAMES_OFFSET (ADDRS_OFFSET + ADDRS_DLEN) #define MAXGETHOSTLEN (NAMES_OFFSET + MAXPACKET) #define AR_TTL 600 /* TTL in seconds for dns cache entries */ /* * the following values should be prime */ #define ARES_CACSIZE 307 #define MAXCACHED 281 /* * RFC 1104/1105 wasn't very helpful about what these fields * should be named, so for now, we'll just name them this way. * we probably should look at what named calls them or something. */ #define TYPE_SIZE 2 #define CLASS_SIZE 2 #define TTL_SIZE 4 #define RDLENGTH_SIZE 2 #define ANSWER_FIXED_SIZE (TYPE_SIZE + CLASS_SIZE + TTL_SIZE + RDLENGTH_SIZE) /* * Building the Hostent * The Hostent struct is arranged like this: * +-------------------------------+ * Hostent: | struct hostent h | * |-------------------------------| * | char *buf | * +-------------------------------+ * * allocated: * * +-------------------------------+ * buf: | h_aliases pointer array | Max size: ALIAS_BLEN; * | NULL | contains `char *'s * |-------------------------------| * | h_addr_list pointer array | Max size: ADDRS_BLEN; * | NULL | contains `struct in_addr *'s * |-------------------------------| * | h_addr_list addresses | Max size: ADDRS_DLEN; * | | contains `struct in_addr's * |-------------------------------| * | storage for hostname strings | Max size: ALIAS_DLEN; * +-------------------------------+ contains `char's * * For requests the size of the h_aliases, and h_addr_list pointer * array sizes are set to MAXALISES and MAXADDRS respectively, and * buf is a fixed size with enough space to hold the largest expected * reply from a nameserver, see RFC 1034 and RFC 1035. * For cached entries the sizes are dependent on the actual number * of aliases and addresses. If new aliases and addresses are found * for cached entries, the buffer is grown and the new entries are added. * The hostent struct is filled in with the addresses of the entries in * the Hostent buf as follows: * h_name - contains a pointer to the start of the hostname string area, * or NULL if none is set. The h_name is followed by the * aliases, in the storage for hostname strings area. * h_aliases - contains a pointer to the start of h_aliases pointer array. * This array contains pointers to the storage for hostname * strings area and is terminated with a NULL. The first alias * is stored directly after the h_name. * h_addr_list - contains a pointer to the start of h_addr_list pointer array. * This array contains pointers to in_addr structures in the * h_addr_list addresses area and is terminated with a NULL. * * Filling the buffer this way allows for proper alignment of the h_addr_list * addresses. * * This arrangement allows us to alias a Hostent struct pointer as a * real struct hostent* without lying. It also allows us to change the * values contained in the cached entries and requests without changing * the actual hostent pointer, which is saved in a client struct and can't * be changed without blowing things up or a lot more fiddling around. * It also allows for defered allocation of the fixed size buffers until * they are really needed. * Nov. 17, 1997 --Bleep */ struct Hostent { struct hostent h; /* the hostent struct we are passing around */ char* buf; /* buffer for data pointed to from hostent */ }; struct ResRequest { struct ResRequest* next; int id; int sent; /* number of requests sent */ time_t ttl; char type; char retries; /* retry counter */ char sends; /* number of sends (>1 means resent) */ char resend; /* send flag. 0 == dont resend */ time_t sentat; time_t timeout; struct in_addr addr; char* name; struct DNSQuery query; /* query callback for this request */ struct Hostent he; }; struct CacheEntry { struct CacheEntry* hname_next; struct CacheEntry* hnum_next; struct CacheEntry* list_next; time_t expireat; time_t ttl; struct Hostent he; struct DNSReply reply; }; struct CacheTable { struct CacheEntry* num_list; struct CacheEntry* name_list; }; int ResolverFileDescriptor = -1; /* GLOBAL - used in s_bsd.c */ static struct Socket resSock; /* Socket describing resolver */ static struct Timer resExpireDNS; /* Timer for DNS expiration */ static struct Timer resExpireCache; /* Timer for cache expiration */ static time_t nextDNSCheck = 0; static time_t nextCacheExpire = 1; /* * Keep a spare file descriptor open. res_init calls fopen to read the * resolv.conf file. If ircd is hogging all the file descriptors below 256, * on systems with crippled FILE structures this will cause wierd bugs. * This is definitely needed for Solaris which uses an unsigned char to * hold the file descriptor. --Dianora */ static int spare_fd = -1; static int cachedCount = 0; static struct CacheTable hashtable[ARES_CACSIZE]; static struct CacheEntry* cacheTop; static struct ResRequest* requestListHead; /* head of resolver request list */ static struct ResRequest* requestListTail; /* tail of resolver request list */ static void add_request(struct ResRequest* request); static void rem_request(struct ResRequest* request); static struct ResRequest* make_request(const struct DNSQuery* query); static time_t timeout_query_list(time_t now); static time_t expire_cache(time_t now); static void rem_cache(struct CacheEntry*); static void do_query_name(const struct DNSQuery* query, const char* name, struct ResRequest* request); static void do_query_number(const struct DNSQuery* query, const struct in_addr*, struct ResRequest* request); static void query_name(const char* name, int query_class, int query_type, struct ResRequest* request); static void resend_query(struct ResRequest* request); static struct CacheEntry* make_cache(struct ResRequest* request); static struct CacheEntry* find_cache_name(const char* name); static struct CacheEntry* find_cache_number(struct ResRequest* request, const char* addr); static struct ResRequest* find_id(int); static struct cacheinfo { int ca_adds; int ca_dels; int ca_expires; int ca_lookups; int ca_na_hits; int ca_nu_hits; int ca_updates; } cainfo; static struct resinfo { 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; int re_shortttl; int re_unkrep; } reinfo; /* * From bind 8.3, these aren't declared in earlier versions of bind */ extern u_short _getshort(const u_char *); extern u_int _getlong(const u_char *); /* * int * res_isourserver(ina) * looks up "ina" in _res.ns_addr_list[] * returns: * 0 : not found * >0 : found * author: * paul vixie, 29may94 */ static int res_ourserver(const struct __res_state* statp, const struct sockaddr_in* inp) { struct sockaddr_in ina; int ns; ina = *inp; for (ns = 0; ns < statp->nscount; ns++) { const struct sockaddr_in *srv = &statp->nsaddr_list[ns]; if (srv->sin_family == ina.sin_family && srv->sin_port == ina.sin_port && (srv->sin_addr.s_addr == INADDR_ANY || srv->sin_addr.s_addr == ina.sin_addr.s_addr)) return (1); } return (0); } /* Socket callback for resolver */ static void res_callback(struct Event* ev) { assert(ev_type(ev) == ET_READ || ev_type(ev) == ET_ERROR); resolver_read(); } /* * start_resolver - do everything we need to read the resolv.conf file * and initialize the resolver file descriptor if needed */ static void start_resolver(void) { Debug((DEBUG_DNS, "Resolver: start_resolver")); /* * close the spare file descriptor so res_init can read resolv.conf * successfully. Needed on Solaris */ if (spare_fd > -1) close(spare_fd); USE_RES_INIT(); /* res_init always returns 0 */ /* * make sure we have a valid file descriptor below 256 so we can * do this again. Needed on Solaris */ spare_fd = open("/dev/null",O_RDONLY,0); if ((spare_fd < 0) || (spare_fd > 255)) { char sparemsg[80]; ircd_snprintf(0, sparemsg, sizeof(sparemsg), "invalid spare_fd %d", spare_fd); server_restart(sparemsg); } if (!_res.nscount) { _res.nscount = 1; _res.nsaddr_list[0].sin_addr.s_addr = inet_addr("127.0.0.1"); } _res.options |= RES_NOALIASES; if (ResolverFileDescriptor < 0) { ResolverFileDescriptor = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == ResolverFileDescriptor) { report_error("Resolver: error creating socket for %s: %s", cli_name(&me), errno); return; } if (!os_set_nonblocking(ResolverFileDescriptor)) report_error("Resolver: error setting non-blocking for %s: %s", cli_name(&me), errno); if (!socket_add(&resSock, res_callback, 0, SS_DATAGRAM, SOCK_EVENT_READABLE, ResolverFileDescriptor)) report_error("Resolver: unable to queue resolver file descriptor for %s", cli_name(&me), ENFILE); } } /* Call the query timeout function */ static void expire_DNS_callback(struct Event* ev) { time_t next; next = timeout_query_list(CurrentTime); timer_add(&resExpireDNS, expire_DNS_callback, 0, TT_ABSOLUTE, next); } /* Call the cache expire function */ static void expire_cache_callback(struct Event* ev) { time_t next; next = expire_cache(CurrentTime); timer_add(&resExpireCache, expire_cache_callback, 0, TT_ABSOLUTE, next); } /* * init_resolver - initialize resolver and resolver library */ int init_resolver(void) { Debug((DEBUG_DNS, "Resolver: init_resolver")); #ifdef LRAND48 srand48(CurrentTime); #endif memset(&cainfo, 0, sizeof(cainfo)); memset(hashtable, 0, sizeof(hashtable)); memset(&reinfo, 0, sizeof(reinfo)); requestListHead = requestListTail = 0; /* initiate the resolver timers */ timer_add(timer_init(&resExpireDNS), expire_DNS_callback, 0, TT_RELATIVE, 1); timer_add(timer_init(&resExpireCache), expire_cache_callback, 0, TT_RELATIVE, 1); errno = h_errno = 0; start_resolver(); Debug((DEBUG_DNS, "Resolver: fd %d errno: %d h_errno: %d: %s", ResolverFileDescriptor, errno, h_errno, (strerror(errno)) ? strerror(errno) : "Unknown")); return ResolverFileDescriptor; } /* * restart_resolver - flush the cache, reread resolv.conf, reopen socket */ void restart_resolver(void) { /* flush_cache(); flush the dns cache */ start_resolver(); } static int validate_hostent(const struct hostent* hp) { const char* name; int i = 0; assert(0 != hp); for (name = hp->h_name; name; name = hp->h_aliases[i++]) { if (!string_is_hostname(name)) return 0; } return 1; } /* * add_request - place a new request in the request list */ static void add_request(struct ResRequest* request) { assert(0 != request); if (!requestListHead) requestListHead = requestListTail = request; else { requestListTail->next = request; requestListTail = request; } request->next = NULL; ++reinfo.re_requests; } /* * rem_request - remove a request from the list. * This must also free any memory that has been allocated for * temporary storage of DNS results. */ static void rem_request(struct ResRequest* request) { struct ResRequest** current; struct ResRequest* prev = NULL; assert(0 != request); for (current = &requestListHead; *current; ) { if (*current == request) { *current = request->next; if (requestListTail == request) requestListTail = prev; break; } prev = *current; current = &(*current)->next; } MyFree(request->he.buf); MyFree(request->name); MyFree(request); } /* * make_request - Create a DNS request record for the server. */ static struct ResRequest* make_request(const struct DNSQuery* query) { struct ResRequest* request; assert(0 != query); request = (struct ResRequest*) MyMalloc(sizeof(struct ResRequest)); memset(request, 0, sizeof(struct ResRequest)); request->sentat = CurrentTime; request->retries = feature_int(FEAT_IRCD_RES_RETRIES); request->resend = 1; request->timeout = feature_int(FEAT_IRCD_RES_TIMEOUT); request->addr.s_addr = INADDR_NONE; request->he.h.h_addrtype = AF_INET; request->he.h.h_length = sizeof(struct in_addr); request->query.vptr = query->vptr; request->query.callback = query->callback; #if defined(NULL_POINTER_NOT_ZERO) request->next = NULL; request->he.buf = NULL; request->he.h.h_name = NULL; request->he.h.h_aliases = NULL; request->he.h.h_addr_list = NULL; #endif add_request(request); return request; } /* * timeout_query_list - Remove queries from the list which have been * there too long without being resolved. */ static time_t timeout_query_list(time_t now) { struct ResRequest* request; struct ResRequest* next_request = 0; time_t next_time = 0; time_t timeout = 0; Debug((DEBUG_DNS, "Resolver: timeout_query_list at %s", myctime(now))); for (request = requestListHead; request; request = next_request) { next_request = request->next; timeout = request->sentat + request->timeout; if (timeout < now) { if (--request->retries <= 0) { ++reinfo.re_timeouts; (*request->query.callback)(request->query.vptr, 0); rem_request(request); continue; } else { request->sentat = now; request->timeout += request->timeout; resend_query(request); } } if (!next_time || timeout < next_time) { next_time = timeout; } } return (next_time > now) ? next_time : (now + AR_TTL); } /* * expire_cache - removes entries from the cache which are older * than their expiry times. returns the time at which the server * should next poll the cache. */ static time_t expire_cache(time_t now) { struct CacheEntry* cp; struct CacheEntry* cp_next; time_t expire = 0; Debug((DEBUG_DNS, "Resolver: expire_cache at %s", myctime(now))); for (cp = cacheTop; cp; cp = cp_next) { cp_next = cp->list_next; if (cp->expireat < now) { ++cainfo.ca_expires; rem_cache(cp); } else if (!expire || expire > cp->expireat) expire = cp->expireat; } return (expire > now) ? expire : (now + AR_TTL); } /* * timeout_resolver - check request list and cache for expired entries */ time_t timeout_resolver(time_t now) { if (nextDNSCheck < now) nextDNSCheck = timeout_query_list(now); if (nextCacheExpire < now) nextCacheExpire = expire_cache(now); return IRCD_MIN(nextDNSCheck, nextCacheExpire); } /* * delete_resolver_queries - cleanup outstanding queries * for which there no longer exist clients or conf lines. */ void delete_resolver_queries(const void* vptr) { struct ResRequest* request; struct ResRequest* next_request; for (request = requestListHead; request; request = next_request) { next_request = request->next; if (vptr == request->query.vptr) rem_request(request); } } /* * send_res_msg - sends msg to all nameservers found in the "_res" structure. * This should reflect /etc/resolv.conf. We will get responses * which arent needed but is easier than checking to see if nameserver * isnt present. Returns number of messages successfully sent to * nameservers or -1 if no successful sends. */ static int send_res_msg(const u_char* msg, int len, int rcount) { int i; int sent = 0; int max_queries = IRCD_MIN(_res.nscount, rcount); assert(0 != msg); /* * RES_PRIMARY option is not implemented * if (_res.options & RES_PRIMARY || 0 == max_queries) */ if (0 == max_queries) max_queries = 1; Debug((DEBUG_DNS, "Resolver: sendto %d", max_queries)); for (i = 0; i < max_queries; i++) { if (sendto(ResolverFileDescriptor, msg, len, 0, (struct sockaddr*) &(_res.nsaddr_list[i]), sizeof(struct sockaddr_in)) == len) { ++reinfo.re_sent; ++sent; } else log_write(LS_RESOLVER, L_ERROR, 0, "Resolver: send failed %m"); } return sent; } /* * find_id - find a dns request id (id is determined by dn_mkquery) */ static struct ResRequest* find_id(int id) { struct ResRequest* request; for (request = requestListHead; request; request = request->next) { if (request->id == id) return request; } return NULL; } /* * gethost_byname - get host address from name */ struct DNSReply* gethost_byname(const char* name, const struct DNSQuery* query) { struct CacheEntry* cp; assert(0 != name); Debug((DEBUG_DNS, "Resolver: gethost_byname %s", name)); ++reinfo.re_na_look; if ((cp = find_cache_name(name))) return &(cp->reply); do_query_name(query, name, NULL); nextDNSCheck = 1; return NULL; } /* * gethost_byaddr - get host name from address */ struct DNSReply* gethost_byaddr(const char* addr, const struct DNSQuery* query) { struct CacheEntry *cp; assert(0 != addr); Debug((DEBUG_DNS, "Resolver: gethost_byaddr %s", ircd_ntoa(addr))); ++reinfo.re_nu_look; if ((cp = find_cache_number(NULL, addr))) return &(cp->reply); do_query_number(query, (const struct in_addr*) addr, NULL); nextDNSCheck = 1; return NULL; } /* * do_query_name - nameserver lookup name */ static void do_query_name(const struct DNSQuery* query, const char* name, struct ResRequest* request) { char hname[HOSTLEN + 1]; assert(0 != name); ircd_strncpy(hname, name, HOSTLEN); hname[HOSTLEN] = '\0'; if (!request) { request = make_request(query); request->type = T_A; request->name = (char*) MyMalloc(strlen(hname) + 1); strcpy(request->name, hname); } query_name(hname, C_IN, T_A, request); } /* * do_query_number - Use this to do reverse IP# lookups. */ static void do_query_number(const struct DNSQuery* query, const struct in_addr* addr, struct ResRequest* request) { char ipbuf[32]; const unsigned char* cp; assert(0 != addr); cp = (const unsigned char*) &addr->s_addr; ircd_snprintf(0, ipbuf, sizeof(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 (!request) { request = make_request(query); request->type = T_PTR; request->addr.s_addr = addr->s_addr; } query_name(ipbuf, C_IN, T_PTR, request); } /* * query_name - generate a query based on class, type and name. */ static void query_name(const char* name, int query_class, int type, struct ResRequest* request) { char buf[MAXPACKET]; int request_len = 0; assert(0 != name); assert(0 != request); Debug((DEBUG_DNS, "Resolver: query_name: %s %d %d", name, query_class, type)); memset(buf, 0, sizeof(buf)); if ((request_len = USE_RES_MKQUERY(QUERY, name, query_class, type, 0, 0, 0, (unsigned char*) buf, sizeof(buf))) > 0) { HEADER* header = (HEADER*) buf; #ifndef LRAND48 int k = 0; struct timeval tv; #endif /* * generate a unique id * NOTE: we don't have to worry about converting this to and from * network byte order, the nameserver does not interpret this value * and returns it unchanged */ #ifdef LRAND48 do { header->id = (header->id + lrand48()) & 0xffff; } while (find_id(header->id)); #else gettimeofday(&tv, NULL); do { header->id = (header->id + k + tv.tv_usec) & 0xffff; ++k; } while (find_id(header->id)); #endif /* LRAND48 */ request->id = header->id; ++request->sends; Debug((DEBUG_DNS, "Resolver: query_name %d: %s %d %d", request->id, name, query_class, type)); request->sent += send_res_msg((const unsigned char*) buf, request_len, request->sends); } } static void resend_query(struct ResRequest* request) { assert(0 != request); if (request->resend == 0) return; ++reinfo.re_resends; switch(request->type) { case T_PTR: do_query_number(NULL, &request->addr, request); break; case T_A: do_query_name(NULL, request->name, request); break; default: break; } } /* Returns true if this is a valid name */ static int validate_name(char *name) { while (*name) { if ((*name<'A' || *name>'Z') && (*name<'a' || *name>'z') && (*name<'0' || *name>'9') && (*name!='-')) { return 0; } name++; } return 1; } /* * proc_answer - process name server reply * build a hostent struct in the passed request */ static int proc_answer(struct ResRequest* request, HEADER* header, u_char* buf, u_char* eob) { char hostbuf[HOSTLEN + 1]; /* working buffer */ u_char* current; /* current position in buf */ char** alias; /* alias list */ char** addr; /* address list */ char** base_addr; /* original pointer to address list */ char* name; /* pointer to name string */ char* address; /* pointer to address */ char* base_address; /* original pointer to address */ char* endp; /* end of our buffer */ int query_class; /* answer class */ int type; /* answer type */ int rd_length; /* record data length */ int answer_count = 0; /* answer counter */ int n; /* temp count */ int addr_count = 0; /* number of addresses in hostent */ int alias_count = 0; /* number of aliases in hostent */ int t_ptr_seen = 0; /* Seen a T_PTR in proc_answer? */ struct hostent* hp; /* hostent getting filled */ assert(0 != request); assert(0 != header); assert(0 != buf); assert(0 != eob); current = buf + sizeof(HEADER); hp = &(request->he.h); /* * lazy allocation of request->he.buf, we don't allocate a buffer * unless there is something to put in it. */ if (!request->he.buf) { request->he.buf = (char*) MyMalloc(MAXGETHOSTLEN + 1); request->he.buf[MAXGETHOSTLEN] = '\0'; /* * array of alias list pointers starts at beginning of buf */ hp->h_aliases = (char**) request->he.buf; hp->h_aliases[0] = NULL; /* * array of address list pointers starts after alias list pointers * the actual addresses follow the the address list pointers */ hp->h_addr_list = (char**)(request->he.buf + ALIAS_BLEN); /* * don't copy the host address to the beginning of h_addr_list */ hp->h_addr_list[0] = NULL; } endp = request->he.buf + MAXGETHOSTLEN; /* * find the end of the address list */ addr = hp->h_addr_list; while (*addr) { ++addr; ++addr_count; } base_addr = addr; /* * make address point to first available address slot */ address = request->he.buf + ADDRS_OFFSET + (sizeof(struct in_addr) * addr_count); base_address = address; /* * find the end of the alias list */ alias = hp->h_aliases; while (*alias) { ++alias; ++alias_count; } /* * make name point to first available space in request->buf */ if (alias_count > 0) { name = hp->h_aliases[alias_count - 1]; name += (strlen(name) + 1); } else if (hp->h_name) name = hp->h_name + strlen(hp->h_name) + 1; else name = request->he.buf + ADDRS_OFFSET + ADDRS_DLEN; /* * skip past queries */ while (header->qdcount-- > 0) { if ((n = dn_skipname(current, eob)) < 0) break; current += (n + QFIXEDSZ); } /* * process each answer sent to us blech. */ while (header->ancount-- > 0 && current < eob && name < endp) { n = dn_expand(buf, eob, current, hostbuf, sizeof(hostbuf)); if (n <= 0) { /* * no more answers left */ return answer_count; } hostbuf[HOSTLEN] = '\0'; /* * With Address arithmetic you have to be very anal * this code was not working on alpha due to that * (spotted by rodder/jailbird/dianora) */ current += (size_t) n; if (!((current + ANSWER_FIXED_SIZE) < eob)) break; type = _getshort(current); current += TYPE_SIZE; query_class = _getshort(current); current += CLASS_SIZE; request->ttl = _getlong(current); current += TTL_SIZE; rd_length = _getshort(current); current += RDLENGTH_SIZE; /* * Wait to set request->type until we verify this structure */ switch(type) { case T_A: /* * check for invalid rd_length or too many addresses * ignore T_A relies if looking for a T_PTR */ if (t_ptr_seen) return answer_count; if (rd_length != sizeof(struct in_addr)) return answer_count; if (++addr_count < RES_MAXADDRS) { if (answer_count == 1) hp->h_addrtype = (query_class == C_IN) ? AF_INET : AF_UNSPEC; memcpy(address, current, sizeof(struct in_addr)); *addr++ = address; *addr = 0; address += sizeof(struct in_addr); if (!hp->h_name) { strcpy(name, hostbuf); hp->h_name = name; name += strlen(name) + 1; } Debug((DEBUG_DNS, "Resolver: A %s for %s", ircd_ntoa((char*) hp->h_addr_list[addr_count - 1]), hostbuf)); } current += rd_length; ++answer_count; break; case T_PTR: t_ptr_seen = 1; addr_count = 0; addr = base_addr; *addr = 0; address = base_address; n = dn_expand(buf, eob, current, hostbuf, sizeof(hostbuf)); if (n < 0) { /* * broken message */ return 0; } else if (n == 0) { /* * no more answers left */ return answer_count; } /* * This comment is based on analysis by Shadowfax, Wohali and johan, * not me. (Dianora) I am only commenting it. * * dn_expand is guaranteed to not return more than sizeof(hostbuf) * but do all implementations of dn_expand also guarantee * buffer is terminated with null byte? Lets not take chances. * -Dianora */ hostbuf[HOSTLEN] = '\0'; /* Validate the name meets RFC */ if (validate_name(hostbuf)) { return 0; } current += (size_t) n; Debug((DEBUG_DNS, "Resolver: PTR %s", hostbuf)); /* * copy the returned hostname into the host name * ignore duplicate ptr records */ if (!hp->h_name) { strcpy(name, hostbuf); hp->h_name = name; name += strlen(name) + 1; } ++answer_count; break; case T_CNAME: Debug((DEBUG_DNS, "Resolver: CNAME %s", hostbuf)); if (++alias_count < RES_MAXALIASES) { ircd_strncpy(name, hostbuf, endp - name); *alias++ = name; *alias = 0; name += strlen(name) + 1; } current += rd_length; ++answer_count; break; default : Debug((DEBUG_DNS,"Resolver: proc_answer type: %d for: %s", type, hostbuf)); break; } } return answer_count; } /* * resolver_read - read a dns reply from the nameserver and process it. * return 0 if nothing was read from the socket, otherwise return 1 */ int resolver_read(void) { u_char buf[sizeof(HEADER) + MAXPACKET]; HEADER* header = 0; struct ResRequest* request = 0; struct CacheEntry* cp = 0; unsigned int rc = 0; int answer_count = 0; struct sockaddr_in sin; Debug((DEBUG_DNS, "Resolver: read")); if (IO_SUCCESS != os_recvfrom_nonb(ResolverFileDescriptor, (char*) buf, sizeof(buf), &rc, &sin)) { return 0; } if (rc < sizeof(HEADER)) { Debug((DEBUG_DNS, "Resolver: short reply %d: %s", rc, (strerror(errno)) ? strerror(errno) : "Unknown")); return 0; } /* * convert DNS reply reader from Network byte order to CPU byte order. */ header = (HEADER*) buf; /* header->id = ntohs(header->id); */ header->ancount = ntohs(header->ancount); header->qdcount = ntohs(header->qdcount); header->nscount = ntohs(header->nscount); header->arcount = ntohs(header->arcount); ++reinfo.re_replies; /* * response for an id which we have already received an answer for * just ignore this response. */ if (0 == (request = find_id(header->id))) { Debug((DEBUG_DNS, "Resolver: can't find request id: %d", header->id)); return 1; } /* * check against possibly fake replies */ if (!res_ourserver(&_res, &sin)) { Debug((DEBUG_DNS, "Resolver: fake reply from: %s", (const char*) &sin.sin_addr)); ++reinfo.re_unkrep; return 1; } if ((header->rcode != NOERROR) || (header->ancount == 0)) { ++reinfo.re_errors; if (SERVFAIL == header->rcode) resend_query(request); else { /* * If a bad error was returned, we stop here and dont send * send any more (no retries granted). * Isomer: Perhaps we should return these error messages back to * the client? */ #ifdef DEBUGMODE switch (header->rcode) { case NOERROR: Debug((DEBUG_DNS, "Fatal DNS error: No Error")); break; case FORMERR: Debug((DEBUG_DNS, "Fatal DNS error: Format Error")); break; case SERVFAIL: Debug((DEBUG_DNS, "Fatal DNS error: Server Failure")); break; case NXDOMAIN: Debug((DEBUG_DNS, "DNS error: Non Existant Domain")); break; case NOTIMP: Debug((DEBUG_DNS, "Fatal DNS error: Not Implemented")); break; case REFUSED: Debug((DEBUG_DNS, "Fatal DNS error: Query Refused")); break; default: Debug((DEBUG_DNS, "Unassigned fatal DNS error: %i", header->rcode)); break; } #endif /* DEBUGMODE */ (*request->query.callback)(request->query.vptr, 0); rem_request(request); } return 1; } /* * If this fails there was an error decoding the received packet, * try it again and hope it works the next time. */ answer_count = proc_answer(request, header, buf, buf + rc); if (answer_count) { if (T_PTR == request->type) { struct DNSReply* reply = 0; if (0 == request->he.h.h_name) { /* * got a PTR response with no name, something bogus is happening * don't bother trying again, the client address doesn't resolve */ (*request->query.callback)(request->query.vptr, reply); rem_request(request); return 1; } Debug((DEBUG_DNS, "relookup %s <-> %s", request->he.h.h_name, ircd_ntoa((char*) &request->addr))); /* * Lookup the 'authoritive' name that we were given for the * ip#. By using this call rather than regenerating the * type we automatically gain the use of the cache with no * extra kludges. */ reply = gethost_byname(request->he.h.h_name, &request->query); if (reply) { (*request->query.callback)(request->query.vptr, reply); } else { /* * If name wasn't found, a request has been queued and it will * be the last one queued. This is rather nasty way to keep * a host alias with the query. -avalon */ MyFree(requestListTail->he.buf); requestListTail->he.buf = request->he.buf; request->he.buf = 0; memcpy(&requestListTail->he.h, &request->he.h, sizeof(struct hostent)); } rem_request(request); } else { /* * got a name and address response, client resolved * XXX - Bug found here by Dianora - * make_cache() occasionally returns a NULL pointer when a * PTR returned a CNAME, cp was not checked before so the * callback was being called with a value of 0x2C != NULL. */ struct DNSReply* reply = 0; if (validate_hostent(&request->he.h)) { if ((cp = make_cache(request))) reply = &cp->reply; } (*request->query.callback)(request->query.vptr, reply); rem_request(request); } } else if (!request->sent) { /* * XXX - we got a response for a query we didn't send with a valid id? * this should never happen, bail here and leave the client unresolved */ (*request->query.callback)(request->query.vptr, 0); rem_request(request); } return 1; } /* * resolver_read_multiple - process up to count reads */ void resolver_read_multiple(int count) { int i = 0; for ( ; i < count; ++i) { if (0 == resolver_read()) return; } } static size_t calc_hostent_buffer_size(const struct hostent* hp) { char** p; size_t count = 0; assert(0 != hp); /* * space for name */ count += (strlen(hp->h_name) + 1); /* * space for aliases */ for (p = hp->h_aliases; *p; ++p) count += (strlen(*p) + 1 + sizeof(char*)); /* * space for addresses */ for (p = hp->h_addr_list; *p; ++p) count += (hp->h_length + sizeof(char*)); /* * space for 2 nulls to terminate h_aliases and h_addr_list */ count += (2 * sizeof(char*)); return count; } /* * dup_hostent - Duplicate a hostent struct, allocate only enough memory for * the data we're putting in it. */ static void dup_hostent(struct Hostent* new_hp, struct hostent* hp) { char* p; char** ap; char** pp; int alias_count = 0; int addr_count = 0; size_t bytes_needed = 0; assert(0 != new_hp); assert(0 != hp); /* how much buffer do we need? */ bytes_needed += (strlen(hp->h_name) + 1); pp = hp->h_aliases; while (*pp) { bytes_needed += (strlen(*pp++) + 1 + sizeof(char*)); ++alias_count; } pp = hp->h_addr_list; while (*pp++) { bytes_needed += (hp->h_length + sizeof(char*)); ++addr_count; } /* Reserve space for 2 nulls to terminate h_aliases and h_addr_list */ bytes_needed += (2 * sizeof(char*)); /* Allocate memory */ new_hp->buf = (char*) MyMalloc(bytes_needed); new_hp->h.h_addrtype = hp->h_addrtype; new_hp->h.h_length = hp->h_length; /* first write the address list */ pp = hp->h_addr_list; ap = new_hp->h.h_addr_list = (char**)(new_hp->buf + ((alias_count + 1) * sizeof(char*))); p = (char*)ap + ((addr_count + 1) * sizeof(char*)); while (*pp) { *ap++ = p; memcpy(p, *pp++, hp->h_length); p += hp->h_length; } *ap = 0; /* next write the name */ new_hp->h.h_name = p; strcpy(p, hp->h_name); p += (strlen(p) + 1); /* last write the alias list */ pp = hp->h_aliases; ap = new_hp->h.h_aliases = (char**) new_hp->buf; while (*pp) { *ap++ = p; strcpy(p, *pp++); p += (strlen(p) + 1); } *ap = 0; } /* * update_hostent - Add records to a Hostent struct in place. */ static void update_hostent(struct Hostent* hp, char** addr, char** alias) { char* p; char** ap; char** pp; int alias_count = 0; int addr_count = 0; char* buf = NULL; size_t bytes_needed = 0; if (!hp || !hp->buf) return; /* how much buffer do we need? */ bytes_needed = strlen(hp->h.h_name) + 1; pp = hp->h.h_aliases; while (*pp) { bytes_needed += (strlen(*pp++) + 1 + sizeof(char*)); ++alias_count; } if (alias) { pp = alias; while (*pp) { bytes_needed += (strlen(*pp++) + 1 + sizeof(char*)); ++alias_count; } } pp = hp->h.h_addr_list; while (*pp++) { bytes_needed += (hp->h.h_length + sizeof(char*)); ++addr_count; } if (addr) { pp = addr; while (*pp++) { bytes_needed += (hp->h.h_length + sizeof(char*)); ++addr_count; } } /* Reserve space for 2 nulls to terminate h_aliases and h_addr_list */ bytes_needed += 2 * sizeof(char*); /* Allocate memory */ buf = (char*) MyMalloc(bytes_needed); assert(0 != buf); /* first write the address list */ pp = hp->h.h_addr_list; ap = hp->h.h_addr_list = (char**)(buf + ((alias_count + 1) * sizeof(char*))); p = (char*)ap + ((addr_count + 1) * sizeof(char*)); while (*pp) { memcpy(p, *pp++, hp->h.h_length); *ap++ = p; p += hp->h.h_length; } if (addr) { while (*addr) { memcpy(p, *addr++, hp->h.h_length); *ap++ = p; p += hp->h.h_length; } } *ap = 0; /* next write the name */ strcpy(p, hp->h.h_name); hp->h.h_name = p; p += (strlen(p) + 1); /* last write the alias list */ pp = hp->h.h_aliases; ap = hp->h.h_aliases = (char**) buf; while (*pp) { strcpy(p, *pp++); *ap++ = p; p += (strlen(p) + 1); } if (alias) { while (*alias) { strcpy(p, *alias++); *ap++ = p; p += (strlen(p) + 1); } } *ap = 0; /* release the old buffer */ p = hp->buf; hp->buf = buf; MyFree(p); } /* * hash_number - IP address hash function */ static int hash_number(const unsigned char* ip) { /* could use loop but slower */ unsigned int hashv; const u_char* p = (const u_char*) ip; assert(0 != p); hashv = *p++; hashv += hashv + *p++; hashv += hashv + *p++; hashv += hashv + *p; hashv %= ARES_CACSIZE; return hashv; } /* * hash_name - hostname hash function */ static int hash_name(const char* name) { unsigned int hashv = 0; const u_char* p = (const u_char*) name; assert(0 != p); for (; *p && *p != '.'; ++p) hashv += *p; hashv %= ARES_CACSIZE; return hashv; } /* * add_to_cache - Add a new cache item to the queue and hash table. */ static struct CacheEntry* add_to_cache(struct CacheEntry* ocp) { int hashv; assert(0 != ocp); ocp->list_next = cacheTop; cacheTop = ocp; hashv = hash_name(ocp->he.h.h_name); ocp->hname_next = hashtable[hashv].name_list; hashtable[hashv].name_list = ocp; hashv = hash_number((const unsigned char*) ocp->he.h.h_addr); ocp->hnum_next = hashtable[hashv].num_list; hashtable[hashv].num_list = ocp; /* * LRU deletion of excessive cache entries. */ if (++cachedCount > MAXCACHED) { struct CacheEntry* cp; struct CacheEntry* cp_next; for (cp = ocp->list_next; cp; cp = cp_next) { cp_next = cp->list_next; rem_cache(cp); } } ++cainfo.ca_adds; return ocp; } /* * update_list - does not alter the cache structure passed. It is assumed that * it already contains the correct expire time, if it is a new entry. Old * entries have the expirey time updated. */ static void update_list(struct ResRequest* request, struct CacheEntry* cachep, int is_rbl) { struct CacheEntry* cp = cachep; char* s; char* t; int i; int j; char** ap; char* addrs[RES_MAXADDRS + 1]; char* aliases[RES_MAXALIASES + 1]; /* * search for the new cache item in the cache list by hostname. * If found, move the entry to the top of the list and return. */ ++cainfo.ca_updates; if (!request) return; /* * Compare the cache entry against the new record. Add any * previously missing names for this entry. */ *aliases = 0; ap = aliases; for (i = 0, s = request->he.h.h_name; s; s = request->he.h.h_aliases[i++]) { for (j = 0, t = cp->he.h.h_name; t; t = cp->he.h.h_aliases[j++]) { if (0 == ircd_strcmp(t, s)) break; } if (!t) { *ap++ = s; *ap = 0; } } /* * Do the same again for IP#'s. */ *addrs = 0; if (!is_rbl) { /* But not for RBL ips */ ap = addrs; for (i = 0; (s = request->he.h.h_addr_list[i]); i++) { for (j = 0; (t = cp->he.h.h_addr_list[j]); j++) { if (!memcmp(t, s, sizeof(struct in_addr))) break; } if (!t) { *ap++ = s; *ap = 0; } } } if (*addrs || *aliases) update_hostent(&cp->he, addrs, aliases); } /* * find_cache_name - find name in nameserver cache */ static struct CacheEntry* find_cache_name(const char* name) { struct CacheEntry* cp; char* s; int hashv; int i; assert(0 != name); hashv = hash_name(name); cp = hashtable[hashv].name_list; for (; cp; cp = cp->hname_next) { for (i = 0, s = cp->he.h.h_name; s; s = cp->he.h.h_aliases[i++]) { if (0 == ircd_strcmp(s, name)) { ++cainfo.ca_na_hits; return cp; } } } for (cp = cacheTop; cp; cp = cp->list_next) { /* * if no aliases or the hash value matches, we've already * done this entry and all possiblilities concerning it. */ if (!cp->he.h.h_name || hashv == hash_name(cp->he.h.h_name)) continue; for (i = 0, s = cp->he.h.h_aliases[i]; s; s = cp->he.h.h_aliases[++i]) { if (0 == ircd_strcmp(name, s)) { ++cainfo.ca_na_hits; return cp; } } } return NULL; } /* * find_cache_number - find a cache entry by ip# and update its expire time */ static struct CacheEntry* find_cache_number(struct ResRequest* request, const char* addr) { struct CacheEntry* cp; int hashv; int i; assert(0 != addr); if(request && request->query.callback == auth_dnsbl_callback) return NULL; hashv = hash_number((const unsigned char*) addr); cp = hashtable[hashv].num_list; for (; cp; cp = cp->hnum_next) { for (i = 0; cp->he.h.h_addr_list[i]; ++i) { if (!memcmp(cp->he.h.h_addr_list[i], addr, sizeof(struct in_addr))) { ++cainfo.ca_nu_hits; return cp; } } } for (cp = cacheTop; cp; cp = cp->list_next) { /* * single address entry...would have been done by hashed * search above... * if the first IP# has the same hashnumber as the IP# we * are looking for, its been done already. */ if (!cp->he.h.h_addr_list[1] || hashv == hash_number((const unsigned char*) cp->he.h.h_addr_list[0])) continue; for (i = 1; cp->he.h.h_addr_list[i]; ++i) { if (!memcmp(cp->he.h.h_addr_list[i], addr, sizeof(struct in_addr))) { ++cainfo.ca_nu_hits; return cp; } } } return NULL; } static struct CacheEntry* make_cache(struct ResRequest* request) { struct CacheEntry* cp; int i; struct hostent* hp; assert(0 != request); hp = &request->he.h; /* * shouldn't happen but it just might... */ assert(0 != hp->h_name); /* assert(0 != hp->h_addr_list[0]); */ if (!hp->h_name || !hp->h_addr_list[0]) return NULL; /* * Make cache entry. First check to see if the cache already exists * and if so, return a pointer to it. */ for (i = 0; hp->h_addr_list[i]; ++i) { if ((cp = find_cache_number(request, hp->h_addr_list[i]))) { update_list(request, cp, request->query.callback==auth_dnsbl_callback); return cp; } } /* * a matching entry wasnt found in the cache so go and make one up. */ cp = (struct CacheEntry*) MyMalloc(sizeof(struct CacheEntry)); assert(0 != cp); memset(cp, 0, sizeof(struct CacheEntry)); dup_hostent(&cp->he, hp); cp->reply.hp = &cp->he.h; /* * hmmm... we could time out the cache after 10 minutes regardless * would that be reasonable since we don't save the reply? */ if (request->ttl < AR_TTL) { ++reinfo.re_shortttl; cp->ttl = AR_TTL; } else cp->ttl = request->ttl; cp->expireat = CurrentTime + cp->ttl; return add_to_cache(cp); } /* * rem_cache - delete a cache entry from the cache structures * and lists and return all memory used for the cache back to the memory pool. */ static void rem_cache(struct CacheEntry* ocp) { struct CacheEntry** cp; int hashv; struct hostent* hp; assert(0 != ocp); if (0 < ocp->reply.ref_count) { if (ocp->expireat < CurrentTime) { ocp->expireat = CurrentTime + AR_TTL; Debug((DEBUG_DNS, "Resolver: referenced cache entry not removed for: %s", ocp->he.h.h_name)); } return; } /* * remove cache entry from linked list */ for (cp = &cacheTop; *cp; cp = &((*cp)->list_next)) { if (*cp == ocp) { *cp = ocp->list_next; break; } } hp = &ocp->he.h; /* * remove cache entry from hashed name list */ assert(0 != hp->h_name); hashv = hash_name(hp->h_name); for (cp = &hashtable[hashv].name_list; *cp; cp = &((*cp)->hname_next)) { if (*cp == ocp) { *cp = ocp->hname_next; break; } } /* * remove cache entry from hashed number list */ hashv = hash_number((const unsigned char*) hp->h_addr); assert(-1 < hashv); for (cp = &hashtable[hashv].num_list; *cp; cp = &((*cp)->hnum_next)) { if (*cp == ocp) { *cp = ocp->hnum_next; break; } } /* * free memory used to hold the various host names and the array * of alias pointers. */ MyFree(ocp->he.buf); MyFree(ocp); --cachedCount; ++cainfo.ca_dels; } void flush_resolver_cache(void) { /* * stubbed - iterate cache and remove everything that isn't referenced */ } /* * m_dns - dns status query */ int m_dns(struct Client *cptr, struct Client *sptr, int parc, char *parv[]) { #if !defined(NDEBUG) struct CacheEntry* cp; int i; struct hostent* hp; if (parv[1] && *parv[1] == 'r') { log_write(LS_SYSTEM, L_INFO, 0, "Resolver: restarting resolver."); sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Resolver: restarting resolver.", sptr); restart_resolver(); return 0; } if (parv[1] && *parv[1] == 'l') { for(cp = cacheTop; cp; cp = cp->list_next) { hp = &cp->he.h; sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :Expire %d ttl %d host %s(%s)", sptr, cp->expireat - CurrentTime, cp->ttl, hp->h_name, ircd_ntoa(hp->h_addr)); for (i = 0; hp->h_aliases[i]; i++) sendcmdto_one(&me, CMD_NOTICE, sptr, "%C : %s = %s (CN)", sptr, hp->h_name, hp->h_aliases[i]); for (i = 1; hp->h_addr_list[i]; i++) sendcmdto_one(&me, CMD_NOTICE, sptr, "%C : %s = %s (IP)", sptr, hp->h_name, ircd_ntoa(hp->h_addr_list[i])); } return 0; } sendcmdto_one(&me, CMD_NOTICE, sptr,"%C :""\x02""Cache\x02: " "Adds %d Dels %d Expires %d Lookups %d " "Hits(addr/name) %d/%d " "Updates %d", sptr, cainfo.ca_adds, cainfo.ca_dels, cainfo.ca_expires, cainfo.ca_lookups, cainfo.ca_na_hits, cainfo.ca_nu_hits, cainfo.ca_updates); sendcmdto_one(&me, CMD_NOTICE, sptr,"%C :\x02Resolver\x02: " "Errors %d Lookups %d/%d Replies %d Requests %d", sptr, reinfo.re_errors, reinfo.re_na_look, reinfo.re_nu_look, reinfo.re_replies, reinfo.re_requests); sendcmdto_one(&me, CMD_NOTICE, sptr,"%C :\x02Resolver\x02: " "Unknown Reply %d Short TTL(<10m) %d Resent %d Resends %d " "Timeouts: %d", sptr, reinfo.re_unkrep, reinfo.re_shortttl, reinfo.re_sent, reinfo.re_resends, reinfo.re_timeouts); sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :ResolverFileDescriptor = %d", sptr, ResolverFileDescriptor); #endif return 0; } size_t cres_mem(struct Client* sptr) { struct CacheEntry* entry; struct ResRequest* request; size_t cache_mem = 0; size_t request_mem = 0; int cache_count = 0; int request_count = 0; for (entry = cacheTop; entry; entry = entry->list_next) { cache_mem += sizeof(struct CacheEntry); cache_mem += calc_hostent_buffer_size(&entry->he.h); ++cache_count; } for (request = requestListHead; request; request = request->next) { request_mem += sizeof(struct ResRequest); if (request->name) request_mem += strlen(request->name) + 1; if (request->he.buf) request_mem += MAXGETHOSTLEN + 1; ++request_count; } if (cachedCount != cache_count) { send_reply(sptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Resolver: cache count mismatch: %d != %d", cachedCount, cache_count); assert(cachedCount == cache_count); } send_reply(sptr, SND_EXPLICIT | RPL_STATSDEBUG, ":Resolver: cache %d(%d) requests %d(%d)", cache_count, cache_mem, request_count, request_mem); return cache_mem + request_mem; }