summaryrefslogtreecommitdiff
path: root/src/dnsstub
diff options
context:
space:
mode:
authorJannis Hoffmann <jannis@fehcom.de>2024-07-09 13:58:20 +0200
committerJannis Hoffmann <jannis@fehcom.de>2024-07-09 13:58:20 +0200
commit249866e3d1e11dc72eaa1305f4bb479ded92ef38 (patch)
tree7118c5f58e29fe61c100e4d067bb90ba8d52589e /src/dnsstub
parent96cf8dffe4f7b0b910f790066ae622dc429eb522 (diff)
reorganized file structure
Moved c files into src/. Corrected VERSION file. Removed BUILD and FILES.
Diffstat (limited to 'src/dnsstub')
-rw-r--r--src/dnsstub/Makefile23
-rw-r--r--src/dnsstub/README.md171
-rw-r--r--src/dnsstub/TARGETS18
-rw-r--r--src/dnsstub/dns_cname.c59
-rw-r--r--src/dnsstub/dns_dfd.c76
-rw-r--r--src/dnsstub/dns_domain.c80
-rw-r--r--src/dnsstub/dns_dtda.c43
-rw-r--r--src/dnsstub/dns_ip.c198
-rw-r--r--src/dnsstub/dns_ipq.c236
-rw-r--r--src/dnsstub/dns_mx.c63
-rw-r--r--src/dnsstub/dns_name.c80
-rw-r--r--src/dnsstub/dns_nd.c48
-rw-r--r--src/dnsstub/dns_packet.c85
-rw-r--r--src/dnsstub/dns_random.c70
-rw-r--r--src/dnsstub/dns_rcip.c114
-rw-r--r--src/dnsstub/dns_rcrw.c141
-rw-r--r--src/dnsstub/dns_resolve.c39
-rw-r--r--src/dnsstub/dns_sortip.c45
-rw-r--r--src/dnsstub/dns_transmit.c436
-rw-r--r--src/dnsstub/dns_txt.c64
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;
+}