links/doh.c

236 lines
5.6 KiB
C

#include "links.h"
struct dnsquery_addr {
struct status stat;
unsigned char *url;
int redirect_cnt;
};
struct dnsquery_doh {
struct dnsquery *q;
struct dnsquery_addr ipv4;
struct dnsquery_addr ipv6;
int in_progress;
uttime ttl;
};
#define skip_bytes(n) do { if (len < (n)) return; len -= (n); start += (n); } while (0)
#define get_byte(b) do { if (!len--) return; (b) = *start++; } while (0)
#define get_word(w) do { unsigned char lo, hi; get_byte(hi); get_byte(lo); (w) = (hi << 8) | lo; } while (0)
#define get_dword(d) do { unsigned short lw, hw; get_word(hw); get_word(lw); (d) = ((unsigned)hw << 16) | lw; } while (0)
#define skip_name() do while (1) { \
unsigned char n; \
get_byte(n); \
if ((n & 0xc0) == 0) { \
if (n == 0) \
break; \
skip_bytes(n); \
continue; \
} else if ((n & 0xc0) == 0xc0) { \
skip_bytes(1); \
break; \
} else { \
return; \
} \
} while (0)
static void parse_dns_reply(struct dnsquery *q, unsigned char *start, size_t len)
{
unsigned short status, qdcount, ancount, i;
struct dnsquery_doh *doh = q->doh;
int preference = q->addr_preference;
#ifdef SUPPORT_IPV6
if (preference == ADDR_PREFERENCE_DEFAULT)
preference = ADDR_PREFERENCE_IPV6;
#endif
if (!support_ipv6) preference = ADDR_PREFERENCE_IPV4_ONLY;
skip_bytes(2);
get_word(status);
if (status & 0xf)
return;
get_word(qdcount);
get_word(ancount);
skip_bytes(4);
for (i = 0; i < qdcount; i++) {
skip_name();
skip_bytes(4);
}
for (i = 0; i < ancount; i++) {
unsigned short typ, cls, rdlength;
unsigned ttl;
skip_name();
get_word(typ);
get_word(cls);
get_dword(ttl);
if (ttl < doh->ttl)
doh->ttl = ttl;
get_word(rdlength);
skip_bytes(rdlength);
/*fprintf(stderr, "typ %04x, class %04x, length %04x\n", typ, cls, rdlength);*/
if (q->addr) {
if (cls == 1 && typ == 1 && rdlength == 4) {
add_address(q->addr, AF_INET, start - rdlength, 0, preference);
}
#ifdef SUPPORT_IPV6
if (cls == 1 && typ == 0x1c && rdlength == 16) {
add_address(q->addr, AF_INET6, start - rdlength, 0, preference);
}
#endif
}
}
}
static void end_doh_lookup(struct dnsquery_doh *doh)
{
uttime ttl;
struct dnsquery *q;
if (--doh->in_progress)
return;
q = doh->q;
ttl = doh->ttl;
mem_free(doh);
if (ttl * 1000 / 1000 != ttl)
ttl = -1;
else
ttl *= 1000;
end_dns_lookup(q, q->addr ? !q->addr->n : 1, ttl);
}
static void doh_end(struct status *stat, void *doh_)
{
struct dnsquery_doh *doh = (struct dnsquery_doh *)doh_;
struct dnsquery_addr *da = get_struct(stat, struct dnsquery_addr, stat);
struct cache_entry *ce;
if (stat->state >= 0)
return;
if (stat->state == S__OK && (ce = stat->ce)) {
if (ce->redirect && da->redirect_cnt++ < MAX_REDIRECTS) {
unsigned char *u;
u = join_urls(da->url, ce->redirect);
if (!strchr(cast_const_char u, POST_CHAR)) {
unsigned char *pc = cast_uchar strchr(cast_const_char da->url, POST_CHAR);
if (pc)
add_to_strn(&u, pc);
}
mem_free(da->url);
da->url = u;
load_url(u, NULL, &da->stat, PRI_DOH, NC_RELOAD, 1, 1, 0, 0);
return;
}
if (ce->http_code == 200) {
unsigned char *start;
size_t len;
get_file_by_term(NULL, ce, &start, &len, NULL);
parse_dns_reply(doh->q, start, len);
}
}
mem_free(da->url);
end_doh_lookup(doh);
}
static int add_host_name(unsigned char **r, int *l, unsigned char *name)
{
while (*name) {
size_t len = strcspn(cast_const_char name, ".");
if (!len || len > 63)
return -1;
add_chr_to_str(r, l, (unsigned char)len);
add_bytes_to_str(r, l, name, len);
name += len;
if (*name == '.')
name++;
}
add_chr_to_str(r, l, 0);
return 0;
}
void do_doh_lookup(struct dnsquery *q)
{
int p;
struct dnsquery_doh *doh;
int bad_name = 0;
q->doh = doh = mem_calloc(sizeof(struct dnsquery_doh));
doh->q = q;
doh->ttl = -1;
for (p = 0; p < 2; p++) {
struct dnsquery_addr *da = !p ? &doh->ipv4 : &doh->ipv6;
unsigned char *u;
int ul;
unsigned char *r;
int rl;
int i;
if (!p) {
#ifdef SUPPORT_IPV6
if (q->addr_preference == ADDR_PREFERENCE_IPV6_ONLY)
continue;
#endif
} else {
#ifndef SUPPORT_IPV6
continue;
#endif
if (q->addr_preference == ADDR_PREFERENCE_IPV4_ONLY)
continue;
}
doh->in_progress++;
da->stat.end = doh_end;
da->stat.data = doh;
u = init_str();
ul = 0;
memset(q->addr, 0, sizeof(struct lookup_result));
if (!casecmp(dns_over_https, cast_uchar "http://", 7) || !casecmp(dns_over_https, cast_uchar "https://", 8)) {
add_to_str(&u, &ul, dns_over_https);
} else {
int ipv6 = 0;
add_to_str(&u, &ul, cast_uchar "https://");
#ifdef SUPPORT_IPV6
if (!numeric_ipv6_address(dns_over_https, NULL, NULL))
ipv6 = 1;
#endif
if (ipv6)
add_chr_to_str(&u, &ul, '[');
add_to_str(&u, &ul, dns_over_https);
if (ipv6)
add_chr_to_str(&u, &ul, ']');
add_to_str(&u, &ul, cast_uchar "/dns-query");
}
add_chr_to_str(&u, &ul, POST_CHAR);
add_to_str(&u, &ul, cast_uchar "application/dns-message\n");
r = init_str();
rl = 0;
add_bytes_to_str(&r, &rl, cast_uchar "\0\0\1\0\0\1\0\0\0\0\0\0", 12);
if (add_host_name(&r, &rl, q->name))
bad_name = 1;
if (!p)
add_bytes_to_str(&r, &rl, cast_uchar "\0\1\0\1", 4);
else
add_bytes_to_str(&r, &rl, cast_uchar "\0\34\0\1", 4);
for (i = 0; i < rl; i++) {
unsigned char p[3];
sprintf(cast_char p, "%02x", (int)r[i]);
add_bytes_to_str(&u, &ul, p, 2);
}
mem_free(r);
da->url = u;
}
for (p = 0; p < 2; p++) {
struct dnsquery_addr *da = !p ? &doh->ipv4 : &doh->ipv6;
if (da->url) {
if (bad_name) {
mem_free(da->url);
end_doh_lookup(doh);
} else {
load_url(da->url, NULL, &da->stat, PRI_DOH, NC_RELOAD, 1, 1, 0, 0);
}
}
}
}