From ed62c46ba15a662db4d4b6dbd0da713d6b14ea30 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Wed, 6 Jan 2016 02:47:22 -0600 Subject: [PATCH] authd: import stripped down charybdis resolver --- authd/Makefile.am | 2 +- authd/Makefile.in | 6 +- authd/res.c | 972 +++++++++++++++++++++++++++++++++++++ authd/res.h | 37 ++ authd/reslib.c | 1178 +++++++++++++++++++++++++++++++++++++++++++++ authd/reslib.h | 127 +++++ 6 files changed, 2319 insertions(+), 3 deletions(-) create mode 100644 authd/res.c create mode 100644 authd/res.h create mode 100644 authd/reslib.c create mode 100644 authd/reslib.h diff --git a/authd/Makefile.am b/authd/Makefile.am index 972bed5f7..6cb60b410 100644 --- a/authd/Makefile.am +++ b/authd/Makefile.am @@ -3,5 +3,5 @@ AM_CFLAGS=$(WARNFLAGS) AM_CPPFLAGS = -I../include -I../libratbox/include -authd_SOURCES = authd.c +authd_SOURCES = authd.c res.c reslib.c authd_LDADD = ../libratbox/src/libratbox.la diff --git a/authd/Makefile.in b/authd/Makefile.in index 679375512..85e1a5de6 100644 --- a/authd/Makefile.in +++ b/authd/Makefile.in @@ -107,7 +107,7 @@ CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(pkglibexecdir)" PROGRAMS = $(pkglibexec_PROGRAMS) -am_authd_OBJECTS = authd.$(OBJEXT) +am_authd_OBJECTS = authd.$(OBJEXT) res.$(OBJEXT) reslib.$(OBJEXT) authd_OBJECTS = $(am_authd_OBJECTS) authd_DEPENDENCIES = ../libratbox/src/libratbox.la AM_V_lt = $(am__v_lt_@AM_V@) @@ -358,7 +358,7 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CFLAGS = $(WARNFLAGS) AM_CPPFLAGS = -I../include -I../libratbox/include -authd_SOURCES = authd.c +authd_SOURCES = authd.c res.c reslib.c authd_LDADD = ../libratbox/src/libratbox.la all: all-am @@ -454,6 +454,8 @@ distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/authd.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/res.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reslib.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< diff --git a/authd/res.c b/authd/res.c new file mode 100644 index 000000000..acfb71a3b --- /dev/null +++ b/authd/res.c @@ -0,0 +1,972 @@ +/* + * A rewrite of Darren Reeds original res.c As there is nothing + * left of Darrens original code, this is now licensed by the hybrid group. + * (Well, some of the function names are the same, and bits of the structs..) + * You can use it where it is useful, free even. Buy us a beer and stuff. + * + * The authors takes no responsibility for any damage or loss + * of property which results from the use of this software. + * + * $Id: res.c 3301 2007-03-28 15:04:06Z jilles $ + * from Hybrid Id: res.c 459 2006-02-12 22:21:37Z db $ + * + * July 1999 - Rewrote a bunch of stuff here. Change hostent builder code, + * added callbacks and reference counting of returned hostents. + * --Bleep (Thomas Helvey ) + * + * This was all needlessly complicated for irc. Simplified. No more hostent + * All we really care about is the IP -> hostname mappings. Thats all. + * + * Apr 28, 2003 --cryogen and Dianora + * + * DNS server flooding lessened, AAAA-or-A lookup removed, ip6.int support + * removed, various robustness fixes + * + * 2006 --jilles and nenolod + * + * Resend queries to other servers if the DNS server replies with an error or + * an invalid response. Also, avoid servers that return errors or invalid + * responses. + * + * October 2012 --mr_flea + * + * ircd-ratbox changes for random IDs merged back in. + * + * January 2016 --kaniini + */ + +#include +#include "setup.h" +#include "res.h" +#include "reslib.h" + +#if (CHAR_BIT != 8) +#error this code needs to be able to address individual octets +#endif + +static PF res_readreply; + +#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 */ +#define AR_TTL 600 /* TTL in seconds for dns cache entries */ + +/* 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 (size_t)2 +#define CLASS_SIZE (size_t)2 +#define TTL_SIZE (size_t)4 +#define RDLENGTH_SIZE (size_t)2 +#define ANSWER_FIXED_SIZE (TYPE_SIZE + CLASS_SIZE + TTL_SIZE + RDLENGTH_SIZE) + +#ifdef RB_IPV6 +extern struct in6_addr ipv6_addr; +#endif +extern struct in_addr ipv4_addr; + +struct reslist +{ + rb_dlink_node node; + int id; + time_t ttl; + char type; + char queryname[IRCD_RES_HOSTLEN + 1]; /* name currently being queried */ + char retries; /* retry counter */ + char sends; /* number of sends (>1 means resent) */ + time_t sentat; + time_t timeout; + int lastns; /* index of last server sent to */ + struct rb_sockaddr_storage addr; + char *name; + struct DNSQuery *query; /* query callback for this request */ +}; + +static rb_fde_t *res_fd; +static rb_dlink_list request_list = { NULL, NULL, 0 }; +static int ns_failure_count[IRCD_MAXNS]; /* timeouts and invalid/failed replies */ + +static void rem_request(struct reslist *request); +static struct reslist *make_request(struct DNSQuery *query); +static void gethost_byname_type_fqdn(const char *name, struct DNSQuery *query, + int type); +static void do_query_name(struct DNSQuery *query, const char *name, struct reslist *request, int); +static void do_query_number(struct DNSQuery *query, const struct rb_sockaddr_storage *, + struct reslist *request); +static void query_name(struct reslist *request); +static int send_res_msg(const char *buf, int len, int count); +static void resend_query(struct reslist *request); +static int check_question(struct reslist *request, HEADER * header, char *buf, char *eob); +static int proc_answer(struct reslist *request, HEADER * header, char *, char *); +static struct reslist *find_id(int id); +static struct DNSReply *make_dnsreply(struct reslist *request); +static int generate_random_port(void); +static uint16_t generate_random_id(void); + +#ifdef RES_MIN +#undef RES_MIN +#endif + +#define RES_MIN(a, b) ((a) < (b) ? (a) : (b)) + +static rb_fde_t * +random_socket(int family) +{ + rb_fde_t *F; + int nport; + int i; + rb_socklen_t len; + struct rb_sockaddr_storage sockaddr; + F = rb_socket(family, SOCK_DGRAM, 0, "UDP resolver socket"); + if(F == NULL) + return NULL; + + memset(&sockaddr, 0, sizeof(sockaddr)); + + SET_SS_FAMILY(&sockaddr, family); + +#ifdef RB_IPV6 + if(family == AF_INET6) + { + struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&sockaddr; + memcpy(&in6->sin6_addr, &ipv6_addr, sizeof(struct in6_addr)); + len = (rb_socklen_t) sizeof(struct sockaddr_in6); + } + else +#endif + { + struct sockaddr_in *in = (struct sockaddr_in *)&sockaddr; + in->sin_addr.s_addr = ipv4_addr.s_addr; + len = (rb_socklen_t) sizeof(struct sockaddr_in); + } + + for(i = 0; i < 10; i++) + { + nport = htons(generate_random_port()); + + if(family == AF_INET) + ((struct sockaddr_in *)&sockaddr)->sin_port = nport; +#ifdef RB_IPV6 + else + ((struct sockaddr_in6 *)&sockaddr)->sin6_port = nport; + +#endif + if(bind(rb_get_fd(F), (struct sockaddr *)&sockaddr, len) == 0) + return F; + } + rb_close(F); + return NULL; +} + +/* + * int + * res_ourserver(inp) + * looks up "inp" in irc_nsaddr_list[] + * returns: + * server ID or -1 for not found + * author: + * paul vixie, 29may94 + * revised for ircd, cryogen(stu) may03 + * slightly modified for charybdis, mr_flea oct12 + */ +static int res_ourserver(const struct rb_sockaddr_storage *inp) +{ +#ifdef RB_IPV6 + const struct sockaddr_in6 *v6; + const struct sockaddr_in6 *v6in = (const struct sockaddr_in6 *)inp; +#endif + const struct sockaddr_in *v4; + const struct sockaddr_in *v4in = (const struct sockaddr_in *)inp; + int ns; + + for (ns = 0; ns < irc_nscount; ns++) + { + const struct rb_sockaddr_storage *srv = &irc_nsaddr_list[ns]; + + if (srv->ss_family != inp->ss_family) + continue; + +#ifdef RB_IPV6 + v6 = (const struct sockaddr_in6 *)srv; +#endif + v4 = (const struct sockaddr_in *)srv; + + /* could probably just memcmp(srv, inp, srv.ss_len) here + * but we'll err on the side of caution - stu + */ + switch (srv->ss_family) + { +#ifdef RB_IPV6 + case AF_INET6: + if (v6->sin6_port == v6in->sin6_port) + if ((memcmp(&v6->sin6_addr.s6_addr, &v6in->sin6_addr.s6_addr, + sizeof(struct in6_addr)) == 0) || + (memcmp(&v6->sin6_addr.s6_addr, &in6addr_any, + sizeof(struct in6_addr)) == 0)) + { + return ns; + } + break; +#endif + case AF_INET: + if (v4->sin_port == v4in->sin_port) + if ((v4->sin_addr.s_addr == INADDR_ANY) + || (v4->sin_addr.s_addr == v4in->sin_addr.s_addr)) + { + return ns; + } + break; + default: + break; + } + } + + return -1; +} + +/* + * 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) +{ + rb_dlink_node *ptr; + rb_dlink_node *next_ptr; + struct reslist *request; + time_t next_time = 0; + time_t timeout = 0; + + RB_DLINK_FOREACH_SAFE(ptr, next_ptr, request_list.head) + { + request = ptr->data; + timeout = request->sentat + request->timeout; + + if (now >= timeout) + { + ns_failure_count[request->lastns]++; + request->sentat = now; + request->timeout += request->timeout; + resend_query(request); + } + + if ((next_time == 0) || timeout < next_time) + { + next_time = timeout; + } + } + + return (next_time > now) ? next_time : (now + AR_TTL); +} + +/* + * timeout_resolver - check request list + */ +static void timeout_resolver(void *notused) +{ + timeout_query_list(rb_current_time()); +} + +static struct ev_entry *timeout_resolver_ev = NULL; + +/* + * 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) +{ + int i; + + irc_res_init(); + for (i = 0; i < irc_nscount; i++) + ns_failure_count[i] = 0; + + if (res_fd == NULL) + { + if ((res_fd = rb_socket(irc_nsaddr_list[0].ss_family, SOCK_DGRAM, 0, + "UDP resolver socket")) == NULL) + return; + + /* At the moment, the resolver FD data is global .. */ + rb_setselect(res_fd, RB_SELECT_READ, res_readreply, NULL); + timeout_resolver_ev = rb_event_add("timeout_resolver", timeout_resolver, NULL, 1); + } +} + +/* + * init_resolver - initialize resolver and resolver library + */ +void init_resolver(void) +{ +#ifdef HAVE_SRAND48 + srand48(rb_current_time()); +#endif + start_resolver(); +} + +/* + * restart_resolver - reread resolv.conf, reopen socket + */ +void restart_resolver(void) +{ + rb_close(res_fd); + res_fd = NULL; + rb_event_delete(timeout_resolver_ev); /* -ddosen */ + start_resolver(); +} + +/* + * add_local_domain - Add the domain to hostname, if it is missing + * (as suggested by eps@TOASTER.SFSU.EDU) + */ +void add_local_domain(char *hname, size_t size) +{ + /* try to fix up unqualified names */ + if (strchr(hname, '.') == NULL) + { + if (irc_domain[0]) + { + size_t len = strlen(hname); + + if ((strlen(irc_domain) + len + 2) < size) + { + hname[len++] = '.'; + strcpy(hname + len, irc_domain); + } + } + } +} + +/* + * 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 reslist *request) +{ + rb_dlinkDelete(&request->node, &request_list); + rb_free(request->name); + rb_free(request); +} + +/* + * make_request - Create a DNS request record for the server. + */ +static struct reslist *make_request(struct DNSQuery *query) +{ + struct reslist *request = rb_malloc(sizeof(struct reslist)); + + request->sentat = rb_current_time(); + request->retries = 3; + request->timeout = 4; /* start at 4 and exponential inc. */ + request->query = query; + + /* + * 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 + * + * we generate an id per request now (instead of per send) to allow + * late replies to be used. + */ + request->id = generate_random_id(); + + rb_dlinkAdd(request, &request->node, &request_list); + + return request; +} + +/* + * retryfreq - determine how many queries to wait before resending + * if there have been that many consecutive timeouts + */ +static int retryfreq(int timeouts) +{ + switch (timeouts) + { + case 1: + return 3; + case 2: + return 9; + case 3: + return 27; + case 4: + return 81; + default: + return 243; + } +} + +/* + * send_res_msg - sends msg to a nameserver. + * This should reflect /etc/resolv.conf. + * Returns number of nameserver successfully sent to + * or -1 if no successful sends. + */ +static int send_res_msg(const char *msg, int len, int rcount) +{ + int i; + int ns; + static int retrycnt; + + retrycnt++; + /* First try a nameserver that seems to work. + * Every once in a while, try a possibly broken one to check + * if it is working again. + */ + for (i = 0; i < irc_nscount; i++) + { + ns = (i + rcount - 1) % irc_nscount; + if (ns_failure_count[ns] && retrycnt % retryfreq(ns_failure_count[ns])) + continue; + if (sendto(rb_get_fd(res_fd), msg, len, 0, + (struct sockaddr *)&(irc_nsaddr_list[ns]), + GET_SS_LEN(&irc_nsaddr_list[ns])) == len) + return ns; + } + + /* No known working nameservers, try some broken one. */ + for (i = 0; i < irc_nscount; i++) + { + ns = (i + rcount - 1) % irc_nscount; + if (!ns_failure_count[ns]) + continue; + if (sendto(rb_get_fd(res_fd), msg, len, 0, + (struct sockaddr *)&(irc_nsaddr_list[ns]), + GET_SS_LEN(&irc_nsaddr_list[ns])) == len) + return ns; + } + + return -1; +} + +/* + * find_id - find a dns request id (id is determined by dn_mkquery) + */ +static struct reslist *find_id(int id) +{ + rb_dlink_node *ptr; + struct reslist *request; + + RB_DLINK_FOREACH(ptr, request_list.head) + { + request = ptr->data; + + if (request->id == id) + return (request); + } + + return (NULL); +} + +static uint16_t +generate_random_id(void) +{ + uint16_t id; + + do + { + rb_get_random(&id, sizeof(id)); + if(id == 0xffff) + continue; + } + while(find_id(id)); + return id; +} + +static int +generate_random_port(void) +{ + uint16_t port; + + while(1) + { + rb_get_random(&port, sizeof(port)); + if(port > 1024) + break; + } + return (int)port; +} + +/* + * gethost_byname_type - get host address from name, adding domain if needed + */ +void gethost_byname_type(const char *name, struct DNSQuery *query, int type) +{ + char fqdn[IRCD_RES_HOSTLEN + 1]; + assert(name != 0); + + rb_strlcpy(fqdn, name, sizeof fqdn); + add_local_domain(fqdn, IRCD_RES_HOSTLEN); + gethost_byname_type_fqdn(fqdn, query, type); +} + +/* + * gethost_byname_type_fqdn - get host address from fqdn + */ +static void gethost_byname_type_fqdn(const char *name, struct DNSQuery *query, + int type) +{ + assert(name != 0); + do_query_name(query, name, NULL, type); +} + +/* + * gethost_byaddr - get host name from address + */ +void gethost_byaddr(const struct rb_sockaddr_storage *addr, struct DNSQuery *query) +{ + do_query_number(query, addr, NULL); +} + +/* + * do_query_name - nameserver lookup name + */ +static void do_query_name(struct DNSQuery *query, const char *name, struct reslist *request, + int type) +{ + if (request == NULL) + { + request = make_request(query); + request->name = rb_strdup(name); + } + + rb_strlcpy(request->queryname, name, sizeof(request->queryname)); + request->type = type; + query_name(request); +} + +/* + * do_query_number - Use this to do reverse IP# lookups. + */ +static void do_query_number(struct DNSQuery *query, const struct rb_sockaddr_storage *addr, + struct reslist *request) +{ + const unsigned char *cp; + + if (request == NULL) + { + request = make_request(query); + memcpy(&request->addr, addr, sizeof(struct rb_sockaddr_storage)); + request->name = (char *)rb_malloc(IRCD_RES_HOSTLEN + 1); + } + + if (addr->ss_family == AF_INET) + { + const struct sockaddr_in *v4 = (const struct sockaddr_in *)addr; + cp = (const unsigned char *)&v4->sin_addr.s_addr; + + rb_sprintf(request->queryname, "%u.%u.%u.%u.in-addr.arpa", (unsigned int)(cp[3]), + (unsigned int)(cp[2]), (unsigned int)(cp[1]), (unsigned int)(cp[0])); + } +#ifdef RB_IPV6 + else if (addr->ss_family == AF_INET6) + { + const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *)addr; + cp = (const unsigned char *)&v6->sin6_addr.s6_addr; + + (void)sprintf(request->queryname, "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.ip6.arpa", + (unsigned int)(cp[15] & 0xf), (unsigned int)(cp[15] >> 4), + (unsigned int)(cp[14] & 0xf), (unsigned int)(cp[14] >> 4), + (unsigned int)(cp[13] & 0xf), (unsigned int)(cp[13] >> 4), + (unsigned int)(cp[12] & 0xf), (unsigned int)(cp[12] >> 4), + (unsigned int)(cp[11] & 0xf), (unsigned int)(cp[11] >> 4), + (unsigned int)(cp[10] & 0xf), (unsigned int)(cp[10] >> 4), + (unsigned int)(cp[9] & 0xf), (unsigned int)(cp[9] >> 4), + (unsigned int)(cp[8] & 0xf), (unsigned int)(cp[8] >> 4), + (unsigned int)(cp[7] & 0xf), (unsigned int)(cp[7] >> 4), + (unsigned int)(cp[6] & 0xf), (unsigned int)(cp[6] >> 4), + (unsigned int)(cp[5] & 0xf), (unsigned int)(cp[5] >> 4), + (unsigned int)(cp[4] & 0xf), (unsigned int)(cp[4] >> 4), + (unsigned int)(cp[3] & 0xf), (unsigned int)(cp[3] >> 4), + (unsigned int)(cp[2] & 0xf), (unsigned int)(cp[2] >> 4), + (unsigned int)(cp[1] & 0xf), (unsigned int)(cp[1] >> 4), + (unsigned int)(cp[0] & 0xf), (unsigned int)(cp[0] >> 4)); + } +#endif + + request->type = T_PTR; + query_name(request); +} + +/* + * query_name - generate a query based on class, type and name. + */ +static void query_name(struct reslist *request) +{ + char buf[MAXPACKET]; + int request_len = 0; + int ns; + + memset(buf, 0, sizeof(buf)); + + if ((request_len = + irc_res_mkquery(request->queryname, C_IN, request->type, (unsigned char *)buf, sizeof(buf))) > 0) + { + HEADER *header = (HEADER *)(void *)buf; + header->id = request->id; + ++request->sends; + + ns = send_res_msg(buf, request_len, request->sends); + if (ns != -1) + request->lastns = ns; + } +} + +static void resend_query(struct reslist *request) +{ + if (--request->retries <= 0) + { + (*request->query->callback) (request->query->ptr, NULL); + rem_request(request); + return; + } + + switch (request->type) + { + case T_PTR: + do_query_number(NULL, &request->addr, request); + break; + case T_A: +#ifdef RB_IPV6 + case T_AAAA: +#endif + do_query_name(NULL, request->name, request, request->type); + break; + default: + break; + } +} + +/* + * check_question - check if the reply really belongs to the + * name we queried (to guard against late replies from previous + * queries with the same id). + */ +static int check_question(struct reslist *request, HEADER * header, char *buf, char *eob) +{ + char hostbuf[IRCD_RES_HOSTLEN + 1]; /* working buffer */ + unsigned char *current; /* current position in buf */ + int n; /* temp count */ + + current = (unsigned char *)buf + sizeof(HEADER); + if (header->qdcount != 1) + return 0; + n = irc_dn_expand((unsigned char *)buf, (unsigned char *)eob, current, hostbuf, + sizeof(hostbuf)); + if (n <= 0) + return 0; + if (strcasecmp(hostbuf, request->queryname)) + return 0; + return 1; +} + +/* + * proc_answer - process name server reply + */ +static int proc_answer(struct reslist *request, HEADER * header, char *buf, char *eob) +{ + char hostbuf[IRCD_RES_HOSTLEN + 100]; /* working buffer */ + unsigned char *current; /* current position in buf */ + int type; /* answer type */ + int n; /* temp count */ + int rd_length; + struct sockaddr_in *v4; /* conversion */ +#ifdef RB_IPV6 + struct sockaddr_in6 *v6; +#endif + current = (unsigned char *)buf + sizeof(HEADER); + + for (; header->qdcount > 0; --header->qdcount) + { + if ((n = irc_dn_skipname(current, (unsigned char *)eob)) < 0) + return 0; + + current += (size_t) n + QFIXEDSZ; + } + + /* + * process each answer sent to us blech. + */ + while (header->ancount > 0 && (char *)current < eob) + { + header->ancount--; + + n = irc_dn_expand((unsigned char *)buf, (unsigned char *)eob, current, hostbuf, + sizeof(hostbuf)); + + if (n < 0) + { + /* + * broken message + */ + return (0); + } + else if (n == 0) + { + /* + * no more answers left + */ + return (0); + } + + hostbuf[IRCD_RES_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 (!(((char *)current + ANSWER_FIXED_SIZE) < eob)) + break; + + type = irc_ns_get16(current); + current += TYPE_SIZE; + + (void) irc_ns_get16(current); + current += CLASS_SIZE; + + request->ttl = irc_ns_get32(current); + current += TTL_SIZE; + + rd_length = irc_ns_get16(current); + current += RDLENGTH_SIZE; + + /* + * Wait to set request->type until we verify this structure + */ + switch (type) + { + case T_A: + if (request->type != T_A) + return (0); + + /* + * check for invalid rd_length or too many addresses + */ + if (rd_length != sizeof(struct in_addr)) + return (0); + v4 = (struct sockaddr_in *)&request->addr; + SET_SS_LEN(&request->addr, sizeof(struct sockaddr_in)); + v4->sin_family = AF_INET; + memcpy(&v4->sin_addr, current, sizeof(struct in_addr)); + return (1); + break; +#ifdef RB_IPV6 + case T_AAAA: + if (request->type != T_AAAA) + return (0); + if (rd_length != sizeof(struct in6_addr)) + return (0); + SET_SS_LEN(&request->addr, sizeof(struct sockaddr_in6)); + v6 = (struct sockaddr_in6 *)&request->addr; + v6->sin6_family = AF_INET6; + memcpy(&v6->sin6_addr, current, sizeof(struct in6_addr)); + return (1); + break; +#endif + case T_PTR: + if (request->type != T_PTR) + return (0); + n = irc_dn_expand((unsigned char *)buf, (unsigned char *)eob, current, + hostbuf, sizeof(hostbuf)); + if (n < 0) + return (0); /* broken message */ + else if (n == 0) + return (0); /* no more answers left */ + + rb_strlcpy(request->name, hostbuf, IRCD_RES_HOSTLEN + 1); + + return (1); + break; + case T_CNAME: + /* real answer will follow */ + current += rd_length; + break; + + default: + break; + } + } + + return (1); +} + +/* + * res_read_single_reply - read a dns reply from the nameserver and process it. + * Return value: 1 if a packet was read, 0 otherwise + */ +static int res_read_single_reply(rb_fde_t *F, void *data) +{ + char buf[sizeof(HEADER) + MAXPACKET] + /* Sparc and alpha need 16bit-alignment for accessing header->id + * (which is uint16_t). Because of the header = (HEADER*) buf; + * lateron, this is neeeded. --FaUl + */ +#if defined(__sparc__) || defined(__alpha__) + __attribute__ ((aligned(16))) +#endif + ; + HEADER *header; + struct reslist *request = NULL; + struct DNSReply *reply = NULL; + int rc; + int answer_count; + socklen_t len = sizeof(struct rb_sockaddr_storage); + struct rb_sockaddr_storage lsin; + int ns; + + rc = recvfrom(rb_get_fd(F), buf, sizeof(buf), 0, (struct sockaddr *)&lsin, &len); + + /* No packet */ + if (rc == 0 || rc == -1) + return 0; + + /* Too small */ + if (rc <= (int)(sizeof(HEADER))) + return 1; + + /* + * convert DNS reply reader from Network byte order to CPU byte order. + */ + header = (HEADER *)(void *)buf; + header->ancount = ntohs(header->ancount); + header->qdcount = ntohs(header->qdcount); + header->nscount = ntohs(header->nscount); + header->arcount = ntohs(header->arcount); + + /* + * response for an id which we have already received an answer for + * just ignore this response. + */ + if (0 == (request = find_id(header->id))) + return 1; + + /* + * check against possibly fake replies + */ + ns = res_ourserver(&lsin); + if (ns == -1) + return 1; + + if (ns != request->lastns) + { + /* + * We'll accept the late reply, but penalize it a little more to make + * sure a laggy server doesn't end up favored. + */ + ns_failure_count[ns] += 3; + } + + + if (!check_question(request, header, buf, buf + rc)) + return 1; + + if ((header->rcode != NO_ERRORS) || (header->ancount == 0)) + { + /* + * RFC 2136 states that in the event of a server returning SERVFAIL + * or NOTIMP, the request should be resent to the next server. + * Additionally, if the server refuses our query, resend it as well. + * -- mr_flea + */ + if (SERVFAIL == header->rcode || NOTIMP == header->rcode || + REFUSED == header->rcode) + { + ns_failure_count[ns]++; + resend_query(request); + } + else + { + /* + * Either a fatal error was returned or no answer. Cancel the + * request. + */ + if (NXDOMAIN == header->rcode) + { + /* If the rcode is NXDOMAIN, treat it as a good response. */ + ns_failure_count[ns] /= 4; + } + (*request->query->callback) (request->query->ptr, NULL); + rem_request(request); + } + return 1; + } + /* + * If this fails there was an error decoding the received packet. + * -- jilles + */ + answer_count = proc_answer(request, header, buf, buf + rc); + + if (answer_count) + { + if (request->type == T_PTR) + { + if (request->name == NULL) + { + /* + * Got a PTR response with no name, something strange is + * happening. Try another DNS server. + */ + ns_failure_count[ns]++; + resend_query(request); + return 1; + } + + /* + * Lookup the 'authoritative' name that we were given for the + * ip#. + */ +#ifdef RB_IPV6 + if (request->addr.ss_family == AF_INET6) + gethost_byname_type_fqdn(request->name, request->query, T_AAAA); + else +#endif + gethost_byname_type_fqdn(request->name, request->query, T_A); + rem_request(request); + } + else + { + /* + * got a name and address response, client resolved + */ + reply = make_dnsreply(request); + (*request->query->callback) (request->query->ptr, reply); + rb_free(reply); + rem_request(request); + } + + ns_failure_count[ns] /= 4; + } + else + { + /* Invalid or corrupt reply - try another resolver. */ + ns_failure_count[ns]++; + resend_query(request); + } + return 1; +} + +static void +res_readreply(rb_fde_t *F, void *data) +{ + while (res_read_single_reply(F, data)) + ; + rb_setselect(F, RB_SELECT_READ, res_readreply, NULL); +} + +static struct DNSReply * +make_dnsreply(struct reslist *request) +{ + struct DNSReply *cp; + lrb_assert(request != 0); + + cp = (struct DNSReply *)rb_malloc(sizeof(struct DNSReply)); + + cp->h_name = request->name; + memcpy(&cp->addr, &request->addr, sizeof(cp->addr)); + return (cp); +} diff --git a/authd/res.h b/authd/res.h new file mode 100644 index 000000000..36e71a4a4 --- /dev/null +++ b/authd/res.h @@ -0,0 +1,37 @@ +/* + * res.h for referencing functions in res.c, reslib.c + * + * $Id: res.h 2023 2006-09-02 23:47:27Z jilles $ + */ + +#ifndef _CHARYBDIS_RES_H +#define _CHARYBDIS_RES_H + +/* Maximum number of nameservers in /etc/resolv.conf we care about + * In hybrid, this was 2 -- but in Charybdis, we want to track + * a few more than that ;) --nenolod + */ +#define IRCD_MAXNS 10 +#define RESOLVER_HOSTLEN 255 + +struct DNSReply +{ + char *h_name; + struct rb_sockaddr_storage addr; +}; + +struct DNSQuery +{ + void *ptr; /* pointer used by callback to identify request */ + void (*callback)(void* vptr, struct DNSReply *reply); /* callback to call */ +}; + +extern struct rb_sockaddr_storage irc_nsaddr_list[]; +extern int irc_nscount; + +extern void init_resolver(void); +extern void restart_resolver(void); +extern void gethost_byname_type(const char *, struct DNSQuery *, int); +extern void gethost_byaddr(const struct rb_sockaddr_storage *, struct DNSQuery *); + +#endif diff --git a/authd/reslib.c b/authd/reslib.c new file mode 100644 index 000000000..3b669601b --- /dev/null +++ b/authd/reslib.c @@ -0,0 +1,1178 @@ +/* + * Copyright (c) 1985, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Portions Copyright (c) 1993 by Digital Equipment Corporation. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies, and that + * the name of Digital Equipment Corporation not be used in advertising or + * publicity pertaining to distribution of the document or software without + * specific, written prior permission. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DIGITAL EQUIPMENT + * CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Portions Copyright (c) 1996-1999 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* Original copyright ISC as above. + * Code modified specifically for ircd use from the following orginal files + * in bind ... + * + * res_comp.c + * ns_name.c + * ns_netint.c + * res_init.c + * + * - Dianora + */ + +#include "stdinc.h" +#include "ircd_defs.h" +#include "common.h" +#include "ircd.h" +#include "res.h" +#include "reslib.h" +#include "match.h" +#include "logger.h" + +#define NS_TYPE_ELT 0x40 /* EDNS0 extended label type */ +#define DNS_LABELTYPE_BITSTRING 0x41 +#define DNS_MAXLINE 128 + +/* $Id: reslib.c 1695 2006-06-27 15:11:23Z jilles $ */ +/* from Hybrid Id: reslib.c 177 2005-10-22 09:05:05Z michael $ */ + +struct rb_sockaddr_storage irc_nsaddr_list[IRCD_MAXNS]; +int irc_nscount = 0; +char irc_domain[IRCD_RES_HOSTLEN + 1]; + +static const char digitvalue[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*16*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*32*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*48*/ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /*64*/ + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*80*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*96*/ + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*112*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*128*/ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*256*/ +}; + +static int parse_resvconf(void); +static void add_nameserver(const char *); + +static const char digits[] = "0123456789"; +static int labellen(const unsigned char *lp); +static int special(int ch); +static int printable(int ch); +static int irc_decode_bitstring(const char **cpp, char *dn, const char *eom); +static int irc_ns_name_compress(const char *src, unsigned char *dst, size_t dstsiz, + const unsigned char **dnptrs, const unsigned char **lastdnptr); +static int irc_dn_find(const unsigned char *, const unsigned char *, const unsigned char * const *, + const unsigned char * const *); +static int irc_encode_bitsring(const char **, const char *, unsigned char **, unsigned char **, + const char *); +static int irc_ns_name_uncompress(const unsigned char *, const unsigned char *, + const unsigned char *, char *, size_t); +static int irc_ns_name_unpack(const unsigned char *, const unsigned char *, + const unsigned char *, unsigned char *, + size_t); +static int irc_ns_name_ntop(const char *, char *, size_t); +static int irc_ns_name_skip(const unsigned char **, const unsigned char *); +static int mklower(int ch); + +int +irc_res_init(void) +{ + irc_nscount = 0; + parse_resvconf(); + if (irc_nscount == 0) + add_nameserver("127.0.0.1"); + return 0; +} + +/* parse_resvconf() + * + * inputs - NONE + * output - -1 if failure 0 if success + * side effects - fills in irc_nsaddr_list + */ +static int +parse_resvconf(void) +{ + char *p; + char *opt; + char *arg; + char input[DNS_MAXLINE]; + FILE *file; + + /* XXX "/etc/resolv.conf" should be from a define in setup.h perhaps + * for cygwin support etc. this hardcodes it to unix for now -db + */ + if ((file = fopen("/etc/resolv.conf", "r")) == NULL) + return -1; + + while (fgets(input, sizeof(input), file) != NULL) + { + /* blow away any newline */ + if ((p = strpbrk(input, "\r\n")) != NULL) + *p = '\0'; + + p = input; + /* skip until something thats not a space is seen */ + while (isspace(*p)) + p++; + /* if at this point, have a '\0' then continue */ + if (*p == '\0') + continue; + + /* Ignore comment lines immediately */ + if (*p == '#' || *p == ';') + continue; + + /* skip until a space is found */ + opt = p; + while (!isspace(*p) && *p != '\0') + p++; + if (*p == '\0') + continue; /* no arguments?.. ignore this line */ + /* blow away the space character */ + *p++ = '\0'; + + /* skip these spaces that are before the argument */ + while (isspace(*p)) + p++; + /* Now arg should be right where p is pointing */ + arg = p; + if ((p = strpbrk(arg, " \t")) != NULL) + *p = '\0'; /* take the first word */ + + if (strcasecmp(opt, "domain") == 0) + rb_strlcpy(irc_domain, arg, sizeof(irc_domain)); + else if (strcasecmp(opt, "nameserver") == 0) + add_nameserver(arg); + } + + fclose(file); + return 0; +} + +/* add_nameserver() + * + * input - either an IPV4 address in dotted quad + * or an IPV6 address in : format + * output - NONE + * side effects - entry in irc_nsaddr_list is filled in as needed + */ +static void +add_nameserver(const char *arg) +{ + struct addrinfo hints, *res; + + /* Done max number of nameservers? */ + if (irc_nscount >= IRCD_MAXNS) + return; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; + + if (getaddrinfo(arg, "domain", &hints, &res)) + return; + + if (res == NULL) + return; + + memcpy(&irc_nsaddr_list[irc_nscount], res->ai_addr, res->ai_addrlen); + SET_SS_LEN(&irc_nsaddr_list[irc_nscount], res->ai_addrlen); + irc_nscount++; + freeaddrinfo(res); +} + +/* + * Expand compressed domain name 'comp_dn' to full domain name. + * 'msg' is a pointer to the begining of the message, + * 'eomorig' points to the first location after the message, + * 'exp_dn' is a pointer to a buffer of size 'length' for the result. + * Return size of compressed name or -1 if there was an error. + */ +int +irc_dn_expand(const unsigned char *msg, const unsigned char *eom, + const unsigned char *src, char *dst, int dstsiz) +{ + int n = irc_ns_name_uncompress(msg, eom, src, dst, (size_t)dstsiz); + + if (n > 0 && dst[0] == '.') + dst[0] = '\0'; + return(n); +} + +/* + * irc_ns_name_uncompress(msg, eom, src, dst, dstsiz) + * Expand compressed domain name to presentation format. + * return: + * Number of bytes read out of `src', or -1 (with errno set). + * note: + * Root domain returns as "." not "". + */ +static int +irc_ns_name_uncompress(const unsigned char *msg, const unsigned char *eom, + const unsigned char *src, char *dst, size_t dstsiz) +{ + unsigned char tmp[NS_MAXCDNAME]; + int n; + + if ((n = irc_ns_name_unpack(msg, eom, src, tmp, sizeof tmp)) == -1) + return(-1); + if (irc_ns_name_ntop((char*)tmp, dst, dstsiz) == -1) + return(-1); + return(n); +} +/* + * irc_ns_name_unpack(msg, eom, src, dst, dstsiz) + * Unpack a domain name from a message, source may be compressed. + * return: + * -1 if it fails, or consumed octets if it succeeds. + */ +static int +irc_ns_name_unpack(const unsigned char *msg, const unsigned char *eom, + const unsigned char *src, unsigned char *dst, + size_t dstsiz) +{ + const unsigned char *srcp, *dstlim; + unsigned char *dstp; + int n, len, checked, l; + + len = -1; + checked = 0; + dstp = dst; + srcp = src; + dstlim = dst + dstsiz; + if (srcp < msg || srcp >= eom) { + errno = EMSGSIZE; + return (-1); + } + /* Fetch next label in domain name. */ + while ((n = *srcp++) != 0) { + /* Check for indirection. */ + switch (n & NS_CMPRSFLGS) { + case 0: + case NS_TYPE_ELT: + /* Limit checks. */ + if ((l = labellen(srcp - 1)) < 0) { + errno = EMSGSIZE; + return(-1); + } + if (dstp + l + 1 >= dstlim || srcp + l >= eom) { + errno = EMSGSIZE; + return (-1); + } + checked += l + 1; + *dstp++ = n; + memcpy(dstp, srcp, l); + dstp += l; + srcp += l; + break; + + case NS_CMPRSFLGS: + if (srcp >= eom) { + errno = EMSGSIZE; + return (-1); + } + if (len < 0) + len = srcp - src + 1; + srcp = msg + (((n & 0x3f) << 8) | (*srcp & 0xff)); + if (srcp < msg || srcp >= eom) { /* Out of range. */ + errno = EMSGSIZE; + return (-1); + } + checked += 2; + /* + * Check for loops in the compressed name; + * if we've looked at the whole message, + * there must be a loop. + */ + if (checked >= eom - msg) { + errno = EMSGSIZE; + return (-1); + } + break; + + default: + errno = EMSGSIZE; + return (-1); /* flag error */ + } + } + *dstp = '\0'; + if (len < 0) + len = srcp - src; + return (len); +} + +/* + * irc_ns_name_ntop(src, dst, dstsiz) + * Convert an encoded domain name to printable ascii as per RFC1035. + * return: + * Number of bytes written to buffer, or -1 (with errno set) + * notes: + * The root is returned as "." + * All other domains are returned in non absolute form + */ +static int +irc_ns_name_ntop(const char *src, char *dst, size_t dstsiz) +{ + const char *cp; + char *dn, *eom; + unsigned char c; + unsigned int n; + int l; + + cp = src; + dn = dst; + eom = dst + dstsiz; + + while ((n = *cp++) != 0) { + if ((n & NS_CMPRSFLGS) == NS_CMPRSFLGS) { + /* Some kind of compression pointer. */ + errno = EMSGSIZE; + return (-1); + } + if (dn != dst) { + if (dn >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '.'; + } + if ((l = labellen((const unsigned char*)(cp - 1))) < 0) { + errno = EMSGSIZE; /* XXX */ + return(-1); + } + if (dn + l >= eom) { + errno = EMSGSIZE; + return (-1); + } + if ((n & NS_CMPRSFLGS) == NS_TYPE_ELT) { + int m; + + if (n != DNS_LABELTYPE_BITSTRING) { + /* XXX: labellen should reject this case */ + errno = EINVAL; + return(-1); + } + if ((m = irc_decode_bitstring(&cp, dn, eom)) < 0) + { + errno = EMSGSIZE; + return(-1); + } + dn += m; + continue; + } + for ((void)NULL; l > 0; l--) { + c = *cp++; + if (special(c)) { + if (dn + 1 >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '\\'; + *dn++ = (char)c; + } else if (!printable(c)) { + if (dn + 3 >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '\\'; + *dn++ = digits[c / 100]; + *dn++ = digits[(c % 100) / 10]; + *dn++ = digits[c % 10]; + } else { + if (dn >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = (char)c; + } + } + } + if (dn == dst) { + if (dn >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '.'; + } + if (dn >= eom) { + errno = EMSGSIZE; + return (-1); + } + *dn++ = '\0'; + return (dn - dst); +} + +/* + * Pack domain name 'exp_dn' in presentation form into 'comp_dn'. + * Return the size of the compressed name or -1. + * 'length' is the size of the array pointed to by 'comp_dn'. + */ +static int +irc_dn_comp(const char *src, unsigned char *dst, int dstsiz, + const unsigned char **dnptrs, const unsigned char **lastdnptr) +{ + return(irc_ns_name_compress(src, dst, (size_t)dstsiz, + (const unsigned char **)dnptrs, + (const unsigned char **)lastdnptr)); +} + +/* + * Skip over a compressed domain name. Return the size or -1. + */ +int +irc_dn_skipname(const unsigned char *ptr, const unsigned char *eom) { + const unsigned char *saveptr = ptr; + + if (irc_ns_name_skip(&ptr, eom) == -1) + return(-1); + return(ptr - saveptr); +} + +/* + * ns_name_skip(ptrptr, eom) + * Advance *ptrptr to skip over the compressed name it points at. + * return: + * 0 on success, -1 (with errno set) on failure. + */ +static int +irc_ns_name_skip(const unsigned char **ptrptr, const unsigned char *eom) +{ + const unsigned char *cp; + unsigned int n; + int l; + + cp = *ptrptr; + + while (cp < eom && (n = *cp++) != 0) + { + /* Check for indirection. */ + switch (n & NS_CMPRSFLGS) + { + case 0: /* normal case, n == len */ + cp += n; + continue; + case NS_TYPE_ELT: /* EDNS0 extended label */ + if ((l = labellen(cp - 1)) < 0) + { + errno = EMSGSIZE; /* XXX */ + return(-1); + } + + cp += l; + continue; + case NS_CMPRSFLGS: /* indirection */ + cp++; + break; + default: /* illegal type */ + errno = EMSGSIZE; + return(-1); + } + + break; + } + + if (cp > eom) + { + errno = EMSGSIZE; + return (-1); + } + + *ptrptr = cp; + return(0); +} + +unsigned int +irc_ns_get16(const unsigned char *src) +{ + unsigned int dst; + + IRC_NS_GET16(dst, src); + return(dst); +} + +unsigned long +irc_ns_get32(const unsigned char *src) +{ + unsigned long dst; + + IRC_NS_GET32(dst, src); + return(dst); +} + +void +irc_ns_put16(unsigned int src, unsigned char *dst) +{ + IRC_NS_PUT16(src, dst); +} + +void +irc_ns_put32(unsigned long src, unsigned char *dst) +{ + IRC_NS_PUT32(src, dst); +} + +/* From ns_name.c */ + +/* + * special(ch) + * Thinking in noninternationalized USASCII (per the DNS spec), + * is this characted special ("in need of quoting") ? + * return: + * boolean. + */ +static int +special(int ch) +{ + switch (ch) + { + case 0x22: /* '"' */ + case 0x2E: /* '.' */ + case 0x3B: /* ';' */ + case 0x5C: /* '\\' */ + case 0x28: /* '(' */ + case 0x29: /* ')' */ + /* Special modifiers in zone files. */ + case 0x40: /* '@' */ + case 0x24: /* '$' */ + return(1); + default: + return(0); + } +} + +static int +labellen(const unsigned char *lp) +{ + int bitlen; + unsigned char l = *lp; + + if ((l & NS_CMPRSFLGS) == NS_CMPRSFLGS) + { + /* should be avoided by the caller */ + return(-1); + } + + if ((l & NS_CMPRSFLGS) == NS_TYPE_ELT) + { + if (l == DNS_LABELTYPE_BITSTRING) + { + if ((bitlen = *(lp + 1)) == 0) + bitlen = 256; + return((bitlen + 7 ) / 8 + 1); + } + + return(-1); /* unknwon ELT */ + } + + return(l); +} + + +/* + * printable(ch) + * Thinking in noninternationalized USASCII (per the DNS spec), + * is this character visible and not a space when printed ? + * return: + * boolean. + */ +static int +printable(int ch) +{ + return(ch > 0x20 && ch < 0x7f); +} + +static int +irc_decode_bitstring(const char **cpp, char *dn, const char *eom) +{ + const char *cp = *cpp; + char *beg = dn, tc; + int b, blen, plen; + + if ((blen = (*cp & 0xff)) == 0) + blen = 256; + plen = (blen + 3) / 4; + plen += sizeof("\\[x/]") + (blen > 99 ? 3 : (blen > 9) ? 2 : 1); + if (dn + plen >= eom) + return(-1); + + cp++; + dn += sprintf(dn, "\\[x"); + for (b = blen; b > 7; b -= 8, cp++) + dn += sprintf(dn, "%02x", *cp & 0xff); + if (b > 4) { + tc = *cp++; + dn += sprintf(dn, "%02x", tc & (0xff << (8 - b))); + } else if (b > 0) { + tc = *cp++; + dn += sprintf(dn, "%1x", + ((tc >> 4) & 0x0f) & (0x0f << (4 - b))); + } + dn += sprintf(dn, "/%d]", blen); + + *cpp = cp; + return(dn - beg); +} + +/* + * irc_ns_name_pton(src, dst, dstsiz) + * Convert a ascii string into an encoded domain name as per RFC1035. + * return: + * -1 if it fails + * 1 if string was fully qualified + * 0 is string was not fully qualified + * notes: + * Enforces label and domain length limits. + */ +static int +irc_ns_name_pton(const char *src, unsigned char *dst, size_t dstsiz) +{ + unsigned char *label, *bp, *eom; + char *cp; + int c, n, escaped, e = 0; + + escaped = 0; + bp = dst; + eom = dst + dstsiz; + label = bp++; + + + while ((c = *src++) != 0) { + if (escaped) { + if (c == '[') { /* start a bit string label */ + if ((cp = strchr(src, ']')) == NULL) { + errno = EINVAL; /* ??? */ + return(-1); + } + if ((e = irc_encode_bitsring(&src, + cp + 2, + &label, + &bp, + (const char *)eom)) + != 0) { + errno = e; + return(-1); + } + escaped = 0; + label = bp++; + if ((c = *src++) == 0) + goto done; + else if (c != '.') { + errno = EINVAL; + return(-1); + } + continue; + } + else if ((cp = strchr(digits, c)) != NULL) { + n = (cp - digits) * 100; + if ((c = *src++) == 0 || + (cp = strchr(digits, c)) == NULL) { + errno = EMSGSIZE; + return (-1); + } + n += (cp - digits) * 10; + if ((c = *src++) == 0 || + (cp = strchr(digits, c)) == NULL) { + errno = EMSGSIZE; + return (-1); + } + n += (cp - digits); + if (n > 255) { + errno = EMSGSIZE; + return (-1); + } + c = n; + } + escaped = 0; + } else if (c == '\\') { + escaped = 1; + continue; + } else if (c == '.') { + c = (bp - label - 1); + if ((c & NS_CMPRSFLGS) != 0) { /* Label too big. */ + errno = EMSGSIZE; + return (-1); + } + if (label >= eom) { + errno = EMSGSIZE; + return (-1); + } + *label = c; + /* Fully qualified ? */ + if (*src == '\0') { + if (c != 0) { + if (bp >= eom) { + errno = EMSGSIZE; + return (-1); + } + *bp++ = '\0'; + } + if ((bp - dst) > NS_MAXCDNAME) { + errno = EMSGSIZE; + return (-1); + } + return (1); + } + if (c == 0 || *src == '.') { + errno = EMSGSIZE; + return (-1); + } + label = bp++; + continue; + } + if (bp >= eom) { + errno = EMSGSIZE; + return (-1); + } + *bp++ = (unsigned char)c; + } + c = (bp - label - 1); + if ((c & NS_CMPRSFLGS) != 0) { /* Label too big. */ + errno = EMSGSIZE; + return (-1); + } + done: + if (label >= eom) { + errno = EMSGSIZE; + return (-1); + } + *label = c; + if (c != 0) { + if (bp >= eom) { + errno = EMSGSIZE; + return (-1); + } + *bp++ = 0; + } + + if ((bp - dst) > NS_MAXCDNAME) + { /* src too big */ + errno = EMSGSIZE; + return (-1); + } + + return (0); +} + +/* + * irc_ns_name_pack(src, dst, dstsiz, dnptrs, lastdnptr) + * Pack domain name 'domain' into 'comp_dn'. + * return: + * Size of the compressed name, or -1. + * notes: + * 'dnptrs' is an array of pointers to previous compressed names. + * dnptrs[0] is a pointer to the beginning of the message. The array + * ends with NULL. + * 'lastdnptr' is a pointer to the end of the array pointed to + * by 'dnptrs'. + * Side effects: + * The list of pointers in dnptrs is updated for labels inserted into + * the message as we compress the name. If 'dnptr' is NULL, we don't + * try to compress names. If 'lastdnptr' is NULL, we don't update the + * list. + */ +static int +irc_ns_name_pack(const unsigned char *src, unsigned char *dst, int dstsiz, + const unsigned char **dnptrs, const unsigned char **lastdnptr) +{ + unsigned char *dstp; + const unsigned char **cpp, **lpp, *eob, *msg; + const unsigned char *srcp; + int n, l, first = 1; + + srcp = src; + dstp = dst; + eob = dstp + dstsiz; + lpp = cpp = NULL; + if (dnptrs != NULL) { + if ((msg = *dnptrs++) != NULL) { + for (cpp = dnptrs; *cpp != NULL; cpp++) + (void)NULL; + lpp = cpp; /* end of list to search */ + } + } else + msg = NULL; + + /* make sure the domain we are about to add is legal */ + l = 0; + do { + int l0; + + n = *srcp; + if ((n & NS_CMPRSFLGS) == NS_CMPRSFLGS) { + errno = EMSGSIZE; + return (-1); + } + if ((l0 = labellen(srcp)) < 0) { + errno = EINVAL; + return(-1); + } + l += l0 + 1; + if (l > NS_MAXCDNAME) { + errno = EMSGSIZE; + return (-1); + } + srcp += l0 + 1; + } while (n != 0); + + /* from here on we need to reset compression pointer array on error */ + srcp = src; + do { + /* Look to see if we can use pointers. */ + n = *srcp; + if (n != 0 && msg != NULL) { + l = irc_dn_find(srcp, msg, (const unsigned char * const *)dnptrs, + (const unsigned char * const *)lpp); + if (l >= 0) { + if (dstp + 1 >= eob) { + goto cleanup; + } + *dstp++ = (l >> 8) | NS_CMPRSFLGS; + *dstp++ = l % 256; + return (dstp - dst); + } + /* Not found, save it. */ + if (lastdnptr != NULL && cpp < lastdnptr - 1 && + (dstp - msg) < 0x4000 && first) { + *cpp++ = dstp; + *cpp = NULL; + first = 0; + } + } + /* copy label to buffer */ + if ((n & NS_CMPRSFLGS) == NS_CMPRSFLGS) { + /* Should not happen. */ + goto cleanup; + } + n = labellen(srcp); + if (dstp + 1 + n >= eob) { + goto cleanup; + } + memcpy(dstp, srcp, n + 1); + srcp += n + 1; + dstp += n + 1; + } while (n != 0); + + if (dstp > eob) { +cleanup: + if (msg != NULL) + *lpp = NULL; + errno = EMSGSIZE; + return (-1); + } + return(dstp - dst); +} + +static int +irc_ns_name_compress(const char *src, unsigned char *dst, size_t dstsiz, + const unsigned char **dnptrs, const unsigned char **lastdnptr) +{ + unsigned char tmp[NS_MAXCDNAME]; + + if (irc_ns_name_pton(src, tmp, sizeof tmp) == -1) + return(-1); + return(irc_ns_name_pack(tmp, dst, dstsiz, dnptrs, lastdnptr)); +} + +static int +irc_encode_bitsring(const char **bp, const char *end, unsigned char **labelp, + unsigned char **dst, const char *eom) +{ + int afterslash = 0; + const char *cp = *bp; + char *tp, c; + const char *beg_blen; + char *end_blen = NULL; + int value = 0, count = 0, tbcount = 0, blen = 0; + + beg_blen = end_blen = NULL; + + /* a bitstring must contain at least 2 characters */ + if (end - cp < 2) + return(EINVAL); + + /* XXX: currently, only hex strings are supported */ + if (*cp++ != 'x') + return(EINVAL); + if (!isxdigit((*cp) & 0xff)) /* reject '\[x/BLEN]' */ + return(EINVAL); + + for (tp = (char*)(dst + 1); cp < end && tp < eom; cp++) { + switch((c = *cp)) { + case ']': /* end of the bitstring */ + if (afterslash) { + if (beg_blen == NULL) + return(EINVAL); + blen = (int)strtol(beg_blen, &end_blen, 10); + if (*end_blen != ']') + return(EINVAL); + } + if (count) + *tp++ = ((value << 4) & 0xff); + cp++; /* skip ']' */ + goto done; + case '/': + afterslash = 1; + break; + default: + if (afterslash) { + if (!isdigit(c&0xff)) + return(EINVAL); + if (beg_blen == NULL) { + + if (c == '0') { + /* blen never begings with 0 */ + return(EINVAL); + } + beg_blen = cp; + } + } else { + if (!isxdigit(c&0xff)) + return(EINVAL); + value <<= 4; + value += digitvalue[(int)c]; + count += 4; + tbcount += 4; + if (tbcount > 256) + return(EINVAL); + if (count == 8) { + *tp++ = value; + count = 0; + } + } + break; + } + } + done: + if (cp >= end || tp >= eom) + return(EMSGSIZE); + + /* + * bit length validation: + * If a is present, the number of digits in the + * MUST be just sufficient to contain the number of bits specified + * by the . If there are insignificant bits in a final + * hexadecimal or octal digit, they MUST be zero. + * RFC 2673, Section 3.2. + */ + if (blen > 0) { + int traillen; + + if (((blen + 3) & ~3) != tbcount) + return(EINVAL); + traillen = tbcount - blen; /* between 0 and 3 */ + if (((value << (8 - traillen)) & 0xff) != 0) + return(EINVAL); + } + else + blen = tbcount; + if (blen == 256) + blen = 0; + + /* encode the type and the significant bit fields */ + **labelp = DNS_LABELTYPE_BITSTRING; + **dst = blen; + + *bp = cp; + *dst = (unsigned char*)tp; + + return(0); +} + +/* + * dn_find(domain, msg, dnptrs, lastdnptr) + * Search for the counted-label name in an array of compressed names. + * return: + * offset from msg if found, or -1. + * notes: + * dnptrs is the pointer to the first name on the list, + * not the pointer to the start of the message. + */ +static int +irc_dn_find(const unsigned char *domain, const unsigned char *msg, + const unsigned char * const *dnptrs, + const unsigned char * const *lastdnptr) +{ + const unsigned char *dn, *cp, *sp; + const unsigned char * const *cpp; + unsigned int n; + + for (cpp = dnptrs; cpp < lastdnptr; cpp++) + { + sp = *cpp; + /* + * terminate search on: + * root label + * compression pointer + * unusable offset + */ + while (*sp != 0 && (*sp & NS_CMPRSFLGS) == 0 && + (sp - msg) < 0x4000) { + dn = domain; + cp = sp; + while ((n = *cp++) != 0) { + /* + * check for indirection + */ + switch (n & NS_CMPRSFLGS) { + case 0: /* normal case, n == len */ + n = labellen(cp - 1); /* XXX */ + + if (n != *dn++) + goto next; + + for ((void)NULL; n > 0; n--) + if (mklower(*dn++) != + mklower(*cp++)) + goto next; + /* Is next root for both ? */ + if (*dn == '\0' && *cp == '\0') + return (sp - msg); + if (*dn) + continue; + goto next; + case NS_CMPRSFLGS: /* indirection */ + cp = msg + (((n & 0x3f) << 8) | *cp); + break; + + default: /* illegal type */ + errno = EMSGSIZE; + return (-1); + } + } + next: ; + sp += *sp + 1; + } + } + errno = ENOENT; + return (-1); +} + +/* + * * Thinking in noninternationalized USASCII (per the DNS spec), + * * convert this character to lower case if it's upper case. + * */ +static int +mklower(int ch) +{ + if (ch >= 0x41 && ch <= 0x5A) + return(ch + 0x20); + + return(ch); +} + +/* From resolv/mkquery.c */ + +/* + * Form all types of queries. + * Returns the size of the result or -1. + */ +int +irc_res_mkquery( + const char *dname, /* domain name */ + int class, int type, /* class and type of query */ + unsigned char *buf, /* buffer to put query */ + int buflen) /* size of buffer */ +{ + HEADER *hp; + unsigned char *cp; + int n; + const unsigned char *dnptrs[20], **dpp, **lastdnptr; + + /* + * Initialize header fields. + */ + if ((buf == NULL) || (buflen < HFIXEDSZ)) + return (-1); + memset(buf, 0, HFIXEDSZ); + hp = (HEADER *)(void *)buf; + + hp->id = 0; + hp->opcode = QUERY; + hp->rd = 1; /* recurse */ + hp->rcode = NO_ERRORS; + cp = buf + HFIXEDSZ; + buflen -= HFIXEDSZ; + dpp = dnptrs; + *dpp++ = buf; + *dpp++ = NULL; + lastdnptr = dnptrs + sizeof dnptrs / sizeof dnptrs[0]; + + if ((buflen -= QFIXEDSZ) < 0) + return (-1); + if ((n = irc_dn_comp(dname, cp, buflen, dnptrs, lastdnptr)) < 0) + return (-1); + + cp += n; + buflen -= n; + IRC_NS_PUT16(type, cp); + IRC_NS_PUT16(class, cp); + hp->qdcount = htons(1); + + return (cp - buf); +} diff --git a/authd/reslib.h b/authd/reslib.h new file mode 100644 index 000000000..52699b6d9 --- /dev/null +++ b/authd/reslib.h @@ -0,0 +1,127 @@ +/* + * include/irc_reslib.h + * + * $Id: reslib.h 446 2006-02-12 02:46:54Z db $ + */ + +#ifndef _CHARYBDIS_RESLIB_H +#define _CHARYBDIS_RESLIB_H + +/* Longest hostname we're willing to work with. + * Due to DNSBLs this is more than HOSTLEN. + */ +#define IRCD_RES_HOSTLEN 255 + +/* Here we define some values lifted from nameser.h */ +#define NS_NOTIFY_OP 4 +#define NS_INT16SZ 2 +#define NS_IN6ADDRSZ 16 +#define NS_INADDRSZ 4 +#define NS_INT32SZ 4 +#define NS_CMPRSFLGS 0xc0 +#define NS_MAXCDNAME 255 +#define QUERY 0 +#define IQUERY 1 +#define NO_ERRORS 0 +#define SERVFAIL 2 +#define NXDOMAIN 3 +#define NOTIMP 4 +#define REFUSED 5 +#define T_A 1 +#define T_AAAA 28 +#define T_PTR 12 +#define T_CNAME 5 +#define T_NULL 10 +#define C_IN 1 +#define QFIXEDSZ 4 +#define RRFIXEDSZ 10 +#define HFIXEDSZ 12 + +typedef struct +{ + unsigned id :16; /* query identification number */ +#ifdef WORDS_BIGENDIAN + /* fields in third byte */ + unsigned qr: 1; /* response flag */ + unsigned opcode: 4; /* purpose of message */ + unsigned aa: 1; /* authoritive answer */ + unsigned tc: 1; /* truncated message */ + unsigned rd: 1; /* recursion desired */ + /* fields in fourth byte */ + unsigned ra: 1; /* recursion available */ + unsigned unused :1; /* unused bits (MBZ as of 4.9.3a3) */ + unsigned ad: 1; /* authentic data from named */ + unsigned cd: 1; /* checking disabled by resolver */ + unsigned rcode :4; /* response code */ +#else + /* fields in third byte */ + unsigned rd :1; /* recursion desired */ + unsigned tc :1; /* truncated message */ + unsigned aa :1; /* authoritive answer */ + unsigned opcode :4; /* purpose of message */ + unsigned qr :1; /* response flag */ + /* fields in fourth byte */ + unsigned rcode :4; /* response code */ + unsigned cd: 1; /* checking disabled by resolver */ + unsigned ad: 1; /* authentic data from named */ + unsigned unused :1; /* unused bits (MBZ as of 4.9.3a3) */ + unsigned ra :1; /* recursion available */ +#endif + /* remaining bytes */ + unsigned qdcount :16; /* number of question entries */ + unsigned ancount :16; /* number of answer entries */ + unsigned nscount :16; /* number of authority entries */ + unsigned arcount :16; /* number of resource entries */ +} HEADER; + +/* + * Inline versions of get/put short/long. Pointer is advanced. + */ +#define IRC_NS_GET16(s, cp) { \ + const unsigned char *t_cp = (const unsigned char *)(cp); \ + (s) = ((u_int16_t)t_cp[0] << 8) \ + | ((u_int16_t)t_cp[1]) \ + ; \ + (cp) += NS_INT16SZ; \ +} + +#define IRC_NS_GET32(l, cp) { \ + const unsigned char *t_cp = (const unsigned char *)(cp); \ + (l) = ((u_int32_t)t_cp[0] << 24) \ + | ((u_int32_t)t_cp[1] << 16) \ + | ((u_int32_t)t_cp[2] << 8) \ + | ((u_int32_t)t_cp[3]) \ + ; \ + (cp) += NS_INT32SZ; \ +} + +#define IRC_NS_PUT16(s, cp) { \ + u_int16_t t_s = (u_int16_t)(s); \ + unsigned char *t_cp = (unsigned char *)(cp); \ + *t_cp++ = t_s >> 8; \ + *t_cp = t_s; \ + (cp) += NS_INT16SZ; \ +} + +#define IRC_NS_PUT32(l, cp) { \ + u_int32_t t_l = (u_int32_t)(l); \ + unsigned char *t_cp = (unsigned char *)(cp); \ + *t_cp++ = t_l >> 24; \ + *t_cp++ = t_l >> 16; \ + *t_cp++ = t_l >> 8; \ + *t_cp = t_l; \ + (cp) += NS_INT32SZ; \ +} + +extern int irc_res_init(void); +extern int irc_dn_expand(const unsigned char *msg, const unsigned char *eom, const unsigned char *src, char *dst, int dstsiz); +extern int irc_dn_skipname(const unsigned char *ptr, const unsigned char *eom); +extern unsigned int irc_ns_get16(const unsigned char *src); +extern unsigned long irc_ns_get32(const unsigned char *src); +extern void irc_ns_put16(unsigned int src, unsigned char *dst); +extern void irc_ns_put32(unsigned long src, unsigned char *dst); +extern int irc_res_mkquery(const char *dname, int class, int type, unsigned char *buf, int buflen); + +extern char irc_domain[IRCD_RES_HOSTLEN + 1]; + +#endif