diff options
Diffstat (limited to 'libc/inet/resolv.c')
-rw-r--r-- | libc/inet/resolv.c | 189 |
1 files changed, 69 insertions, 120 deletions
diff --git a/libc/inet/resolv.c b/libc/inet/resolv.c index ed2e0d2fa..8bbd7c7cd 100644 --- a/libc/inet/resolv.c +++ b/libc/inet/resolv.c @@ -11,6 +11,7 @@ /* * Portions Copyright (c) 1985, 1993 * The Regents of the University of California. All rights reserved. + * Portions Copyright © 2021 mirabilos <m@mirbsd.org> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -395,14 +396,6 @@ extern int __dns_lookup(const char *name, int type, unsigned char **outpacket, struct resolv_answer *a) attribute_hidden; -extern int __encode_dotted(const char *dotted, - unsigned char *dest, - int maxlen) attribute_hidden; -extern int __decode_dotted(const unsigned char *packet, - int offset, - int packet_len, - char *dest, - int dest_len) attribute_hidden; extern int __encode_header(struct resolv_header *h, unsigned char *dest, int maxlen) attribute_hidden; @@ -416,6 +409,13 @@ extern int __encode_answer(struct resolv_answer *a, int maxlen) attribute_hidden; extern void __open_nameservers(void) attribute_hidden; extern void __close_nameservers(void) attribute_hidden; +extern int __hnbad(const char *dotted) attribute_hidden; + +#define __encode_dotted(dotted,dest,maxlen) \ + dn_comp((dotted), (dest), (maxlen), NULL, NULL) +#define __decode_dotted(packet,offset,packet_len,dest,dest_len) \ + dn_expand((packet), (packet) + (packet_len), (packet) + (offset), \ + (dest), (dest_len)) /* * Theory of operation. @@ -552,116 +552,6 @@ void __decode_header(unsigned char *data, #endif /* L_decodeh */ -#ifdef L_encoded - -/* Encode a dotted string into nameserver transport-level encoding. - This routine is fairly dumb, and doesn't attempt to compress - the data */ -int __encode_dotted(const char *dotted, unsigned char *dest, int maxlen) -{ - unsigned used = 0; - - while (dotted && *dotted) { - char *c = strchr(dotted, '.'); - int l = c ? c - dotted : strlen(dotted); - - /* two consecutive dots are not valid */ - if (l == 0) - return -1; - - if (l >= (maxlen - used - 1)) - return -1; - - dest[used++] = l; - memcpy(dest + used, dotted, l); - used += l; - - if (!c) - break; - dotted = c + 1; - } - - if (maxlen < 1) - return -1; - - dest[used++] = 0; - - return used; -} -#endif /* L_encoded */ - - -#ifdef L_decoded - -/* Decode a dotted string from nameserver transport-level encoding. - This routine understands compressed data. */ -int __decode_dotted(const unsigned char *packet, - int offset, - int packet_len, - char *dest, - int dest_len) -{ - unsigned b; - bool measure = 1; - unsigned total = 0; - unsigned used = 0; - unsigned maxiter = 256; - - if (!packet) - return -1; - - dest[0] = '\0'; - while (--maxiter) { - if (offset >= packet_len) - return -1; - b = packet[offset++]; - if (b == 0) - break; - - if (measure) - total++; - - if ((b & 0xc0) == 0xc0) { - if (offset >= packet_len) - return -1; - if (measure) - total++; - /* compressed item, redirect */ - offset = ((b & 0x3f) << 8) | packet[offset]; - measure = 0; - continue; - } - - if (used + b + 1 >= dest_len) - return -1; - if (offset + b >= packet_len) - return -1; - memcpy(dest + used, packet + offset, b); - offset += b; - used += b; - - if (measure) - total += b; - - if (packet[offset] != 0) - dest[used++] = '.'; - else - dest[used++] = '\0'; - } - if (!maxiter) - return -1; - - /* The null byte must be counted too */ - if (measure) - total++; - - DPRINTF("Total decode len = %d\n", total); - - return total; -} -#endif /* L_decoded */ - - #ifdef L_encodeq int __encode_question(const struct resolv_question *q, @@ -1204,6 +1094,7 @@ int __dns_lookup(const char *name, bool ends_with_dot; bool contains_dot; sockaddr46_t sa; + int num_answers; fd = -1; lookup = NULL; @@ -1446,6 +1337,7 @@ int __dns_lookup(const char *name, goto fail1; } pos = HFIXEDSZ; + /*XXX TODO: check that question matches query (and qdcount==1?) */ for (j = 0; j < h.qdcount; j++) { DPRINTF("Skipping question %d at %d\n", j, pos); i = __length_question(packet + pos, packet_len - pos); @@ -1460,6 +1352,7 @@ int __dns_lookup(const char *name, DPRINTF("Decoding answer at pos %d\n", pos); first_answer = 1; + num_answers = 0; a->dotted = NULL; for (j = 0; j < h.ancount; j++) { i = __decode_answer(packet, pos, packet_len, &ma); @@ -1467,12 +1360,15 @@ int __dns_lookup(const char *name, DPRINTF("failed decode %d\n", i); /* If the message was truncated but we have * decoded some answers, pretend it's OK */ - if (j && h.tc) + if (num_answers && h.tc) break; goto try_next_server; } pos += i; + if (__hnbad(ma.dotted)) + break; + ++num_answers; if (first_answer) { ma.buf = a->buf; ma.buflen = a->buflen; @@ -1502,6 +1398,10 @@ int __dns_lookup(const char *name, ++a->add_count; } } + if (!num_answers) { + h_errno = NO_RECOVERY; + goto fail1; + } /* Success! */ DPRINTF("Answer name = |%s|\n", a->dotted); @@ -2468,7 +2368,7 @@ int gethostbyaddr_r(const void *addr, socklen_t addrlen, /* Decode CNAME into buf, feed it to __dns_lookup() again */ i = __decode_dotted(packet, a.rdoffset, packet_len, buf, buflen); free(packet); - if (i < 0) { + if (i < 0 || __hnbad(buf)) { *h_errnop = NO_RECOVERY; return -1; } @@ -2477,6 +2377,10 @@ int gethostbyaddr_r(const void *addr, socklen_t addrlen, if (a.atype == T_PTR) { /* ADDRESS */ i = __decode_dotted(packet, a.rdoffset, packet_len, buf, buflen); free(packet); + if (__hnbad(buf)) { + *h_errnop = NO_RECOVERY; + return -1; + } result_buf->h_name = buf; result_buf->h_addrtype = type; result_buf->h_length = addrlen; @@ -3042,6 +2946,51 @@ int ns_name_pton(const char *src, u_char *dst, size_t dstsiz) libc_hidden_def(ns_name_pton) /* + * __hnbad(dotted) + * Check whether a name is valid enough for DNS. The rules, as + * laid down by glibc, are: + * - printable input string + * - converts to label notation + * - each label only contains [0-9a-zA-Z_-], up to 63 octets + * - first label doesn’t begin with ‘-’ + * This both is weaker than Unix hostnames (e.g. it allows + * underscores and leading/trailing hyphen-minus) and stronger + * than general (e.g. a leading “*.” is valid sometimes), take care. + * return: + * 0 if the name is ok + */ +int __hnbad(const char *dotted) +{ + unsigned char c, n, *cp; + unsigned char buf[NS_MAXCDNAME]; + + cp = (unsigned char *)dotted; + while ((c = *cp++)) + if (c < 0x21 || c > 0x7E) + return (1); + if (ns_name_pton(dotted, buf, sizeof(buf)) < 0) + return (2); + if (buf[0] > 0 && buf[1] == '-') + return (3); + cp = buf; + while ((n = *cp++)) { + if (n > 63) + return (4); + while (n--) { + c = *cp++; + if (c < '-' || + (c > '-' && c < '0') || + (c > '9' && c < 'A') || + (c > 'Z' && c < '_') || + (c > '_' && c < 'a') || + c > 'z') + return (5); + } + } + return (0); +} + +/* * ns_name_unpack(msg, eom, src, dst, dstsiz) * Unpack a domain name from a message, source may be compressed. * return: |