rspamd/contrib/librdns/packet.c

285 lines
6.4 KiB
C

/* 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 */
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;
}