/* Copyright (c) 2014, Vsevolod Stakhov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "rdns.h" #include "dns_private.h" #include "punycode.h" #include "packet.h" #include "util.h" #include "logger.h" #include "compression.h" void rdns_allocate_packet (struct rdns_request* req, unsigned int namelen) { namelen += 96 + 2 + 4 + 11; /* EDNS0 RR */ req->packet = malloc (namelen); req->pos = 0; req->packet_len = namelen; } void rdns_make_dns_header (struct rdns_request *req, unsigned int qcount) { struct dns_header *header; /* Set DNS header values */ header = (struct dns_header *)req->packet; memset (header, 0 , sizeof (struct dns_header)); header->qid = rdns_permutor_generate_id (); header->rd = 1; header->qdcount = htons (qcount); header->arcount = htons (1); req->pos += sizeof (struct dns_header); req->id = header->qid; } static bool rdns_maybe_punycode_label (const uint8_t *begin, uint8_t const **dot, size_t *label_len) { bool ret = false; const uint8_t *p = begin; *dot = NULL; while (*p) { if (*p == '.') { *dot = p; break; } else if ((*p) & 0x80) { ret = true; } p ++; } if (*p) { *label_len = p - begin; } else { *label_len = p - begin; } return ret; } bool rdns_format_dns_name (struct rdns_resolver *resolver, const char *in, size_t inlen, char **out, size_t *outlen) { const uint8_t *dot; const uint8_t *p = in, *end = in + inlen; char *o; int labels = 0; size_t label_len, olen, remain; uint32_t *uclabel; size_t punylabel_len, uclabel_len; char tmp_label[DNS_D_MAXLABEL]; bool need_encode = false; if (inlen == 0) { inlen = strlen (in); } /* Check for non-ascii characters */ if (!(resolver->flags & RDNS_RESOLVER_NOIDN)) { while (p != end) { if (*p >= 0x80) { need_encode = true; } else if (*p == '.') { labels++; } p++; } } if (!need_encode) { *out = malloc (inlen + 1); if (*out == NULL) { return false; } o = *out; memcpy (o, in, inlen); o[inlen] = '\0'; *outlen = inlen; return true; } /* We need to encode */ p = in; /* We allocate 4 times more memory as we cannot guarantee encoding bounds */ olen = inlen * sizeof (int32_t) + 1 + sizeof ("xn--") * labels; *out = malloc (olen + 1); if (*out == NULL) { return false; } o = *out; remain = olen; while (p != end) { /* Check label for unicode characters */ if (rdns_maybe_punycode_label (p, &dot, &label_len)) { /* Convert to ucs4 */ if (rdns_utf8_to_ucs4 (p, label_len, &uclabel, &uclabel_len) == 0) { punylabel_len = DNS_D_MAXLABEL; rdns_punycode_label_toascii (uclabel, uclabel_len, tmp_label, &punylabel_len); if (remain >= punylabel_len + 1) { memcpy (o, tmp_label, punylabel_len); o += punylabel_len; *o++ = '.'; remain -= punylabel_len + 1; } else { rdns_info ("no buffer remain for punycoding query"); goto err; } free (uclabel); if (dot) { p = dot + 1; } else { break; } } else { break; } } else { if (dot) { if (label_len > DNS_D_MAXLABEL) { rdns_info ("dns name component is longer than 63 bytes, should be stripped"); label_len = DNS_D_MAXLABEL; } if (remain < label_len + 1) { rdns_info ("no buffer remain for punycoding query"); goto err; } if (label_len == 0) { /* Two dots in order, skip this */ rdns_info ("name contains two or more dots in a row, replace it with one dot"); p = dot + 1; continue; } memcpy (o, p, label_len); o += label_len; *o++ = '.'; remain -= label_len + 1; p = dot + 1; } else { if (label_len == 0) { /* If name is ended with dot */ break; } if (label_len > DNS_D_MAXLABEL) { rdns_info ("dns name component is longer than 63 bytes, should be stripped"); label_len = DNS_D_MAXLABEL; } if (remain < label_len + 1) { rdns_info ("no buffer remain for punycoding query"); goto err; } memcpy (o, p, label_len); o += label_len; *o++ = '.'; remain -= label_len + 1; p = dot + 1; break; } } if (remain == 0) { rdns_info ("no buffer remain for punycoding query"); goto err; } } *o = '\0'; *outlen = o - *out; return true; err: free (*out); *out = NULL; return false; } #define U16_TO_WIRE_ADVANCE(val, p8) \ *p8++ = (uint8_t)(((uint16_t)(val)) >> 8); \ *p8++ = (uint8_t)(((uint16_t)(val)) & 0xFF); bool rdns_add_rr (struct rdns_request *req, const char *name, unsigned int len, enum dns_type type, struct rdns_compression_entry **comp) { uint8_t *p8; if (!rdns_write_name_compressed (req, name, len, comp)) { return false; } p8 = (req->packet + req->pos); U16_TO_WIRE_ADVANCE (type, p8); U16_TO_WIRE_ADVANCE (DNS_C_IN, p8); req->pos += sizeof (uint16_t) * 2; return true; } bool rdns_add_edns0 (struct rdns_request *req) { uint8_t *p8; p8 = (req->packet + req->pos); *p8++ = '\0'; /* Name is root */ U16_TO_WIRE_ADVANCE (DNS_T_OPT, p8); U16_TO_WIRE_ADVANCE (UDP_PACKET_SIZE, p8); U16_TO_WIRE_ADVANCE (0, p8); if (req->resolver->enable_dnssec) { *p8++ = 0x80; } else { *p8++ = 0x00; } *p8++ = 0; /* Length */ U16_TO_WIRE_ADVANCE (0, p8); req->pos += sizeof (uint8_t) + sizeof (uint16_t) * 5; return true; }