diff options
Diffstat (limited to 'src/dnsstub')
-rw-r--r-- | src/dnsstub/Makefile | 23 | ||||
-rw-r--r-- | src/dnsstub/README.md | 171 | ||||
-rw-r--r-- | src/dnsstub/TARGETS | 18 | ||||
-rw-r--r-- | src/dnsstub/dns_cname.c | 59 | ||||
-rw-r--r-- | src/dnsstub/dns_dfd.c | 76 | ||||
-rw-r--r-- | src/dnsstub/dns_domain.c | 80 | ||||
-rw-r--r-- | src/dnsstub/dns_dtda.c | 43 | ||||
-rw-r--r-- | src/dnsstub/dns_ip.c | 198 | ||||
-rw-r--r-- | src/dnsstub/dns_ipq.c | 236 | ||||
-rw-r--r-- | src/dnsstub/dns_mx.c | 63 | ||||
-rw-r--r-- | src/dnsstub/dns_name.c | 80 | ||||
-rw-r--r-- | src/dnsstub/dns_nd.c | 48 | ||||
-rw-r--r-- | src/dnsstub/dns_packet.c | 85 | ||||
-rw-r--r-- | src/dnsstub/dns_random.c | 70 | ||||
-rw-r--r-- | src/dnsstub/dns_rcip.c | 114 | ||||
-rw-r--r-- | src/dnsstub/dns_rcrw.c | 141 | ||||
-rw-r--r-- | src/dnsstub/dns_resolve.c | 39 | ||||
-rw-r--r-- | src/dnsstub/dns_sortip.c | 45 | ||||
-rw-r--r-- | src/dnsstub/dns_transmit.c | 436 | ||||
-rw-r--r-- | src/dnsstub/dns_txt.c | 64 |
20 files changed, 2089 insertions, 0 deletions
diff --git a/src/dnsstub/Makefile b/src/dnsstub/Makefile new file mode 100644 index 0000000..ee7f94d --- /dev/null +++ b/src/dnsstub/Makefile @@ -0,0 +1,23 @@ + +COMPILE=../compile +MAKELIB=../makelib +CCFLAGS=-I../../include + +default: clean check dnsresolv.a + +check: + @[ -f $(COMPILE) ] && [ -f $(MAKELIB) ] || ( cd .. ; ./configure ; ) + +clean: + rm -f `cat TARGETS` + +dnsresolv.a: \ +dns_domain.c dns_dtda.c dns_ip.c dns_ipq.c dns_name.c \ +dns_nd.c dns_packet.c dns_random.c dns_rcip.c dns_rcrw.c dns_resolve.c \ +dns_sortip.c dns_transmit.c dns_txt.c + $(COMPILE) $(CCFLAGS) dns_domain.c dns_dfd.c dns_dtda.c dns_ip.c dns_ipq.c \ + dns_mx.c dns_name.c dns_nd.c dns_packet.c dns_random.c dns_rcip.c \ + dns_rcrw.c dns_resolve.c dns_sortip.c dns_transmit.c dns_txt.c dns_cname.c + $(MAKELIB) dnsresolv.a dns_domain.o dns_dfd.o dns_dtda.o dns_ip.o dns_ipq.o \ + dns_mx.o dns_name.o dns_nd.o dns_packet.o dns_random.o dns_rcip.o \ + dns_rcrw.o dns_resolve.o dns_sortip.o dns_transmit.o dns_txt.o dns_cname.o diff --git a/src/dnsstub/README.md b/src/dnsstub/README.md new file mode 100644 index 0000000..95cd11c --- /dev/null +++ b/src/dnsstub/README.md @@ -0,0 +1,171 @@ +/*! \mainpage + +Stub Resolver +============= + +Simple DJBDNS stub-resolver based on 'djbdns-1.05(IPv6)' allowing for each +calling application individually to include up to 16 DNSCACHEIP(s) +as DNS forwarding/resolving servers to be tried sequentially. + +IP Addresses +------------ + +Here, + - global IPv6, + - IPv6 ULA, and + - IPv6 LLU addresses with a given Interface-Id +can be specified. The IPv4 format could be either a + - legacy dotted-decimal or a + - IPv4-mapped IPv6 address. + +In any case, compactified IPv6 addresses are understood. +IPv4/IPv6 addresses in brackets are understood by dns_ip. + +Resolver Call +------------- + +If $DNSCACHEIP is not provided as environment variable, the stub-resolver +will use the system-wide + - /etc/resolv.conf +file; however now without the capability for IPv6 LLU addresses. +While IPv4-mapped IPv6 addresses are supported here by default as well, +care has to taken not to jeopardize other client's usage. + +Name Qualification +------------------ + +If provided, the stub-resolver uses either a system-wide configuration file + - /etc/dnsrewritefile or assumes this file to available as given in + - $DNSREWRITEFILE +in order to define persistent mapping-rules of local domain names to public +ones (for lookup) or IP addresses (for direct matching). + +Well-known domain names 'localhost', 'ip4-loopback' and 'ip6-loopback' +are handled locally, thus no DNS query is used (RFC 6761). +'localhost' is advertised as '::1' and '::ff:127.0.0.1' in it's native +IPv6 format. It is up to the caller to convert the IPv6-mapped IPv4 +address to the IPv4 format. + +Local domain names can be alternatively specified (per application) using +the environment variable + - $LOCALDOMAIN +to be appended to unqualified hostnames dynamically. This is roughly equivalent +with the 'search' string in /etc/resolv. Several domains names may be +specified within $LOCALDOMAIN separated by blanks. + +See: https://cr.yp.to/djbdns/qualify.html + + +Specific DNS Record type lookup +------------------------------- + +* dns_ip (A, AAAAA) +* dns_name (PTR) +* dns_cname (CNAME) +* dns_txt (TXT) -- now considering several 'labels' +* dns_mx (MX) + + +Internals +--------- + +* UDP message size: +Unlike other implementations, this DNS stub-resolver supports UDP packet +sizes up to 1028 byte without the need for (E)DNS0 packet enhancements. + +* DNS UDP query retrials: +In case the NS is not able to initally reply to the query, +it is retried again at the intervalls {1, 2, 4, 8, 16} secs. + +* DNS name qualification (dns_ip_qualify): +Well-known domain names are qualified locally without invoking a DNS query +while handling IPv4 and IPv6 addresses separately. + +* NS qualification/sorting for NS replies: +NS qualification is not supported (yet), thus we use a randomly sorted +list of NS IP addresses. + +* Query/Reply to/from DNS Cache servers/forwarders: +Neither message (CurveDNS) nor transport layer (TLS) encryption is provided; +the sub-resolver 'trusts' it's upstream caches/forwarders. We recommend to +setup communication on private IPv4/IPv6 addresses; if applicable. + +* DNS TXT Records: +The label substructure is now recognized in the RDATA section; +each label may have the size of 255 byte. +The length information is excluded from the output. +Only printable characters are recognized in the output. + +* Return Codes: +Different from DJB's initial routines, the DNS front-end routines + dns_cname*, dns_ip*, dns_mx*, dns_name*, dns dns_txt* +return now the number of replies received (not bytes!). +Thus, three cases need to be considered: + + - rc < 0: Problem occured (SOFTFAIL, HARDFAIL) + - rc = 0: No answer obtained (but query was successful) = NXDOMAIN + - rc > 0: rc answers received; positive reply + +For return codes < 0, the following conventions have been applied: + + include/dnsresolv.h + +\#define DNS_NXD 0 +\#define DNS_MEM -1 +\#define DNS_ERR -2 /* parsing errors and others */ +\#define DNS_COM -3 /* (socket) communication errors */ +\#define DNS_INT -4 /* internal errors */ +\#define DNS_SOFT -5 /* either -2 or -3 */ +\#define DNS_HARD -6 /* CNAME loop problem */ + +The modification of the return code is typically not problematic, +since mostly just rc = -1 is checked. + +In the future, these return codes are subject of change. +Thus, instead of + + if (dns_XX(...) == -1) + +one shoud use the more general syntax + + if (dns_XX(...) < 0) + +to check for 'negative' results, allowing further actions +and refinements given the calling sequence. + + + +Environment Variables Read +-------------------------- + +$DNSCACHEPIP The upstream resolver's IP[v4|v6] addresses (up to 32). + IPv6 LLU addresses may be suffixed with the interface name. +$DNSREWRITEFILE Alternate location for the system-wide + /etc/dnsrewrite +file +$LOCALDOMAIN Additional local domain name appended to unqualified + hostnames dynamically. + +Sample for the file /etc/dnsrewrite: + +\#annything.local -> me +\-.example.com:me +\# me -> 127.0.0.1 +\=me:127.0.0.1 +\# any.name.a -> any.name.af.mil +\*.a:.af.mil +\# any-name-without-dots -> any-name-without-dots.heaven.af.mil +\?:.heaven.af.mil +\# remove trailing dot +\*.: + +and DJB's explanations are given here: + +Instructions are followed in order, each at most once. There are four types of instructions: + +\=post:new means that the host name post is replaced by new. +\*post:new means that any name of the form prepost is replaced by prenew. +\?post:new means that any name of the form prepost, where pre does not contain dots or brackets, is replaced by prenew. +\-post:new means that any name of the form prepost is replaced by new. + +Erwin Hoffmann, June 2023. diff --git a/src/dnsstub/TARGETS b/src/dnsstub/TARGETS new file mode 100644 index 0000000..62f40be --- /dev/null +++ b/src/dnsstub/TARGETS @@ -0,0 +1,18 @@ +dns_cname.o +dns_dfd.o +dns_domain.o +dns_dtda.o +dns_ip.o +dns_ipq.o +dns_mx.o +dns_name.o +dns_nd.o +dns_packet.o +dns_random.o +dns_rcip.o +dns_rcrw.o +dns_resolve.o +dns_sortip.o +dns_transmit.o +dns_txt.o +dnsresolv.a diff --git a/src/dnsstub/dns_cname.c b/src/dnsstub/dns_cname.c new file mode 100644 index 0000000..408949a --- /dev/null +++ b/src/dnsstub/dns_cname.c @@ -0,0 +1,59 @@ +#include "stralloc.h" +#include "uint_t.h" +#include "byte.h" +#include "ip.h" +#include "case.h" +#include "dnsresolv.h" + +/** + @file dns_cname.c + @author feh + @brief DNS cname lookup +*/ + +static char *q = 0; + +int dns_cname_packet(stralloc *out,const char *buf,unsigned int len) +{ + unsigned int pos; + char header[12]; + uint16 numanswers; + uint16 datalen; + int ranswers = 0; + + if (!stralloc_copys(out,"")) return DNS_MEM; + + pos = dns_packet_copy(buf,len,0,header,12); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 6,&numanswers); + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos += 4; + + while (numanswers--) { + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 8,&datalen); + if (byte_equal(header,2,DNS_T_CNAME)) + if (byte_equal(header + 2,2,DNS_C_IN)) { + if (!dns_packet_getname(buf,len,pos,&q)) return DNS_ERR; + if (dns_domain_todot_cat(out,q) <= 0) return DNS_ERR; + } + pos += datalen; + ++ranswers; + } + + return ranswers; +} + +int dns_cname(stralloc *out,stralloc *fqdn) +{ + int rc; + + if (dns_domain_fromdot(&q,fqdn->s,fqdn->len) <= 0) return DNS_ERR; + if (dns_resolve(q,DNS_T_CNAME) < 0) return DNS_ERR; + if ((rc = dns_cname_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen)) < 0) return DNS_ERR; + dns_transmit_free(&dns_resolve_tx); + dns_domain_free(&q); + if (case_equals(out->s,fqdn->s)) rc = -6; // loop DNS_HARD + + return rc; +} diff --git a/src/dnsstub/dns_dfd.c b/src/dnsstub/dns_dfd.c new file mode 100644 index 0000000..756a1f8 --- /dev/null +++ b/src/dnsstub/dns_dfd.c @@ -0,0 +1,76 @@ +#include "error.h" +#include "alloc.h" +#include "byte.h" +#include "dnsresolv.h" + +/** + @file dns_dfd.c + @author djb + @source ucspi-tcp + @brief domain name qualification (domain from dot) +*/ + +int dns_domain_fromdot(char **out,const char *buf,unsigned int n) +{ + char label[63]; + unsigned int labellen = 0; /* <= sizeof label */ + char name[255]; + unsigned int namelen = 0; /* <= sizeof name */ + char ch; + char *x; + + errno = EPROTO; + + for (;;) { + if (!n) break; + ch = *buf++; --n; + if (ch == '.') { + if (labellen) { + if (namelen + labellen + 1 > sizeof(name)) return 0; + name[namelen++] = labellen; + byte_copy(name + namelen,labellen,label); + namelen += labellen; + labellen = 0; + } + continue; + } + if (ch == '\\') { // octal -> decimal + if (!n) break; + ch = *buf++; --n; + if ((ch >= '0') && (ch <= '7')) { + ch -= '0'; + if (n && (*buf >= '0') && (*buf <= '7')) { + ch <<= 3; + ch += *buf - '0'; + ++buf; --n; + if (n && (*buf >= '0') && (*buf <= '7')) { + ch <<= 3; + ch += *buf - '0'; + ++buf; --n; + } + } + } + } + if (labellen >= sizeof(label)) return 0; + label[labellen++] = ch; + } + + if (labellen) { + if (namelen + labellen + 1 > sizeof(name)) return 0; + name[namelen++] = labellen; + byte_copy(name + namelen,labellen,label); + namelen += labellen; + labellen = 0; + } + + if (namelen + 1 > sizeof(name)) return 0; + name[namelen++] = 0; + + x = alloc(namelen); + if (!x) return DNS_MEM; + byte_copy(x,namelen,name); + + if (*out) alloc_free(*out); + *out = x; + return 1; +} diff --git a/src/dnsstub/dns_domain.c b/src/dnsstub/dns_domain.c new file mode 100644 index 0000000..654a827 --- /dev/null +++ b/src/dnsstub/dns_domain.c @@ -0,0 +1,80 @@ +#include "alloc.h" +#include "case.h" +#include "byte.h" +#include "dnsresolv.h" + +/** + @file dns_domain.c + @author djb + @source ucspi-tcp + @brief domain qualification +*/ + +unsigned int dns_domain_length(const char *dn) +{ + const char *x; + unsigned char c; + + x = dn; + while ((c = *x++)) + x += (unsigned int) c; + return x - dn; +} + +void dns_domain_free(char **out) +{ + if (*out) { + alloc_free(*out); + *out = 0; + } +} + +int dns_domain_copy(char **out,const char *in) +{ + unsigned int len; + char *x; + + len = dns_domain_length(in); + x = alloc(len); + if (!x) return 0; + byte_copy(x,len,in); + if (*out) alloc_free(*out); + *out = x; + return 1; +} + +int dns_domain_equal(const char *dn1,const char *dn2) +{ + unsigned int len; + + len = dns_domain_length(dn1); + if (len != dns_domain_length(dn2)) return 0; + + if (case_diffb((char *)dn1,len,(char *)dn2)) return 0; /* safe since 63 < 'A' */ + return 1; +} + +int dns_domain_suffix(const char *big,const char *little) +{ + unsigned char c; + + for (;;) { + if (dns_domain_equal(big,little)) return 1; + c = *big++; + if (!c) return 0; + big += c; + } +} + +unsigned int dns_domain_suffixpos(const char *big,const char *little) +{ + const char *orig = big; + unsigned char c; + + for (;;) { + if (dns_domain_equal(big,little)) return big - orig; + c = *big++; + if (!c) return 0; + big += c; + } +} diff --git a/src/dnsstub/dns_dtda.c b/src/dnsstub/dns_dtda.c new file mode 100644 index 0000000..38358a2 --- /dev/null +++ b/src/dnsstub/dns_dtda.c @@ -0,0 +1,43 @@ +#include "stralloc.h" +#include "dnsresolv.h" + +/** + @file dns_dtda.c + @author djb + @source ucspi-tcp + @brief domain to dot append +*/ + +int dns_domain_todot_cat(stralloc *out,const char *d) +{ + char ch; + char ch2; + unsigned char ch3; + char buf[4]; + + if (!*d) + return stralloc_append(out,"."); + + for (;;) { + ch = *d++; + while (ch--) { + ch2 = *d++; + if ((ch2 >= 'A') && (ch2 <= 'Z')) ch2 += 32; // FQDN -> lowercase + if (((ch2 >= 'a') && (ch2 <= 'z')) || + ((ch2 >= '0') && (ch2 <= '9')) || + (ch2 == '-') || (ch2 == '_')) { + if (!stralloc_append(out,&ch2)) return DNS_MEM; + } + else { // decimal -> octal + ch3 = ch2; + buf[3] = '0' + (ch3 & 7); ch3 >>= 3; + buf[2] = '0' + (ch3 & 7); ch3 >>= 3; + buf[1] = '0' + (ch3 & 7); + buf[0] = '\\'; + if (!stralloc_catb(out,buf,4)) return DNS_MEM; + } + } + if (!*d) return 1; + if (!stralloc_append(out,".")) return DNS_MEM; + } +} diff --git a/src/dnsstub/dns_ip.c b/src/dnsstub/dns_ip.c new file mode 100644 index 0000000..f89728c --- /dev/null +++ b/src/dnsstub/dns_ip.c @@ -0,0 +1,198 @@ +#include "stralloc.h" +#include "uint_t.h" +#include "byte.h" +#include "ip.h" +#include "dnsresolv.h" + +/** + @file dns_ip.c + @author djb, fefe, feh + @source ucspi-tcp6 + @brief DNS IP query +*/ + +static char *q = 0; + +int dns_ip4_packet(stralloc *out,const char *buf,unsigned int len) +{ + unsigned int pos; + char header[12]; + uint16 numanswers; + uint16 datalen; + int ranswers = 0; + + if (!stralloc_copys(out,"")) return DNS_MEM; + + pos = dns_packet_copy(buf,len,0,header,12); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 6,&numanswers); + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos += 4; + + while (numanswers--) { + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 8,&datalen); + if (byte_equal(header,2,DNS_T_A)) + if (byte_equal(header + 2,2,DNS_C_IN)) + if (datalen == 4) { + if (!dns_packet_copy(buf,len,pos,header,4)) return DNS_ERR; + if (!stralloc_catb(out,header,4)) return DNS_MEM; + } + pos += datalen; + ++ranswers; + } + + dns_sortip4(out->s,out->len); + return ranswers; +} + +int dns_ip4(stralloc *out,stralloc *fqdn) +{ + unsigned int i; + char code = 0; + int dot = 0; + char ch; + char ip[4]; + int r; + int rc = 0; + + if (!stralloc_copys(out,"")) return DNS_MEM; + if (!stralloc_readyplus(fqdn,1)) return DNS_MEM; + + fqdn->s[fqdn->len] = 0; /* test FQDN string */ + for (i = 1; i < fqdn->len; i++) { + if (fqdn->s[i] >= '_') { code = 127; break; } + if (fqdn->s[i] == '.') dot++; + } + + if (code != 127 && dot == 3) /* if FQDN is just IPv4 */ + if (ip4_scan(fqdn->s,ip) || ip4_scanbracket(fqdn->s,ip)) { + if (!stralloc_copyb(out,ip,4)) return DNS_MEM; + return 1; + } + + code = 0; + for (i = 0; i <= fqdn->len; ++i) { + if (i < fqdn->len) + ch = fqdn->s[i]; + else + ch = '.'; + + if ((ch == '[') || (ch == ']')) continue; + if (ch == '.') { + if (!stralloc_append(out,&code)) return DNS_MEM; + code = 0; + continue; + } + if ((ch >= '0') && (ch <= '9')) { + code *= 10; + code += ch - '0'; + continue; + } + + if (dns_domain_fromdot(&q,fqdn->s,fqdn->len) <= 0) return DNS_ERR; // fdqn -> A query -> response + if (dns_resolve(q,DNS_T_A) >= 0) { + if ((r = dns_ip4_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen)) < 0) return DNS_ERR; + dns_transmit_free(&dns_resolve_tx); + dns_domain_free(&q); + rc += r; + } + + return rc; + } + + out->len &= ~3; + return 0; +} + +int dns_ip6_packet(stralloc *out,const char *buf,unsigned int len) +{ + unsigned int pos; + char header[16]; + uint16 numanswers; + uint16 datalen; + int ranswers = 0; + + if (!stralloc_copys(out,"")) return DNS_MEM; + + pos = dns_packet_copy(buf,len,0,header,12); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 6,&numanswers); + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos += 4; + + while (numanswers--) { + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 8,&datalen); + if (byte_equal(header,2,DNS_T_AAAA)) { + if (byte_equal(header + 2,2,DNS_C_IN)) + if (datalen == 16) { + if (!dns_packet_copy(buf,len,pos,header,16)) return DNS_ERR; + if (!stralloc_catb(out,header,16)) return DNS_MEM; + } + } else if (byte_equal(header,2,DNS_T_A)) + if (byte_equal(header + 2,2,DNS_C_IN)) + if (datalen == 4) { + byte_copy(header,12,V4mappedprefix); + if (!dns_packet_copy(buf,len,pos,header + 12,4)) return DNS_ERR; + if (!stralloc_catb(out,header,16)) return DNS_MEM; + } + pos += datalen; + ++ranswers; + } + + dns_sortip6(out->s,out->len); + return ranswers; +} + +int dns_ip6(stralloc *out,stralloc *fqdn) +{ + unsigned int i; + char code; + char ch; + char ip[16]; + int r; + int rc = 0; + + if (!stralloc_copys(out,"")) return DNS_MEM; + if (!stralloc_readyplus(fqdn,1)) return DNS_MEM; + + fqdn->s[fqdn->len] = 0; /* if FQDN is just IPv6 */ + if (ip6_scan(fqdn->s,ip) || ip6_scanbracket(fqdn->s,ip)) { + if (!stralloc_copyb(out,ip,16)) return DNS_MEM; + return 1; + } + + code = 0; + for (i = 0; i <= fqdn->len; ++i) { + if (i < fqdn->len) + ch = fqdn->s[i]; + else + ch = '.'; + + if ((ch == '[') || (ch == ']')) continue; + if (ch == '.') { + if (!stralloc_append(out,&code)) return DNS_MEM; + code = 0; + continue; + } + if ((ch >= '0') && (ch <= '9')) { + code *= 10; + code += ch - '0'; + continue; + } + + if (dns_domain_fromdot(&q,fqdn->s,fqdn->len) <= 0) return DNS_ERR; // fqdn -> AAAA query -> response + if (dns_resolve(q,DNS_T_AAAA) >= 0) { + if ((r = dns_ip6_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen)) < 0) return DNS_ERR; + dns_transmit_free(&dns_resolve_tx); + dns_domain_free(&q); + rc += r; + } + + return rc; + } + + out->len &= ~3; + return 0; +} diff --git a/src/dnsstub/dns_ipq.c b/src/dnsstub/dns_ipq.c new file mode 100644 index 0000000..26c3818 --- /dev/null +++ b/src/dnsstub/dns_ipq.c @@ -0,0 +1,236 @@ +#include "case.h" +#include "byte.h" +#include "str.h" +#include "stralloc.h" +#include "dnsresolv.h" +#include "socket_if.h" +#include "ip.h" + +/** + @file dns_ipq.c + @author djb, feh + @source ucspi-tcp + @brief DNS hostname qualification for ipv4 and ipv6 +*/ + +/** + @fn int doit -> @return number of added chars to name +*/ +static int doit(stralloc *work,const char *rule) +{ + char ch; + unsigned int colon; + unsigned int prefixlen; + + ch = *rule++; + if ((ch != '?') && (ch != '=') && (ch != '*') && (ch != '-')) return 1; + colon = str_chr((char *)rule,':'); + if (!rule[colon]) return 1; + + if (work->len < colon) return 1; + prefixlen = work->len - colon; + if ((ch == '=') && prefixlen) return 1; + if (case_diffb((char *)rule,colon,work->s + prefixlen)) return 1; + if (ch == '?') { + if (byte_chr(work->s,prefixlen,'.') < prefixlen) return 1; + if (byte_chr(work->s,prefixlen,'[') < prefixlen) return 1; + if (byte_chr(work->s,prefixlen,']') < prefixlen) return 1; + } + + work->len = prefixlen; + if (ch == '-') work->len = 0; + return stralloc_cats(work,rule + colon + 1); +} + +/** @fn int dns_ip4_qualify_rules -> @return number of IPv4 addresss with rules */ + +int dns_ip4_qualify_rules(stralloc *ipout,stralloc *fqdn,const stralloc *in,const stralloc *rules) +{ + unsigned int i; + unsigned int j; + unsigned int plus; + unsigned int fqdnlen; + int rc = 0; + + if (!stralloc_copy(fqdn,(stralloc *)in)) return DNS_MEM; + + for (j = i = 0; j < rules->len; ++j) + if (!rules->s[j]) { + if (!doit(fqdn,rules->s + i)) return DNS_INT; + i = j + 1; + } + + fqdnlen = fqdn->len; + plus = byte_chr(fqdn->s,fqdnlen,'+'); + if (plus >= fqdnlen) + return dns_ip4(ipout,fqdn); + + i = plus + 1; + for (;;) { + j = byte_chr(fqdn->s + i,fqdnlen - i,'+'); + byte_copy(fqdn->s + plus,j,fqdn->s + i); + fqdn->len = plus + j; + if (rc += dns_ip4(ipout,fqdn) < 0) return DNS_ERR; + i += j; + if (i >= fqdnlen) return rc; + ++i; + } + return 0; +} + +/** @fn int dns_ip4_qualify -> @return number of IPv4 addresss qualified */ + +int dns_ip4_qualify(stralloc *ipout,stralloc *fqdn,const stralloc *in) +{ + int r; + static stralloc rules; + + if ((r = dns_ip_qualify_localhost(ipout,fqdn,in)) > 0 ) return r; + if (dns_resolvconfrewrite(&rules) < 0) return DNS_INT; + return dns_ip4_qualify_rules(ipout,fqdn,in,&rules); +} + +/** @fn int dns_ip4_qualify_rules -> @return number of IPv6 addresss with rules */ + +int dns_ip6_qualify_rules(stralloc *ipout,stralloc *fqdn,const stralloc *in,const stralloc *rules) +{ + unsigned int i; + unsigned int j; + unsigned int plus; + unsigned int fqdnlen; + int rc = 0; + + if (!stralloc_copy(fqdn,(stralloc *)in)) return DNS_MEM; + + for (j = i = 0; j < rules->len; ++j) + if (!rules->s[j]) { + if (!doit(fqdn,rules->s + i)) return DNS_INT; + i = j + 1; + } + + fqdnlen = fqdn->len; + plus = byte_chr(fqdn->s,fqdnlen,'+'); + if (plus >= fqdnlen) + return dns_ip6(ipout,fqdn); + + i = plus + 1; + for (;;) { + j = byte_chr(fqdn->s + i,fqdnlen - i,'+'); + byte_copy(fqdn->s + plus,j,fqdn->s + i); + fqdn->len = plus + j; + if ((rc += dns_ip6(ipout,fqdn)) < 0) return DNS_ERR; + i += j; + if (i >= fqdnlen) return rc; + ++i; + } + return 0; +} + +/** @fn int dns_ip6_qualify -> @return number of IPv6 addresss qualified */ + +int dns_ip6_qualify(stralloc *ipout,stralloc *fqdn,const stralloc *in) +{ + int r; + static stralloc rules; + + if ((r = dns_ip_qualify_localhost(ipout,fqdn,in)) > 0) return r; + if (dns_resolvconfrewrite(&rules) < 0) return DNS_INT; + return dns_ip6_qualify_rules(ipout,fqdn,in,&rules); +} + +/** @fn int dns_ip_qualify_rules -> @return number of IPv6+IPv4 addresss with rules */ + +int dns_ip_qualify_rules(stralloc *ipout,stralloc *fqdn,const stralloc *in,const stralloc *rules) +{ + unsigned int i; + unsigned int j; + unsigned int k; + unsigned int plus; + unsigned int fqdnlen; + stralloc tmp = {0}; + int rc = 0; + + if (!stralloc_copy(fqdn,(stralloc *)in)) return DNS_MEM; + if (!stralloc_copys(ipout,"")) return DNS_MEM; + + for (j = i = 0; j < rules->len; ++j) + if (!rules->s[j]) { + if (!doit(fqdn,rules->s + i)) return DNS_INT; + i = j + 1; + } + + fqdnlen = fqdn->len; + plus = byte_chr(fqdn->s,fqdnlen,'+'); + if (plus >= fqdnlen) { + rc = dns_ip6(ipout,fqdn); + if (dns_ip4(&tmp,fqdn) > 0) { + for (k = 0; k < tmp.len; k += 4) { + if (!stralloc_catb(ipout,(const char *) V4mappedprefix,12)) return DNS_MEM; + if (!stralloc_catb(ipout,tmp.s + k,4)) return DNS_MEM; + rc++; + } + } + return rc; + } + + i = plus + 1; + for (;;) { + j = byte_chr(fqdn->s + i,fqdnlen - i,'+'); + byte_copy(fqdn->s + plus,j,fqdn->s + i); + fqdn->len = plus + j; + if (!stralloc_copys(ipout,"")) return DNS_MEM; + rc = dns_ip6(&tmp,fqdn); + if (rc) if (!stralloc_cat(ipout,&tmp)) return DNS_MEM; + if (dns_ip4(&tmp,fqdn) > 0) { + for (k = 0; k < tmp.len; k += 4) { + if (!stralloc_catb(ipout,(const char *) V4mappedprefix,12)) return DNS_MEM; + if (!stralloc_catb(ipout,tmp.s + k,4)) return DNS_MEM; + rc++; + } + } + + if (rc < 0) return DNS_ERR; + i += j; + if (i >= fqdnlen) return rc; + ++i; + } + return 0; +} + +/** @fn int dns_ip_qualify_localhost -> @return number of IP addresss */ + +int dns_ip_qualify_localhost(stralloc *ipout,stralloc *fqdn,const stralloc *in) +{ + if (!stralloc_copys(ipout,"")) return DNS_MEM; + if (!stralloc_copys(fqdn,"")) return DNS_MEM; + ipout->len = 0; + + if (byte_equal(in->s,9,LOCALHOST)) { + if (!stralloc_copyb(ipout,(const char *) V6loopback,16)) return DNS_MEM; + if (!stralloc_catb(ipout,(const char *) V46loopback,16)) return DNS_MEM; + if (!stralloc_copys(fqdn,"localhost.localhost.")) return DNS_MEM; + } + if (byte_equal(in->s,13,IP4_LOOPBACK)) { + if (!stralloc_copyb(ipout,(const char *) V46loopback,16)) return DNS_MEM; + if (!stralloc_copys(fqdn,"ip4-loopback.localhost.")) return DNS_MEM; + } + if (byte_equal(in->s,13,IP6_LOOPBACK)) { + if (!stralloc_copyb(ipout,(const char *) V6loopback,16)) return DNS_MEM; + if (!stralloc_copys(fqdn,"ip6-loopback.localhost.")) return DNS_MEM; + } +// if (!stralloc_0(fqdn)) return DNS_MEM; // don't do it + + return ipout->len ? ipout->len % 15 : 0; +} + +/** @fn int dns_ip_qualify -> @return number of IP addresss */ + +int dns_ip_qualify(stralloc *ipout,stralloc *fqdn,const stralloc *in) +{ + int r; + static stralloc rules; + + if ((r = dns_ip_qualify_localhost(ipout,fqdn,in)) > 0 ) return r; + if (dns_resolvconfrewrite(&rules) < 0) return DNS_INT; + return dns_ip_qualify_rules(ipout,fqdn,in,&rules); +} diff --git a/src/dnsstub/dns_mx.c b/src/dnsstub/dns_mx.c new file mode 100644 index 0000000..c0845ef --- /dev/null +++ b/src/dnsstub/dns_mx.c @@ -0,0 +1,63 @@ +#include "stralloc.h" +#include "byte.h" +#include "uint_t.h" +#include "dnsresolv.h" + +/** + @file dns_mx.c + @author djb + @source qmail + @brief dns MX query + @param (on output) stralloc out +*/ + +static char *q = 0; + +int dns_mx_packet(stralloc *out,const char *buf,unsigned int len) +{ + unsigned int pos; + char header[12]; + char pref[2]; + uint16 numanswers; + uint16 datalen; + int ranswers = 0; + + if (!stralloc_copys(out,"")) return DNS_MEM; + + pos = dns_packet_copy(buf,len,0,header,12); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 6,&numanswers); + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos += 4; + + while (numanswers--) { + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 8,&datalen); + if (byte_equal(header,2,DNS_T_MX)) + if (byte_equal(header + 2,2,DNS_C_IN)) { + if (!dns_packet_copy(buf,len,pos,pref,2)) return DNS_ERR; + if (!dns_packet_getname(buf,len,pos + 2,&q)) return DNS_ERR; + if (!stralloc_catb(out,pref,2)) return DNS_MEM; + if (dns_domain_todot_cat(out,q) <= 0) return DNS_ERR; + if (!stralloc_0(out)) return DNS_MEM; + } + pos += datalen; + ++ranswers; + } + + return ranswers; +} + +int dns_mx(stralloc *out,const stralloc *fqdn) +{ + int rc = 0; + + if (dns_domain_fromdot(&q,fqdn->s,fqdn->len) <= 0) return DNS_ERR; + if (dns_resolve(q,DNS_T_MX) >= 0) { + if ((rc = dns_mx_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen)) < 0) return DNS_ERR; + dns_transmit_free(&dns_resolve_tx); + dns_domain_free(&q); + } + + return rc; +} diff --git a/src/dnsstub/dns_name.c b/src/dnsstub/dns_name.c new file mode 100644 index 0000000..0723a8f --- /dev/null +++ b/src/dnsstub/dns_name.c @@ -0,0 +1,80 @@ +#include "stralloc.h" +#include "uint_t.h" +#include "byte.h" +#include "ip.h" +#include "dnsresolv.h" + +/** + @file dns_name.c + @author djb, fefe, feh + @source ucspi-tcp + @brief DNS name query (ptr) +*/ + +static char *q = 0; + +int dns_name_packet(stralloc *out,const char *buf,unsigned int len) +{ + unsigned int pos; + char header[12]; + uint16 numanswers; + uint16 datalen; + + if (!stralloc_copys(out,"")) return DNS_MEM; + + pos = dns_packet_copy(buf,len,0,header,12); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 6,&numanswers); + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos += 4; + + while (numanswers--) { + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 8,&datalen); + if (byte_equal(header,2,DNS_T_PTR)) + if (byte_equal(header + 2,2,DNS_C_IN)) { + if (!dns_packet_getname(buf,len,pos,&q)) return DNS_ERR; + if (dns_domain_todot_cat(out,q) <= 0) return DNS_ERR; + return 1; + } + pos += datalen; + } + + return 0; +} + +int dns_name4(stralloc *out,const char ip[4]) +{ + int rc; + char name[DNS_NAME4_DOMAIN]; + + dns_name4_domain(name,ip); + if (dns_resolve(name,DNS_T_PTR) < 0) return DNS_ERR; + if ((rc = dns_name_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen)) < 0) return DNS_ERR; + dns_transmit_free(&dns_resolve_tx); + dns_domain_free(&q); + + return rc; +} + +int dns_name6(stralloc *out,const char ip[16]) +{ + int rc; + char name[DNS_NAME6_DOMAIN]; + + dns_name6_domain(name,ip); + if (dns_resolve(name,DNS_T_PTR) < 0) return DNS_ERR; + if ((rc = dns_name_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen)) < 0) return DNS_ERR; + dns_transmit_free(&dns_resolve_tx); + dns_domain_free(&q); + + return rc; +} + +int dns_name(stralloc *out,const char ip[16]) +{ + if (ip6_isv4mapped(ip)) + return dns_name4(out,ip+12); + else + return dns_name6(out,ip); +} diff --git a/src/dnsstub/dns_nd.c b/src/dnsstub/dns_nd.c new file mode 100644 index 0000000..6ce8ed9 --- /dev/null +++ b/src/dnsstub/dns_nd.c @@ -0,0 +1,48 @@ +#include "byte.h" +#include "fmt.h" +#include "ip.h" +#include "dnsresolv.h" + +/** + @file dns_nd.c + @autor djb, fefe + @source ucspi-tcp + @brief DNS domain name for ip (wire format) +*/ + +int dns_name4_domain(char name[DNS_NAME4_DOMAIN],const char ip[4]) +{ + unsigned int namelen; + unsigned int i; + + namelen = 0; + i = fmt_ulong(name + namelen + 1,(unsigned long) (unsigned char) ip[3]); + name[namelen++] = i; + namelen += i; + i = fmt_ulong(name + namelen + 1,(unsigned long) (unsigned char) ip[2]); + name[namelen++] = i; + namelen += i; + i = fmt_ulong(name + namelen + 1,(unsigned long) (unsigned char) ip[1]); + name[namelen++] = i; + namelen += i; + i = fmt_ulong(name + namelen + 1,(unsigned long) (unsigned char) ip[0]); + name[namelen++] = i; + namelen += i; + byte_copy(name + namelen,14,"\7in-addr\4arpa\0"); + return namelen+14; +} + +int dns_name6_domain(char name[DNS_NAME6_DOMAIN],const char ip[16]) +{ + unsigned int j; + + for (j = 0; j < 16; j++) { + name[j * 4] = 1; + name[j * 4 + 1] = tohex(ip[15 - j] & 15); + name[j * 4 + 2] = 1; + name[j * 4 + 3] = tohex((unsigned char)ip[15 - j] >> 4); + } + byte_copy(name + 4 * 16,10,"\3ip6\4arpa\0"); + return 4 * 16 + 10; +} + diff --git a/src/dnsstub/dns_packet.c b/src/dnsstub/dns_packet.c new file mode 100644 index 0000000..ce322ea --- /dev/null +++ b/src/dnsstub/dns_packet.c @@ -0,0 +1,85 @@ +#include "error.h" +#include "dnsresolv.h" + +/** + @file dns_packet.c + @author djb + @source ucspi-tcp + @brief DNS low level packet routine + @brief DNS should have used LZ77 instead of its own sophomoric compression algorithm. +*/ + +unsigned int dns_packet_copy(const char *buf,unsigned int len,unsigned int pos,char *out,unsigned int outlen) +{ + while (outlen) { + if (pos >= len) { errno = EPROTO; return 0; } + *out = buf[pos++]; + ++out; --outlen; + } + return pos; +} + +unsigned int dns_packet_skipname(const char *buf,unsigned int len,unsigned int pos) +{ + unsigned char ch; + + for (;;) { + if (pos >= len) break; + ch = buf[pos++]; + if (ch >= 192) return pos + 1; + if (ch >= 64) break; + if (!ch) return pos; + pos += ch; + } + + errno = EPROTO; + return 0; +} + +unsigned int dns_packet_getname(const char *buf,unsigned int len,unsigned int pos,char **d) +{ + unsigned int loop = 0; + unsigned int state = 0; + unsigned int firstcompress = 0; + unsigned int where; + unsigned char ch; + char name[255]; + unsigned int namelen = 0; + + for (;;) { + if (pos >= len) goto PROTO; + ch = buf[pos++]; + if (++loop >= 1000) goto PROTO; + + if (state) { + if (namelen + 1 > sizeof(name)) goto PROTO; + name[namelen++] = ch; + --state; + } else { + while (ch >= 192) { + where = ch; where -= 192; where <<= 8; + if (pos >= len) goto PROTO; + ch = buf[pos++]; + if (!firstcompress) firstcompress = pos; + pos = where + ch; + if (pos >= len) goto PROTO; + ch = buf[pos++]; + if (++loop >= 1000) goto PROTO; + } + if (ch >= 64) goto PROTO; + if (namelen + 1 > sizeof(name)) goto PROTO; + name[namelen++] = ch; + if (!ch) break; + state = ch; + } + } + + if (!dns_domain_copy(d,name)) return 0; + + if (firstcompress) return firstcompress; + return pos; + + PROTO: + errno = EPROTO; + return 0; +} diff --git a/src/dnsstub/dns_random.c b/src/dnsstub/dns_random.c new file mode 100644 index 0000000..200cd6c --- /dev/null +++ b/src/dnsstub/dns_random.c @@ -0,0 +1,70 @@ +#include <unistd.h> +#include "taia.h" +#include "uint_t.h" +#include "dnsresolv.h" + +/** + @file dns_random.c + @author djb + @source ucspi-tcp + @brief random use of DNS resolvers given their IP +*/ + +static uint32 seed[32]; +static uint32 in[12]; +static uint32 out[8]; +static int outleft = 0; + +#define ROTATE(x,b) (((x) << (b)) | ((x) >> (32 - (b)))) +#define MUSH(i,b) x = t[i] += (((x ^ seed[i]) + sum) ^ ROTATE(x,b)); + +static void surf(void) +{ + uint32 t[12]; uint32 x; uint32 sum = 0; + int r; int i; int loop; + + for (i = 0; i < 12; ++i) t[i] = in[i] ^ seed[12 + i]; + for (i = 0; i < 8; ++i) out[i] = seed[24 + i]; + x = t[11]; + for (loop = 0; loop < 2; ++loop) { + for (r = 0; r < 16; ++r) { + sum += 0x9e3779b9; + MUSH(0,5) MUSH(1,7) MUSH(2,9) MUSH(3,13) + MUSH(4,5) MUSH(5,7) MUSH(6,9) MUSH(7,13) + MUSH(8,5) MUSH(9,7) MUSH(10,9) MUSH(11,13) + } + for (i = 0; i < 8; ++i) out[i] ^= t[i + 4]; + } +} + +void dns_random_init(const char data[128]) +{ + int i; + struct taia t; + char tpack[16]; + + for (i = 0; i < 32; ++i) + uint32_unpack((char *)data + 4 * i,seed + i); + + taia_now(&t); + taia_pack(tpack,&t); + for (i = 0; i < 4; ++i) + uint32_unpack(tpack + 4 * i,in + 4 + i); + + in[8] = getpid(); + in[9] = getppid(); + /* more space in 10 and 11, but this is probably enough */ +} + +unsigned int dns_random(unsigned int n) +{ + if (!n) return 0; + + if (!outleft) { + if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3]; + surf(); + outleft = 8; + } + + return out[--outleft] % n; +} diff --git a/src/dnsstub/dns_rcip.c b/src/dnsstub/dns_rcip.c new file mode 100644 index 0000000..93b0daa --- /dev/null +++ b/src/dnsstub/dns_rcip.c @@ -0,0 +1,114 @@ +#include "taia.h" +#include "readclose.h" +#include "byte.h" +#include "ip.h" +#include "env.h" +#include "dnsresolv.h" +#include "socket_if.h" + +/** + @file dns_rcip.c + @author djb, fefe, feh + @source ucspi-tcp + @brief DNS receive for query +*/ + +static stralloc data = {0}; +static stralloc ifname = {0}; + +static int init(char ip[QUERY_MAXIPLEN],uint32 sid[QUERY_MAXNS]) +{ + int i; + int j; + int k = 0; + int iplen = 0; + char *x; + char ip4[4]; + +/* Read (compactified) IPv4|v6 addresses of resolvers + Store them in array IP with fixed length : + ip(64) -> 16 IPv4 addresses (not used anymore) + ip(512) -> 16*2 IPv6 addresses (we use IPv4 mapped IPv6 addresses) + sid(32) -> the scope for the respective IPv6 or 0 +*/ + for (i = 0; i < QUERY_MAXNS; ++i) sid[i] = 0; + + x = env_get("DNSCACHEIP"); + if (x) + while (iplen <= 240 && *x != '\0') { + if (*x == ' ') + ++x; + else + if ((i = ip6_ifscan(x,ip + iplen,&ifname))) { + if (ifname.len > 2) sid[k] = socket_getifidx(ifname.s); + iplen += 16; k++; + if (*(x += i) == '\0') break; + } + } + + if (!iplen) { + i = openreadclose("/etc/resolv.conf",&data,64); + if (i == -1) return DNS_INT; + if (i) { + if (!stralloc_append(&data,"\n")) return DNS_MEM; + i = 0; + for (j = 0; j < data.len; ++j) + if (data.s[j] == '\n') { + if (byte_equal("nameserver ",11,data.s + i) || byte_equal("nameserver\t",11,data.s + i)) { + i += 10; + while ((data.s[i] == ' ') || (data.s[i] == '\t')) + i++; + if (iplen <= 240) { + data.s[j] = '\0'; /* ip6_ifscan needs terminated string on input */ + if (ip4_scan(data.s + i,ip4)) { + if (byte_equal(ip4,4,"\0\0\0\0")) + byte_copy(ip4,4,"\177\0\0\1"); + byte_copy(ip + iplen,12,V4mappedprefix); + byte_copy(ip + iplen + 12,4,ip4); + sid[k] = 0; iplen += 16; k++; + } else if (ip6_ifscan(data.s + i,ip + iplen,&ifname)) { + if (ifname.len > 2) sid[k] = socket_getifidx(ifname.s); + iplen += 16; k++; + } + } + } + i = j + 1; + } + } + } + + if (!iplen) { + byte_copy(ip,16,"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1"); + iplen = 16; + } + byte_zero(ip + iplen,QUERY_MAXIPLEN - iplen); + return 0; +} + +static int ok = 0; +static unsigned int uses; +static struct taia deadline; +static char ip[QUERY_MAXIPLEN]; /* defined if ok */ +static uint32 scopes[QUERY_MAXNS]; + +int dns_resolvconfip(char s[QUERY_MAXIPLEN],uint32 scope[QUERY_MAXNS]) +{ + struct taia now; + + taia_now(&now); + if (taia_less(&deadline,&now)) ok = 0; + if (!uses) ok = 0; + + if (!ok) { + if (init(ip,scopes) < 0) return DNS_INT; + taia_uint(&deadline,600); + taia_add(&deadline,&now,&deadline); + uses = 10000; + ok = 1; + } + + --uses; + byte_copy(s,QUERY_MAXIPLEN,ip); + byte_copy(scope,128,scopes); + return 0; +} diff --git a/src/dnsstub/dns_rcrw.c b/src/dnsstub/dns_rcrw.c new file mode 100644 index 0000000..4633fed --- /dev/null +++ b/src/dnsstub/dns_rcrw.c @@ -0,0 +1,141 @@ +#include <unistd.h> +#include "taia.h" +#include "env.h" +#include "byte.h" +#include "str.h" +#include "readclose.h" +#include "dnsresolv.h" + +/** + @file dns_rcrw.c + @author djb + @source ucspi-tcp + @brief DNS receive rewrite +*/ + +static stralloc data = {0}; + +static int init(stralloc *rules) +{ + char host[256]; + const char *x; + int i; + int j; + int k; + + if (!stralloc_copys(rules,"")) return DNS_MEM; + + x = env_get("DNSREWRITEFILE"); + if (!x) x = "/etc/dnsrewrite"; + + i = openreadclose(x,&data,64); + if (i == -1) return DNS_INT; + + if (i) { + if (!stralloc_append(&data,"\n")) return DNS_MEM; + i = 0; + for (j = 0; j < data.len; ++j) + if (data.s[j] == '\n') { + if (!stralloc_catb(rules,data.s + i,j - i)) return DNS_MEM; + while (rules->len) { + if (rules->s[rules->len - 1] != ' ') + if (rules->s[rules->len - 1] != '\t') + if (rules->s[rules->len - 1] != '\r') + break; + --rules->len; + } + if (!stralloc_0(rules)) return DNS_MEM; + i = j + 1; + } + return 0; + } + + x = env_get("LOCALDOMAIN"); + if (x) { + if (!stralloc_copys(&data,x)) return DNS_MEM; + if (!stralloc_append(&data," ")) return DNS_MEM; + if (!stralloc_copys(rules,"?:")) return DNS_MEM; + i = 0; + for (j = 0; j < data.len; ++j) + if (data.s[j] == ' ') { + if (!stralloc_cats(rules,"+.")) return DNS_MEM; + if (!stralloc_catb(rules,data.s + i,j - i)) return DNS_MEM; + i = j + 1; + } + if (!stralloc_0(rules)) return DNS_MEM; + if (!stralloc_cats(rules,"*.:")) return DNS_MEM; + if (!stralloc_0(rules)) return DNS_MEM; + return 0; + } + + i = openreadclose("/etc/resolv.conf",&data,64); + if (i == -1) return DNS_INT; + + if (i) { + if (!stralloc_append(&data,"\n")) return DNS_MEM; + i = 0; + for (j = 0; j < data.len; ++j) + if (data.s[j] == '\n') { + if (byte_equal("search ",7,data.s + i) || + byte_equal("search\t",7,data.s + i) || + byte_equal("domain ",7,data.s + i) || + byte_equal("domain\t",7,data.s + i)) { + if (!stralloc_copys(rules,"?:")) return DNS_MEM; + i += 7; + while (i < j) { + k = byte_chr(data.s + i,j - i,' '); + k = byte_chr(data.s + i,k,'\t'); + if (!k) { ++i; continue; } + if (!stralloc_cats(rules,"+.")) return DNS_MEM; + if (!stralloc_catb(rules,data.s + i,k)) return DNS_MEM; + i += k; + } + if (!stralloc_0(rules)) return DNS_MEM; + if (!stralloc_cats(rules,"*.:")) return DNS_MEM; + if (!stralloc_0(rules)) return DNS_MEM; + return 0; + } + i = j + 1; + } + } + + host[0] = 0; + if (gethostname(host,sizeof(host)) == -1) return DNS_ERR; + host[(sizeof(host)) - 1] = 0; + i = str_chr(host,'.'); + if (host[i]) { + if (!stralloc_copys(rules,"?:")) return DNS_MEM; + if (!stralloc_cats(rules,host + i)) return DNS_MEM; + if (!stralloc_0(rules)) return DNS_MEM; + } + if (!stralloc_cats(rules,"*.:")) return DNS_MEM; + if (!stralloc_0(rules)) return DNS_MEM; + + return 0; +} + +static int ok = 0; +static unsigned int uses; +static struct taia deadline; +static stralloc rules = {0}; /* defined if ok */ + +int dns_resolvconfrewrite(stralloc *out) +{ + struct taia now; + + taia_now(&now); + if (taia_less(&deadline,&now)) ok = 0; + if (!uses) ok = 0; + + if (!ok) { + if (init(&rules) < 0) return DNS_INT; + taia_uint(&deadline,600); + taia_add(&deadline,&now,&deadline); + uses = 10000; + ok = 1; + } + + --uses; + if (!stralloc_copy(out,&rules)) return DNS_MEM; + return 0; +} diff --git a/src/dnsstub/dns_resolve.c b/src/dnsstub/dns_resolve.c new file mode 100644 index 0000000..bcc4308 --- /dev/null +++ b/src/dnsstub/dns_resolve.c @@ -0,0 +1,39 @@ +#include "iopause.h" +#include "taia.h" +#include "byte.h" +#include "ip.h" +#include "dnsresolv.h" + +/** + @file dns_resolve.c + @author djb, fefe, feh + @source ucspi-tcp + @brief high-level DNS resolve function +*/ + +struct dns_transmit dns_resolve_tx = {0}; + +int dns_resolve(const char *q,const char qtype[2]) +{ + struct taia stamp; + struct taia deadline; + char servers[QUERY_MAXIPLEN]; + uint32 scopes[QUERY_MAXNS]; + iopause_fd x[1]; + int r; + + if (dns_resolvconfip(servers,scopes) < 0) return DNS_INT; + + if (dns_transmit_start6(&dns_resolve_tx,servers,1,q,qtype,(const char *)V6localnet,scopes) < 0) return DNS_COM; + + for (;;) { + taia_now(&stamp); + taia_uint(&deadline,120); + taia_add(&deadline,&deadline,&stamp); + dns_transmit_io(&dns_resolve_tx,x,&deadline); + iopause(x,1,&deadline,&stamp); + r = dns_transmit_get(&dns_resolve_tx,x,&stamp); + if (r < 0) return DNS_COM; + if (r == 1) return 0; + } +} diff --git a/src/dnsstub/dns_sortip.c b/src/dnsstub/dns_sortip.c new file mode 100644 index 0000000..56742e0 --- /dev/null +++ b/src/dnsstub/dns_sortip.c @@ -0,0 +1,45 @@ +#include "byte.h" +#include "ip.h" +#include "dnsresolv.h" + +/** + @file dns_sortip.c + @authors djb, fefe, feh + @source ucspi-tcp6 + @brief random sort of DNS servers per IP +*/ + +/* XXX: sort servers by configurable notion of closeness? */ +/* XXX: pay attention to competence of each server? */ +/* XXX: pay attention to qualification (DNSSec, DNSCurve) of each server? */ +/* YYY: we use a randomly sorted list of NS; not depending on answer */ + +void dns_sortip4(char *s,unsigned int n) +{ + unsigned int i; + char tmp[4]; + + n >>= 2; /* 4 byte per IPv4 address */ + while (n > 1) { + i = dns_random(n); + --n; + byte_copy(tmp,4,s + (i << 2)); + byte_copy(s + (i << 2),4,s + (n << 2)); + byte_copy(s + (n << 2),4,tmp); + } +} + +void dns_sortip6(char *s,unsigned int n) +{ + unsigned int i; + char tmp[16]; + + n >>= 4; /* 16 byte per IPv4 address */ + while (n > 1) { + i = dns_random(n); + --n; + byte_copy(tmp,16,s + (i << 4)); + byte_copy(s + (i << 4),16,s + (n << 4)); + byte_copy(s + (n << 4),16,tmp); + } +} diff --git a/src/dnsstub/dns_transmit.c b/src/dnsstub/dns_transmit.c new file mode 100644 index 0000000..2513565 --- /dev/null +++ b/src/dnsstub/dns_transmit.c @@ -0,0 +1,436 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <unistd.h> +#include "socket_if.h" +#include "alloc.h" +#include "error.h" +#include "byte.h" +#include "uint_t.h" +#include "ip.h" +#include "dnsresolv.h" + +/** + @file dns_transmit.c + @authors djb, fefe, feh + @source qlibs + @brief DNS query function + @brief scope_ids[32] -> 32 LLU root servers supported +*/ + +#define DNSPORT 53 + +uint32 scope_ids[QUERY_MAXNS]; + +static const int timeouts[5] = { 1, 2, 4, 8, 16 }; /* quadratic, not exponentially */ + +int getscopeid(const struct dns_transmit *d,const char *ip) +{ + int i; + + if (byte_diff(ip,2,V6linklocal)) return 0; + for (i = 0; i < QUERY_MAXNS; ++i) + if (byte_equal(d->servers + 16 * i,16,ip)) + return scope_ids[i]; + + return 0; +} + +int serverwantstcp(const char *buf,unsigned int len) +{ + char out[12]; + + if (!dns_packet_copy(buf,len,0,out,12)) return 1; + if (out[2] & 2) return 1; + + return 0; +} + +int serverfailed(const char *buf,unsigned int len) +{ + char out[12]; + unsigned int rcode; + + if (!dns_packet_copy(buf,len,0,out,12)) return 1; + rcode = out[3]; + rcode &= 15; + if (rcode && (rcode != 3)) { errno = EAGAIN; return 1; } + + return 0; +} + +int irrelevant(const struct dns_transmit *d,const char *buf,unsigned int len) +{ + char out[12]; + char *dn; + unsigned int pos; + + pos = dns_packet_copy(buf,len,0,out,12); if (!pos) return 1; + if (byte_diff(out,2,d->query + 2)) return 1; + if (out[4] != 0) return 1; + if (out[5] != 1) return 1; + + dn = 0; + pos = dns_packet_getname(buf,len,pos,&dn); if (!pos) return 1; + if (!dns_domain_equal(dn,d->query + 14)) { alloc_free(dn); return 1; } + alloc_free(dn); + + pos = dns_packet_copy(buf,len,pos,out,4); if (!pos) return 1; + if (byte_diff(out,2,d->qtype)) return 1; + if (byte_diff(out + 2,2,DNS_C_IN)) return 1; + + return 0; +} + +void packetfree(struct dns_transmit *d) +{ + if (!d->packet) return; + alloc_free(d->packet); + d->packet = 0; +} + +void queryfree(struct dns_transmit *d) +{ + if (!d->query) return; + alloc_free(d->query); + d->query = 0; +} + +void socketfree(struct dns_transmit *d) +{ + if (!d->s1) return; + close(d->s1 - 1); + d->s1 = 0; +} + +void dns_transmit_free(struct dns_transmit *d) +{ + queryfree(d); + socketfree(d); + packetfree(d); +} + +int randombind6(struct dns_transmit *d) +{ + int j; + + for (j = 0; j < 10; ++j) { + if (socket_bind6(d->s1 - 1,d->localip,1025 + dns_random(64510),d->scope_id) == 0) + return 0; + } + if (socket_bind6(d->s1 - 1,d->localip,0,d->scope_id) == 0) + return 0; + + return DNS_COM; +} + +int randombind4(struct dns_transmit *d) +{ + int j; + + for (j = 0; j < 10; ++j) { + if (socket_bind4(d->s1 - 1,d->localip + 12,1025 + dns_random(64510)) == 0) + return 0; + } + if (socket_bind4(d->s1 - 1,d->localip + 12,0) == 0) + return 0; + + return DNS_COM; +} + +int thisudp(struct dns_transmit *d) +{ + const char *ip; + + socketfree(d); + + while (d->udploop < 5) { + for (; d->curserver < QUERY_MAXNS; ++d->curserver) { + ip = d->servers + 16 * d->curserver; + if (byte_diff(ip,16,V6localnet)) { + d->query[2] = dns_random(256); + d->query[3] = dns_random(256); + + if (ip6_isv4mapped(ip)) { + d->s1 = 1 + socket_udp4(); + if (!d->s1) { dns_transmit_free(d); return DNS_COM; } + if (randombind4(d) < 0) { dns_transmit_free(d); return DNS_COM; } + } else { + d->s1 = 1 + socket_udp6(); + if (!d->s1) { dns_transmit_free(d); return DNS_COM; } + if (randombind6(d) < 0) { dns_transmit_free(d); return DNS_COM; } + } + + if (byte_equal(ip,2,V6linklocal) && !d->scope_id) + d->scope_id = getscopeid(d,ip); + if (socket_connect(d->s1 - 1,ip,DNSPORT,d->scope_id) == 0) + if (send(d->s1 - 1,d->query + 2,d->querylen - 2,0) == d->querylen - 2) { + struct taia now; + taia_now(&now); + taia_uint(&d->deadline,timeouts[d->udploop]); + taia_add(&d->deadline,&d->deadline,&now); + d->tcpstate = 0; + return 0; + } + socketfree(d); + } + } + ++d->udploop; + d->curserver = 0; + } + + dns_transmit_free(d); return DNS_COM; +} + +int firstudp(struct dns_transmit *d) +{ + d->curserver = 0; + return thisudp(d); +} + +int nextudp(struct dns_transmit *d) +{ + ++d->curserver; + return thisudp(d); +} + +int thistcp(struct dns_transmit *d) +{ + struct taia now; + const char *ip; + + socketfree(d); + packetfree(d); + + for (; d->curserver < QUERY_MAXNS; ++d->curserver) { + ip = d->servers + 16 * d->curserver; + if (byte_diff(ip,16,V6localnet)) { + d->query[2] = dns_random(256); + d->query[3] = dns_random(256); + + if (ip6_isv4mapped(ip)) { + d->s1 = 1 + socket_tcp4(); + if (!d->s1) { dns_transmit_free(d); return DNS_COM; } + if (randombind4(d) < 0) { dns_transmit_free(d); return DNS_COM; } + } else { + d->s1 = 1 + socket_tcp6(); + if (!d->s1) { dns_transmit_free(d); return DNS_COM; } + if (randombind6(d) < 0) { dns_transmit_free(d); return DNS_COM; } + } + + taia_now(&now); + taia_uint(&d->deadline,10); + taia_add(&d->deadline,&d->deadline,&now); + + if (byte_equal(ip,2,V6linklocal) && !d->scope_id) + d->scope_id = getscopeid(d,ip); + if (socket_connect(d->s1 - 1,ip,DNSPORT,d->scope_id) == 0) { + d->tcpstate = 2; + return 0; + } + if ((errno == EINPROGRESS) || (errno == EWOULDBLOCK)) { + d->tcpstate = 1; + return 0; + } + + socketfree(d); + } + } + + dns_transmit_free(d); + return DNS_COM; +} + +int firsttcp(struct dns_transmit *d) +{ + d->curserver = 0; + return thistcp(d); +} + +int nexttcp(struct dns_transmit *d) +{ + ++d->curserver; + return thistcp(d); +} + +int dns_transmit_start(struct dns_transmit *d,const char servers[QUERY_MAXIPLEN], \ + int flagrecursive,const char *q,const char qtype[2],const char localip[16]) +{ + unsigned int len; + + dns_transmit_free(d); + errno = EIO; + + len = dns_domain_length(q); + d->querylen = len + 18; + d->query = alloc(d->querylen); + if (!d->query) return DNS_COM; + + uint16_pack_big(d->query,len + 16); + byte_copy(d->query + 2,12,flagrecursive ? "\0\0\1\0\0\1\0\0\0\0\0\0" : \ + "\0\0\0\0\0\1\0\0\0\0\0\0gcc-bug-workaround"); + byte_copy(d->query + 14,len,q); + byte_copy(d->query + 14 + len,2,qtype); + byte_copy(d->query + 16 + len,2,DNS_C_IN); + + byte_copy(d->qtype,2,(char *) qtype); + d->servers = servers; + byte_copy(d->localip,16,(char *) localip); + + d->udploop = flagrecursive ? 1 : 0; + + if (len + 16 > MSGSIZE) return firsttcp(d); + return firstudp(d); +} + +int dns_transmit_start6(struct dns_transmit *d,const char servers[QUERY_MAXIPLEN], \ + int flagrecursive,const char *q,const char qtype[2], \ + const char localip[16],const uint32 scopes[QUERY_MAXNS]) +{ + byte_copy(scope_ids,128,(char *) scopes); + + return dns_transmit_start(d,servers,flagrecursive,q,qtype,localip); +} + +void dns_transmit_io(struct dns_transmit *d,iopause_fd *x,struct taia *deadline) +{ + x->fd = d->s1 - 1; + + switch (d->tcpstate) { + case 0: case 3: case 4: case 5: + x->events = IOPAUSE_READ; + break; + case 1: case 2: + x->events = IOPAUSE_WRITE; + break; + } + + if (taia_less(&d->deadline,deadline)) + *deadline = d->deadline; +} + +int dns_transmit_get(struct dns_transmit *d,const iopause_fd *x,const struct taia *when) +{ + char udpbuf[MSGSIZE + 1]; + unsigned char ch; + int r; + int fd; + + errno = EIO; + fd = d->s1 - 1; + + if (!x->revents) { + if (taia_less((struct taia *)when,&d->deadline)) return 0; + errno = ETIMEDOUT; + if (d->tcpstate == 0) return nextudp(d); + return nexttcp(d); + } + +/* +have attempted to send UDP query to each server udploop times +have sent query to curserver on UDP socket s +*/ + if (d->tcpstate == 0) { + r = recv(fd,udpbuf,sizeof(udpbuf),0); + if (r <= 0) { + if (errno == ECONNREFUSED) if (d->udploop == 2) return 0; + return nextudp(d); + } + if (r + 1 > sizeof(udpbuf)) return 0; + + if (irrelevant(d,udpbuf,r)) return 0; + if (serverwantstcp(udpbuf,r)) return firsttcp(d); + if (serverfailed(udpbuf,r)) { + if (d->udploop == 2) return 0; + return nextudp(d); + } + socketfree(d); + + d->packetlen = r; + d->packet = alloc(d->packetlen); + if (!d->packet) { dns_transmit_free(d); return DNS_COM; } + byte_copy(d->packet,d->packetlen,udpbuf); + queryfree(d); + return 1; + } + +/* +have sent connection attempt to curserver on TCP socket s +pos not defined +*/ + if (d->tcpstate == 1) { + if (!socket_connected(fd)) return nexttcp(d); + d->pos = 0; + d->tcpstate = 2; + return 0; + } + +/* +have connection to curserver on TCP socket s +have sent pos bytes of query +*/ + if (d->tcpstate == 2) { + r = write(fd,d->query + d->pos,d->querylen - d->pos); + if (r <= 0) return nexttcp(d); + d->pos += r; + if (d->pos == d->querylen) { + struct taia now; + taia_now(&now); + taia_uint(&d->deadline,10); + taia_add(&d->deadline,&d->deadline,&now); + d->tcpstate = 3; + } + return 0; + } + +/* +have sent entire query to curserver on TCP socket s +pos not defined +*/ + if (d->tcpstate == 3) { + r = read(fd,&ch,1); + if (r <= 0) return nexttcp(d); + d->packetlen = ch; + d->tcpstate = 4; + return 0; + } + +/* +have sent entire query to curserver on TCP socket s +pos not defined +have received one byte of packet length into packetlen +*/ + if (d->tcpstate == 4) { + r = read(fd,&ch,1); + if (r <= 0) return nexttcp(d); + d->packetlen <<= 8; + d->packetlen += ch; + d->tcpstate = 5; + d->pos = 0; + d->packet = alloc(d->packetlen); + if (!d->packet) { dns_transmit_free(d); return DNS_COM; } + return 0; + } + +/* +have sent entire query to curserver on TCP socket s +have received entire packet length into packetlen +packet is allocated +have received pos bytes of packet +*/ + if (d->tcpstate == 5) { + r = read(fd,d->packet + d->pos,d->packetlen - d->pos); + if (r <= 0) return nexttcp(d); + d->pos += r; + if (d->pos < d->packetlen) return 0; + + socketfree(d); + if (irrelevant(d,d->packet,d->packetlen)) return nexttcp(d); + if (serverwantstcp(d->packet,d->packetlen)) return nexttcp(d); + if (serverfailed(d->packet,d->packetlen)) return nexttcp(d); + + queryfree(d); + return 1; + } + + return 0; +} diff --git a/src/dnsstub/dns_txt.c b/src/dnsstub/dns_txt.c new file mode 100644 index 0000000..9a1b56a --- /dev/null +++ b/src/dnsstub/dns_txt.c @@ -0,0 +1,64 @@ +#include "stralloc.h" +#include "uint_t.h" +#include "byte.h" +#include "dnsresolv.h" + +int dns_txt_packet(stralloc *out,const char *buf,unsigned int len) +{ + unsigned int pos; + char header[12]; + uint16 numanswers; + uint16 datalen; + char ch; + unsigned int txtlen; + int i; + int ranswers = 0; + + if (!stralloc_copys(out,"")) return DNS_MEM; + + pos = dns_packet_copy(buf,len,0,header,12); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 6,&numanswers); + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos += 4; + + while (numanswers--) { + pos = dns_packet_skipname(buf,len,pos); if (!pos) return DNS_ERR; + pos = dns_packet_copy(buf,len,pos,header,10); if (!pos) return DNS_ERR; + uint16_unpack_big(header + 8,&datalen); + if (byte_equal(header,2,DNS_T_TXT)) + if (byte_equal(header + 2,2,DNS_C_IN)) { + if (pos + datalen > len) return DNS_ERR; + txtlen = (unsigned char) buf[pos]; + for (i = 1; i < datalen; ++i) { + ch = buf[pos + i]; + if (i == txtlen + 1) // next label + txtlen += (unsigned char) ch + 1; + else { + if (ch < 32) ch = '?'; + if (ch > 126) ch = '?'; + if (!stralloc_append(out,&ch)) return DNS_MEM; + } + } + } + pos += datalen; + ++ranswers; + if (numanswers) if (!stralloc_append(out,"\n")) return DNS_MEM; + } + + return ranswers; +} + +static char *q = 0; + +int dns_txt(stralloc *out,const stralloc *fqdn) +{ + int rc; + + if (dns_domain_fromdot(&q,fqdn->s,fqdn->len) <= 0) return DNS_ERR; + if (dns_resolve(q,DNS_T_TXT) < 0) return DNS_ERR; + if ((rc = dns_txt_packet(out,dns_resolve_tx.packet,dns_resolve_tx.packetlen)) < 0) return DNS_ERR; + dns_transmit_free(&dns_resolve_tx); + dns_domain_free(&q); + + return rc; +} |