diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
commit | 133a45c109da5310add55824db21af5239951f93 (patch) | |
tree | ba6ac4c0a950a0dda56451944315d66409923918 /contrib/librdns | |
parent | Initial commit. (diff) | |
download | rspamd-upstream.tar.xz rspamd-upstream.zip |
Adding upstream version 3.8.1.upstream/3.8.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | contrib/librdns/CMakeLists.txt | 11 | ||||
-rw-r--r-- | contrib/librdns/compression.c | 168 | ||||
-rw-r--r-- | contrib/librdns/compression.h | 48 | ||||
-rw-r--r-- | contrib/librdns/curve.c | 890 | ||||
-rw-r--r-- | contrib/librdns/dns_private.h | 338 | ||||
-rw-r--r-- | contrib/librdns/logger.c | 53 | ||||
-rw-r--r-- | contrib/librdns/logger.h | 48 | ||||
-rw-r--r-- | contrib/librdns/packet.c | 288 | ||||
-rw-r--r-- | contrib/librdns/packet.h | 61 | ||||
-rw-r--r-- | contrib/librdns/parse.c | 461 | ||||
-rw-r--r-- | contrib/librdns/parse.h | 65 | ||||
-rw-r--r-- | contrib/librdns/punycode.c | 303 | ||||
-rw-r--r-- | contrib/librdns/punycode.h | 59 | ||||
-rw-r--r-- | contrib/librdns/rdns.h | 493 | ||||
-rw-r--r-- | contrib/librdns/rdns_curve.h | 75 | ||||
-rw-r--r-- | contrib/librdns/rdns_ev.h | 235 | ||||
-rw-r--r-- | contrib/librdns/rdns_event.h | 231 | ||||
-rw-r--r-- | contrib/librdns/ref.h | 71 | ||||
-rw-r--r-- | contrib/librdns/resolver.c | 1577 | ||||
-rw-r--r-- | contrib/librdns/upstream.h | 282 | ||||
-rw-r--r-- | contrib/librdns/util.c | 1035 | ||||
-rw-r--r-- | contrib/librdns/util.h | 99 |
22 files changed, 6891 insertions, 0 deletions
diff --git a/contrib/librdns/CMakeLists.txt b/contrib/librdns/CMakeLists.txt new file mode 100644 index 0000000..a5733e6 --- /dev/null +++ b/contrib/librdns/CMakeLists.txt @@ -0,0 +1,11 @@ +SET(LIBRDNSSRC util.c + logger.c + compression.c + punycode.c + curve.c + parse.c + packet.c + resolver.c) + +ADD_LIBRARY(rdns STATIC ${LIBRDNSSRC}) +SET_TARGET_PROPERTIES(rdns PROPERTIES COMPILE_FLAGS "-DUSE_RSPAMD_CRYPTOBOX")
\ No newline at end of file diff --git a/contrib/librdns/compression.c b/contrib/librdns/compression.c new file mode 100644 index 0000000..895178e --- /dev/null +++ b/contrib/librdns/compression.c @@ -0,0 +1,168 @@ +/* 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 "compression.h" +#include "logger.h" +#include "contrib/mumhash/mum.h" + +#define rdns_compression_hash(n) (mum_hash(n.suffix, n.suffix_len, 0xdeadbeef)) +#define rdns_compression_equal(n1, n2) ((n1).suffix_len == (n2).suffix_len && \ + (memcmp((n1).suffix, (n2).suffix, (n1).suffix_len) == 0)) +__KHASH_IMPL(rdns_compression_hash, kh_inline, struct rdns_compression_name, char, 0, rdns_compression_hash, + rdns_compression_equal); + +static struct rdns_compression_name * +rdns_can_compress (const char *pos, unsigned int len, khash_t(rdns_compression_hash) *comp) +{ + struct rdns_compression_name check; + khiter_t k; + + if (comp == NULL) { + return NULL; + } + + check.suffix_len = len; + check.suffix = pos; + k = kh_get(rdns_compression_hash, comp, check); + + if (k != kh_end(comp)) { + return &kh_key(comp, k); + } + + return NULL; +} + +static unsigned int +rdns_calculate_label_len (const char *pos, const char *end) +{ + const char *p = pos; + unsigned int res = 0; + + while (p != end) { + if (*p == '.') { + break; + } + res ++; + p ++; + } + return res; +} + +static void +rdns_add_compressed (const char *pos, const char *end, + khash_t(rdns_compression_hash) *comp, + int offset) +{ + struct rdns_compression_name new_name; + int r; + + if (comp != NULL) { + + assert (offset >= 0); + new_name.suffix_len = end - pos; + new_name.suffix = pos; + new_name.offset = offset; + + kh_put(rdns_compression_hash, comp, new_name, &r); + } +} + +void +rdns_compression_free (khash_t(rdns_compression_hash) *comp) +{ + if (comp != NULL) { + kh_destroy(rdns_compression_hash, comp); + } +} + +bool +rdns_write_name_compressed (struct rdns_request *req, + const char *name, unsigned int namelen, + khash_t(rdns_compression_hash) **comp) +{ + uint8_t *target = req->packet + req->pos; + const char *pos = name, *end = name + namelen; + unsigned int remain = req->packet_len - req->pos - 5, label_len; + struct rdns_resolver *resolver = req->resolver; + uint16_t pointer; + + if (comp != NULL && *comp == NULL) { + *comp = kh_init(rdns_compression_hash); + } + + while (pos < end && remain > 0) { + if (comp) { + struct rdns_compression_name *test = rdns_can_compress(pos, end - pos, *comp); + if (test != NULL) { + /* Can compress name */ + if (remain < 2) { + rdns_info ("no buffer remain for constructing query"); + return false; + } + + pointer = htons ((uint16_t) test->offset) | DNS_COMPRESSION_BITS; + memcpy(target, &pointer, sizeof(pointer)); + req->pos += 2; + + return true; + } + } + + label_len = rdns_calculate_label_len (pos, end); + if (label_len == 0) { + /* We have empty label it is allowed only if pos == end - 1 */ + if (pos == end - 1) { + break; + } + else { + rdns_err ("double dots in the name requested"); + return false; + } + } + else if (label_len > DNS_D_MAXLABEL) { + rdns_err ("too large label: %d", (int)label_len); + return false; + } + + if (label_len + 1 > remain) { + rdns_info ("no buffer remain for constructing query, strip %d to %d", + (int)label_len, (int)remain); + label_len = remain - 1; + } + + if (comp) { + rdns_add_compressed(pos, end, *comp, target - req->packet); + } + /* Write label as is */ + *target++ = (uint8_t)label_len; + memcpy (target, pos, label_len); + target += label_len; + pos += label_len + 1; + } + + /* Termination label */ + *target++ = '\0'; + req->pos = target - req->packet; + + return true; +} diff --git a/contrib/librdns/compression.h b/contrib/librdns/compression.h new file mode 100644 index 0000000..6056117 --- /dev/null +++ b/contrib/librdns/compression.h @@ -0,0 +1,48 @@ +/* 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. + */ +#ifndef COMPRESSION_H_ +#define COMPRESSION_H_ + +#include "dns_private.h" +#include "khash.h" + +struct rdns_compression_name { + const char *suffix; /**< suffix packed */ + unsigned int suffix_len; /**< length of the suffix */ + unsigned int offset; /**< offset in the packet */ +}; + + +KHASH_DECLARE(rdns_compression_hash, struct rdns_compression_name, char); + +/** + * Try to compress name passed or write it 'as is' + * @return + */ +bool rdns_write_name_compressed (struct rdns_request *req, + const char *name, unsigned int namelen, + khash_t(rdns_compression_hash) **comp); + +void rdns_compression_free (khash_t(rdns_compression_hash) *comp); + +#endif /* COMPRESSION_H_ */ diff --git a/contrib/librdns/curve.c b/contrib/librdns/curve.c new file mode 100644 index 0000000..19ec250 --- /dev/null +++ b/contrib/librdns/curve.c @@ -0,0 +1,890 @@ +/* + * 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 BY AUTHOR ''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 <sys/types.h> +#include <sys/uio.h> +#include <sys/socket.h> +#include <unistd.h> + +#include "rdns.h" +#include "dns_private.h" +#include "rdns_curve.h" +#include "ottery.h" +#include "ref.h" +#include "logger.h" + +#ifdef TWEETNACL + +#include <tweetnacl.h> + +void +randombytes(uint8_t *data, uint64_t len) +{ + ottery_rand_bytes (data, len); +} +void sodium_memzero (uint8_t *data, uint64_t len) +{ + volatile uint8_t *p = data; + + while (len--) { + *p = '\0'; + } +} +void sodium_init(void) +{ + +} + +ssize_t rdns_curve_send (struct rdns_request *req, void *plugin_data); +ssize_t rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, + void *plugin_data, struct rdns_request **req_out); +void rdns_curve_finish_request (struct rdns_request *req, void *plugin_data); +void rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data); + +struct rdns_curve_entry { + char *name; + unsigned char pk[crypto_box_PUBLICKEYBYTES]; + UT_hash_handle hh; +}; + +struct rdns_curve_nm_entry { + unsigned char k[crypto_box_BEFORENMBYTES]; + struct rdns_curve_entry *entry; + struct rdns_curve_nm_entry *prev, *next; +}; + +struct rdns_curve_client_key { + unsigned char pk[crypto_box_PUBLICKEYBYTES]; + unsigned char sk[crypto_box_SECRETKEYBYTES]; + struct rdns_curve_nm_entry *nms; + uint64_t counter; + unsigned int uses; + ref_entry_t ref; +}; + +struct rdns_curve_request { + struct rdns_request *req; + struct rdns_curve_client_key *key; + struct rdns_curve_entry *entry; + struct rdns_curve_nm_entry *nm; + unsigned char nonce[crypto_box_NONCEBYTES]; + UT_hash_handle hh; +}; + +struct rdns_curve_ctx { + struct rdns_curve_entry *entries; + struct rdns_curve_client_key *cur_key; + struct rdns_curve_request *requests; + double key_refresh_interval; + void *key_refresh_event; + struct rdns_resolver *resolver; +}; + +static struct rdns_curve_client_key * +rdns_curve_client_key_new (struct rdns_curve_ctx *ctx) +{ + struct rdns_curve_client_key *new; + struct rdns_curve_nm_entry *nm; + struct rdns_curve_entry *entry, *tmp; + + new = calloc (1, sizeof (struct rdns_curve_client_key)); + crypto_box_keypair (new->pk, new->sk); + + HASH_ITER (hh, ctx->entries, entry, tmp) { + nm = calloc (1, sizeof (struct rdns_curve_nm_entry)); + nm->entry = entry; + crypto_box_beforenm (nm->k, entry->pk, new->sk); + + DL_APPEND (new->nms, nm); + } + + new->counter = ottery_rand_uint64 (); + + return new; +} + +static struct rdns_curve_nm_entry * +rdns_curve_find_nm (struct rdns_curve_client_key *key, struct rdns_curve_entry *entry) +{ + struct rdns_curve_nm_entry *nm; + + DL_FOREACH (key->nms, nm) { + if (nm->entry == entry) { + return nm; + } + } + + return NULL; +} + +static void +rdns_curve_client_key_free (struct rdns_curve_client_key *key) +{ + struct rdns_curve_nm_entry *nm, *tmp; + + DL_FOREACH_SAFE (key->nms, nm, tmp) { + sodium_memzero (nm->k, sizeof (nm->k)); + free (nm); + } + sodium_memzero (key->sk, sizeof (key->sk)); + free (key); +} + +struct rdns_curve_ctx* +rdns_curve_ctx_new (double key_refresh_interval) +{ + struct rdns_curve_ctx *new; + + new = calloc (1, sizeof (struct rdns_curve_ctx)); + new->key_refresh_interval = key_refresh_interval; + + return new; +} + +void +rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx, + const char *name, const unsigned char *pubkey) +{ + struct rdns_curve_entry *entry; + bool success = true; + + entry = malloc (sizeof (struct rdns_curve_entry)); + if (entry != NULL) { + entry->name = strdup (name); + if (entry->name == NULL) { + success = false; + } + memcpy (entry->pk, pubkey, sizeof (entry->pk)); + if (success) { + HASH_ADD_KEYPTR (hh, ctx->entries, entry->name, strlen (entry->name), entry); + } + } +} + +#define rdns_curve_write_hex(in, out, offset, base) do { \ + *(out) |= ((in)[(offset)] - (base)) << ((1 - offset) * 4); \ +} while (0) + +static bool +rdns_curve_hex_to_byte (const char *in, unsigned char *out) +{ + int i; + + for (i = 0; i <= 1; i ++) { + if (in[i] >= '0' && in[i] <= '9') { + rdns_curve_write_hex (in, out, i, '0'); + } + else if (in[i] >= 'a' && in[i] <= 'f') { + rdns_curve_write_hex (in, out, i, 'a' - 10); + } + else if (in[i] >= 'A' && in[i] <= 'F') { + rdns_curve_write_hex (in, out, i, 'A' - 10); + } + else { + return false; + } + } + return true; +} + +#undef rdns_curve_write_hex + +unsigned char * +rdns_curve_key_from_hex (const char *hex) +{ + unsigned int len = strlen (hex), i; + unsigned char *res = NULL; + + if (len == crypto_box_PUBLICKEYBYTES * 2) { + res = calloc (1, crypto_box_PUBLICKEYBYTES); + for (i = 0; i < crypto_box_PUBLICKEYBYTES; i ++) { + if (!rdns_curve_hex_to_byte (&hex[i * 2], &res[i])) { + free (res); + return NULL; + } + } + } + + return res; +} + +void +rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx) +{ + struct rdns_curve_entry *entry, *tmp; + + HASH_ITER (hh, ctx->entries, entry, tmp) { + free (entry->name); + free (entry); + } + + free (ctx); +} + +static void +rdns_curve_refresh_key_callback (void *user_data) +{ + struct rdns_curve_ctx *ctx = user_data; + struct rdns_resolver *resolver; + + resolver = ctx->resolver; + rdns_info ("refresh dnscurve keys"); + REF_RELEASE (ctx->cur_key); + ctx->cur_key = rdns_curve_client_key_new (ctx); + REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free); +} + +void +rdns_curve_register_plugin (struct rdns_resolver *resolver, + struct rdns_curve_ctx *ctx) +{ + struct rdns_plugin *plugin; + + if (!resolver->async_binded) { + return; + } + + plugin = calloc (1, sizeof (struct rdns_plugin)); + if (plugin != NULL) { + plugin->data = ctx; + plugin->type = RDNS_PLUGIN_CURVE; + plugin->cb.curve_plugin.send_cb = rdns_curve_send; + plugin->cb.curve_plugin.recv_cb = rdns_curve_recv; + plugin->cb.curve_plugin.finish_cb = rdns_curve_finish_request; + plugin->dtor = rdns_curve_dtor; + sodium_init (); + ctx->cur_key = rdns_curve_client_key_new (ctx); + REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free); + + if (ctx->key_refresh_interval > 0) { + ctx->key_refresh_event = resolver->async->add_periodic ( + resolver->async->data, ctx->key_refresh_interval, + rdns_curve_refresh_key_callback, ctx); + } + ctx->resolver = resolver; + rdns_resolver_register_plugin (resolver, plugin); + } +} + +ssize_t +rdns_curve_send (struct rdns_request *req, void *plugin_data) +{ + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_entry *entry; + struct iovec iov[4]; + unsigned char *m; + static const char qmagic[] = "Q6fnvWj8"; + struct rdns_curve_request *creq; + struct rdns_curve_nm_entry *nm; + ssize_t ret, boxed_len; + + /* Check for key */ + HASH_FIND_STR (ctx->entries, req->io->srv->name, entry); + if (entry != NULL) { + nm = rdns_curve_find_nm (ctx->cur_key, entry); + creq = malloc (sizeof (struct rdns_curve_request)); + if (creq == NULL) { + return -1; + } + + boxed_len = req->pos + crypto_box_ZEROBYTES; + m = malloc (boxed_len); + if (m == NULL) { + return -1; + } + + /* Ottery is faster than sodium native PRG that uses /dev/random only */ + memcpy (creq->nonce, &ctx->cur_key->counter, sizeof (uint64_t)); + ottery_rand_bytes (creq->nonce + sizeof (uint64_t), 12 - sizeof (uint64_t)); + sodium_memzero (creq->nonce + 12, crypto_box_NONCEBYTES - 12); + + sodium_memzero (m, crypto_box_ZEROBYTES); + memcpy (m + crypto_box_ZEROBYTES, req->packet, req->pos); + + if (crypto_box_afternm (m, m, boxed_len, + creq->nonce, nm->k) == -1) { + sodium_memzero (m, boxed_len); + free (m); + return -1; + } + + creq->key = ctx->cur_key; + REF_RETAIN (ctx->cur_key); + creq->entry = entry; + creq->req = req; + creq->nm = nm; + HASH_ADD_KEYPTR (hh, ctx->requests, creq->nonce, 12, creq); + req->curve_plugin_data = creq; + + ctx->cur_key->counter ++; + ctx->cur_key->uses ++; + + /* Now form a dnscurve packet */ + iov[0].iov_base = (void *)qmagic; + iov[0].iov_len = sizeof (qmagic) - 1; + iov[1].iov_base = ctx->cur_key->pk; + iov[1].iov_len = sizeof (ctx->cur_key->pk); + iov[2].iov_base = creq->nonce; + iov[2].iov_len = 12; + iov[3].iov_base = m + crypto_box_BOXZEROBYTES; + iov[3].iov_len = boxed_len - crypto_box_BOXZEROBYTES; + + ret = writev (req->io->sock, iov, sizeof (iov) / sizeof (iov[0])); + sodium_memzero (m, boxed_len); + free (m); + } + else { + ret = write (req->io->sock, req->packet, req->pos); + req->curve_plugin_data = NULL; + } + + return ret; +} + +ssize_t +rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, void *plugin_data, + struct rdns_request **req_out) +{ + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + ssize_t ret, boxlen; + static const char rmagic[] = "R6fnvWJ8"; + unsigned char *p, *box; + unsigned char enonce[crypto_box_NONCEBYTES]; + struct rdns_curve_request *creq; + struct rdns_resolver *resolver; + + resolver = ctx->resolver; + ret = read (ioc->sock, buf, len); + + if (ret <= 0 || ret < 64) { + /* Definitely not a DNSCurve packet */ + return ret; + } + + if (memcmp (buf, rmagic, sizeof (rmagic) - 1) == 0) { + /* Likely DNSCurve packet */ + p = ((unsigned char *)buf) + 8; + HASH_FIND (hh, ctx->requests, p, 12, creq); + if (creq == NULL) { + rdns_info ("unable to find nonce in the internal hash"); + return ret; + } + memcpy (enonce, p, crypto_box_NONCEBYTES); + p += crypto_box_NONCEBYTES; + boxlen = ret - crypto_box_NONCEBYTES + + crypto_box_BOXZEROBYTES - + sizeof (rmagic) + 1; + if (boxlen < 0) { + return ret; + } + box = malloc (boxlen); + sodium_memzero (box, crypto_box_BOXZEROBYTES); + memcpy (box + crypto_box_BOXZEROBYTES, p, + boxlen - crypto_box_BOXZEROBYTES); + + if (crypto_box_open_afternm (box, box, boxlen, enonce, creq->nm->k) != -1) { + memcpy (buf, box + crypto_box_ZEROBYTES, + boxlen - crypto_box_ZEROBYTES); + ret = boxlen - crypto_box_ZEROBYTES; + *req_out = creq->req; + } + else { + rdns_info ("unable open cryptobox of size %d", (int)boxlen); + } + free (box); + } + + return ret; +} + +void +rdns_curve_finish_request (struct rdns_request *req, void *plugin_data) +{ + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_request *creq = req->curve_plugin_data; + + if (creq != NULL) { + REF_RELEASE (creq->key); + HASH_DELETE (hh, ctx->requests, creq); + } +} + +void +rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data) +{ + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + + if (ctx->key_refresh_event != NULL) { + resolver->async->del_periodic (resolver->async->data, + ctx->key_refresh_event); + } + REF_RELEASE (ctx->cur_key); +} +#elif defined(USE_RSPAMD_CRYPTOBOX) + +#include "cryptobox.h" + + +#ifndef crypto_box_ZEROBYTES +#define crypto_box_ZEROBYTES 32 +#endif +#ifndef crypto_box_BOXZEROBYTES +#define crypto_box_BOXZEROBYTES 16 +#endif + +ssize_t rdns_curve_send (struct rdns_request *req, void *plugin_data, + struct sockaddr *saddr, socklen_t slen); +ssize_t rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, + void *plugin_data, struct rdns_request **req_out, + struct sockaddr *saddr, socklen_t slen); +void rdns_curve_finish_request (struct rdns_request *req, void *plugin_data); +void rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data); + +struct rdns_curve_entry { + char *name; + rspamd_pk_t pk; + UT_hash_handle hh; +}; + +struct rdns_curve_nm_entry { + rspamd_nm_t k; + struct rdns_curve_entry *entry; + struct rdns_curve_nm_entry *prev, *next; +}; + +struct rdns_curve_client_key { + rspamd_pk_t pk; + rspamd_sk_t sk; + struct rdns_curve_nm_entry *nms; + uint64_t counter; + unsigned int uses; + ref_entry_t ref; +}; + +struct rdns_curve_request { + struct rdns_request *req; + struct rdns_curve_client_key *key; + struct rdns_curve_entry *entry; + struct rdns_curve_nm_entry *nm; + rspamd_nonce_t nonce; + UT_hash_handle hh; +}; + +struct rdns_curve_ctx { + struct rdns_curve_entry *entries; + struct rdns_curve_client_key *cur_key; + struct rdns_curve_request *requests; + double key_refresh_interval; + void *key_refresh_event; + struct rdns_resolver *resolver; +}; + +static struct rdns_curve_client_key * +rdns_curve_client_key_new (struct rdns_curve_ctx *ctx) +{ + struct rdns_curve_client_key *new; + struct rdns_curve_nm_entry *nm; + struct rdns_curve_entry *entry, *tmp; + + new = calloc (1, sizeof (struct rdns_curve_client_key)); + rspamd_cryptobox_keypair (new->pk, new->sk, RSPAMD_CRYPTOBOX_MODE_25519); + + HASH_ITER (hh, ctx->entries, entry, tmp) { + nm = calloc (1, sizeof (struct rdns_curve_nm_entry)); + nm->entry = entry; + rspamd_cryptobox_nm (nm->k, entry->pk, new->sk, + RSPAMD_CRYPTOBOX_MODE_25519); + + DL_APPEND (new->nms, nm); + } + + new->counter = ottery_rand_uint64 (); + + return new; +} + +static struct rdns_curve_nm_entry * +rdns_curve_find_nm (struct rdns_curve_client_key *key, struct rdns_curve_entry *entry) +{ + struct rdns_curve_nm_entry *nm; + + DL_FOREACH (key->nms, nm) { + if (nm->entry == entry) { + return nm; + } + } + + return NULL; +} + +static void +rdns_curve_client_key_free (struct rdns_curve_client_key *key) +{ + struct rdns_curve_nm_entry *nm, *tmp; + + DL_FOREACH_SAFE (key->nms, nm, tmp) { + rspamd_explicit_memzero (nm->k, sizeof (nm->k)); + free (nm); + } + + rspamd_explicit_memzero (key->sk, sizeof (key->sk)); + free (key); +} + +struct rdns_curve_ctx* +rdns_curve_ctx_new (double key_refresh_interval) +{ + struct rdns_curve_ctx *new; + + new = calloc (1, sizeof (struct rdns_curve_ctx)); + new->key_refresh_interval = key_refresh_interval; + + return new; +} + +void +rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx, + const char *name, const unsigned char *pubkey) +{ + struct rdns_curve_entry *entry; + bool success = true; + + entry = malloc (sizeof (struct rdns_curve_entry)); + if (entry != NULL) { + entry->name = strdup (name); + if (entry->name == NULL) { + success = false; + } + memcpy (entry->pk, pubkey, sizeof (entry->pk)); + if (success) { + HASH_ADD_KEYPTR (hh, ctx->entries, entry->name, strlen (entry->name), entry); + } + } +} + +#define rdns_curve_write_hex(in, out, offset, base) do { \ + *(out) |= ((in)[(offset)] - (base)) << ((1 - offset) * 4); \ +} while (0) + +static bool +rdns_curve_hex_to_byte (const char *in, unsigned char *out) +{ + int i; + + for (i = 0; i <= 1; i ++) { + if (in[i] >= '0' && in[i] <= '9') { + rdns_curve_write_hex (in, out, i, '0'); + } + else if (in[i] >= 'a' && in[i] <= 'f') { + rdns_curve_write_hex (in, out, i, 'a' - 10); + } + else if (in[i] >= 'A' && in[i] <= 'F') { + rdns_curve_write_hex (in, out, i, 'A' - 10); + } + else { + return false; + } + } + return true; +} + +#undef rdns_curve_write_hex + +unsigned char * +rdns_curve_key_from_hex (const char *hex) +{ + unsigned int len = strlen (hex), i; + unsigned char *res = NULL; + + if (len == rspamd_cryptobox_pk_bytes (RSPAMD_CRYPTOBOX_MODE_25519) * 2) { + res = calloc (1, rspamd_cryptobox_pk_bytes (RSPAMD_CRYPTOBOX_MODE_25519)); + for (i = 0; + i < rspamd_cryptobox_pk_bytes (RSPAMD_CRYPTOBOX_MODE_25519); + i ++) { + if (!rdns_curve_hex_to_byte (&hex[i * 2], &res[i])) { + free (res); + return NULL; + } + } + } + + return res; +} + +void +rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx) +{ + struct rdns_curve_entry *entry, *tmp; + + HASH_ITER (hh, ctx->entries, entry, tmp) { + free (entry->name); + free (entry); + } + + free (ctx); +} + +static void +rdns_curve_refresh_key_callback (void *user_data) +{ + struct rdns_curve_ctx *ctx = user_data; + struct rdns_resolver *resolver; + + resolver = ctx->resolver; + rdns_info ("refresh dnscurve keys"); + REF_RELEASE (ctx->cur_key); + ctx->cur_key = rdns_curve_client_key_new (ctx); + REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free); +} + +void +rdns_curve_register_plugin (struct rdns_resolver *resolver, + struct rdns_curve_ctx *ctx) +{ + struct rdns_plugin *plugin; + + if (!resolver->async_binded) { + return; + } + + plugin = calloc (1, sizeof (struct rdns_plugin)); + if (plugin != NULL) { + plugin->data = ctx; + plugin->type = RDNS_PLUGIN_CURVE; + plugin->cb.curve_plugin.send_cb = rdns_curve_send; + plugin->cb.curve_plugin.recv_cb = rdns_curve_recv; + plugin->cb.curve_plugin.finish_cb = rdns_curve_finish_request; + plugin->dtor = rdns_curve_dtor; + ctx->cur_key = rdns_curve_client_key_new (ctx); + REF_INIT_RETAIN (ctx->cur_key, rdns_curve_client_key_free); + + if (ctx->key_refresh_interval > 0) { + ctx->key_refresh_event = resolver->async->add_periodic ( + resolver->async->data, ctx->key_refresh_interval, + rdns_curve_refresh_key_callback, ctx); + } + ctx->resolver = resolver; + rdns_resolver_register_plugin (resolver, plugin); + } +} + +ssize_t +rdns_curve_send (struct rdns_request *req, void *plugin_data, + struct sockaddr *saddr, socklen_t slen) +{ + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_entry *entry; + struct iovec iov[4]; + unsigned char *m; + static const char qmagic[] = "Q6fnvWj8"; + struct rdns_curve_request *creq; + struct rdns_curve_nm_entry *nm; + ssize_t ret, boxed_len; + + /* Check for key */ + HASH_FIND_STR (ctx->entries, req->io->srv->name, entry); + if (entry != NULL) { + nm = rdns_curve_find_nm (ctx->cur_key, entry); + creq = malloc (sizeof (struct rdns_curve_request)); + if (creq == NULL) { + return -1; + } + + boxed_len = req->pos + crypto_box_ZEROBYTES; + m = malloc (boxed_len); + if (m == NULL) { + free(creq); + return -1; + } + + /* Ottery is faster than sodium native PRG that uses /dev/random only */ + memcpy (creq->nonce, &ctx->cur_key->counter, sizeof (uint64_t)); + ottery_rand_bytes (creq->nonce + sizeof (uint64_t), 12 - sizeof (uint64_t)); + rspamd_explicit_memzero (creq->nonce + 12, + rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519) - 12); + + rspamd_explicit_memzero (m, crypto_box_ZEROBYTES); + memcpy (m + crypto_box_ZEROBYTES, req->packet, req->pos); + + rspamd_cryptobox_encrypt_nm_inplace (m + crypto_box_ZEROBYTES, + boxed_len, + creq->nonce, + nm->k, + m, + RSPAMD_CRYPTOBOX_MODE_25519); + + creq->key = ctx->cur_key; + REF_RETAIN (ctx->cur_key); + creq->entry = entry; + creq->req = req; + creq->nm = nm; + HASH_ADD_KEYPTR (hh, ctx->requests, creq->nonce, 12, creq); + req->curve_plugin_data = creq; + + ctx->cur_key->counter ++; + ctx->cur_key->uses ++; + + /* Now form a dnscurve packet */ + iov[0].iov_base = (void *)qmagic; + iov[0].iov_len = sizeof (qmagic) - 1; + iov[1].iov_base = ctx->cur_key->pk; + iov[1].iov_len = sizeof (ctx->cur_key->pk); + iov[2].iov_base = creq->nonce; + iov[2].iov_len = 12; + iov[3].iov_base = m + crypto_box_BOXZEROBYTES; + iov[3].iov_len = boxed_len - crypto_box_BOXZEROBYTES; + + struct msghdr msg; + + memset (&msg, 0, sizeof (msg)); + msg.msg_namelen = slen; + msg.msg_name = saddr; + msg.msg_iov = iov; + msg.msg_iovlen = sizeof (iov) / sizeof (iov[0]); + ret = sendmsg (req->io->sock, &msg, 0); + rspamd_explicit_memzero (m, boxed_len); + free (m); + } + else { + ret = sendto (req->io->sock, req->packet, req->pos, 0, saddr, slen); + req->curve_plugin_data = NULL; + } + + return ret; +} + +ssize_t +rdns_curve_recv (struct rdns_io_channel *ioc, void *buf, size_t len, void *plugin_data, + struct rdns_request **req_out, struct sockaddr *saddr, socklen_t slen) +{ + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + ssize_t ret, boxlen; + static const char rmagic[] = "R6fnvWJ8"; + unsigned char *p, *box; + unsigned char enonce[24]; + struct rdns_curve_request *creq; + struct rdns_resolver *resolver; + + resolver = ctx->resolver; + ret = recv (ioc->sock, buf, len, 0); + + if (ret <= 0 || ret < 64) { + /* Definitely not a DNSCurve packet */ + return ret; + } + + if (memcmp (buf, rmagic, sizeof (rmagic) - 1) == 0) { + /* Likely DNSCurve packet */ + p = ((unsigned char *)buf) + 8; + HASH_FIND (hh, ctx->requests, p, 12, creq); + if (creq == NULL) { + rdns_info ("unable to find nonce in the internal hash"); + return ret; + } + + memcpy (enonce, p, rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519)); + p += rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519); + boxlen = ret - rspamd_cryptobox_nonce_bytes (RSPAMD_CRYPTOBOX_MODE_25519) + + crypto_box_BOXZEROBYTES - + sizeof (rmagic) + 1; + if (boxlen < 0) { + return ret; + } + + box = malloc (boxlen); + rspamd_explicit_memzero (box, crypto_box_BOXZEROBYTES); + memcpy (box + crypto_box_BOXZEROBYTES, p, + boxlen - crypto_box_BOXZEROBYTES); + + if (!rspamd_cryptobox_decrypt_nm_inplace ( + box + rspamd_cryptobox_mac_bytes (RSPAMD_CRYPTOBOX_MODE_25519), + boxlen - rspamd_cryptobox_mac_bytes (RSPAMD_CRYPTOBOX_MODE_25519), + enonce, creq->nm->k, box, RSPAMD_CRYPTOBOX_MODE_25519)) { + memcpy (buf, box + crypto_box_ZEROBYTES, + boxlen - crypto_box_ZEROBYTES); + ret = boxlen - crypto_box_ZEROBYTES; + *req_out = creq->req; + } + else { + rdns_info ("unable open cryptobox of size %d", (int)boxlen); + } + + free (box); + } + + return ret; +} + +void +rdns_curve_finish_request (struct rdns_request *req, void *plugin_data) +{ + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + struct rdns_curve_request *creq = req->curve_plugin_data; + + if (creq != NULL) { + REF_RELEASE (creq->key); + HASH_DELETE (hh, ctx->requests, creq); + } +} + +void +rdns_curve_dtor (struct rdns_resolver *resolver, void *plugin_data) +{ + struct rdns_curve_ctx *ctx = (struct rdns_curve_ctx *)plugin_data; + + if (ctx->key_refresh_event != NULL) { + resolver->async->del_periodic (resolver->async->data, + ctx->key_refresh_event); + } + REF_RELEASE (ctx->cur_key); +} +#else + +/* Fake functions */ +struct rdns_curve_ctx* rdns_curve_ctx_new (double rekey_interval) +{ + return NULL; +} +void rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx, + const char *name, const unsigned char *pubkey) +{ + +} +void rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx) +{ + +} +void rdns_curve_register_plugin (struct rdns_resolver *resolver, + struct rdns_curve_ctx *ctx) +{ + +} + +unsigned char * +rdns_curve_key_from_hex (const char *hex) +{ + return NULL; +} +#endif diff --git a/contrib/librdns/dns_private.h b/contrib/librdns/dns_private.h new file mode 100644 index 0000000..c240deb --- /dev/null +++ b/contrib/librdns/dns_private.h @@ -0,0 +1,338 @@ +/* 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. + */ + +#ifndef DNS_PRIVATE_H_ +#define DNS_PRIVATE_H_ + +#include "config.h" +#include "uthash.h" +#include "utlist.h" +#include "khash.h" +#include "rdns.h" +#include "upstream.h" +#include "ref.h" + +static const int dns_port = 53; +static const int default_io_cnt = 8; +static const int default_tcp_io_cnt = 1; + +#define UDP_PACKET_SIZE (4096) + +#define DNS_COMPRESSION_BITS 0xC0 + +#define DNS_D_MAXLABEL 63 /* + 1 '\0' */ +#define DNS_D_MAXNAME 255 /* + 1 '\0' */ + +#define RESOLV_CONF "/etc/resolv.conf" + +struct dns_header { + unsigned int qid :16; + +#if BYTE_ORDER == BIG_ENDIAN + unsigned int qr:1; + unsigned int opcode:4; + unsigned int aa:1; + unsigned int tc:1; + unsigned int rd:1; + + unsigned int ra:1; + unsigned int cd : 1; + unsigned int ad : 1; + unsigned int z : 1; + unsigned int rcode:4; +#else + unsigned int rd :1; + unsigned int tc :1; + unsigned int aa :1; + unsigned int opcode :4; + unsigned int qr :1; + + unsigned int rcode :4; + unsigned int z : 1; + unsigned int ad : 1; + unsigned int cd : 1; + unsigned int ra :1; +#endif + + unsigned int qdcount :16; + unsigned int ancount :16; + unsigned int nscount :16; + unsigned int arcount :16; +}; + +/** + * Represents DNS server + */ +struct rdns_server { + char *name; + unsigned int port; + unsigned int io_cnt; + unsigned int tcp_io_cnt; + + struct rdns_io_channel **io_channels; + struct rdns_io_channel **tcp_io_channels; + void *ups_elt; + upstream_entry_t up; +}; + +enum rdns_request_state { + RDNS_REQUEST_NEW = 0, + RDNS_REQUEST_REGISTERED = 1, + RDNS_REQUEST_WAIT_SEND, + RDNS_REQUEST_WAIT_REPLY, + RDNS_REQUEST_REPLIED, + RDNS_REQUEST_FAKE, + RDNS_REQUEST_ERROR, + RDNS_REQUEST_TCP, +}; + +struct rdns_request { + struct rdns_resolver *resolver; + struct rdns_async_context *async; + struct rdns_io_channel *io; + struct rdns_reply *reply; + enum rdns_request_type type; + + double timeout; + unsigned int retransmits; + + int id; + struct rdns_request_name *requested_names; + unsigned int qcount; + enum rdns_request_state state; + + uint8_t *packet; + off_t pos; + unsigned int packet_len; + + dns_callback_type func; + void *arg; + + void *async_event; + +#if defined(TWEETNACL) || defined(USE_RSPAMD_CRYPTOBOX) + void *curve_plugin_data; +#endif + + ref_entry_t ref; +}; + + +enum rdns_io_channel_flags { + RDNS_CHANNEL_CONNECTED = 1u << 0u, + RDNS_CHANNEL_ACTIVE = 1u << 1u, + RDNS_CHANNEL_TCP = 1u << 2u, + RDNS_CHANNEL_TCP_CONNECTING = 1u << 3u, +}; + +#define IS_CHANNEL_CONNECTED(ioc) (((ioc)->flags & RDNS_CHANNEL_CONNECTED) != 0) +#define IS_CHANNEL_ACTIVE(ioc) (((ioc)->flags & RDNS_CHANNEL_ACTIVE) != 0) +#define IS_CHANNEL_TCP(ioc) (((ioc)->flags & RDNS_CHANNEL_TCP) != 0) + +/** + * Used to chain output DNS requests for a TCP connection + */ +struct rdns_tcp_output_chain { + uint16_t next_write_size; /* Network byte order! */ + uint16_t cur_write; /* Cur bytes written including `next_write_size` */ + unsigned char *write_buf; + struct rdns_tcp_output_chain *prev, *next; +}; + +/** + * Specific stuff for a TCP IO chain + */ +struct rdns_tcp_channel { + uint16_t next_read_size; /* Network byte order on read, then host byte order */ + uint16_t cur_read; /* Cur bytes read including `next_read_size` */ + unsigned char *cur_read_buf; + unsigned read_buf_allocated; + + /* Chained set of the planned writes */ + struct rdns_tcp_output_chain *output_chain; + unsigned cur_output_chains; + + void *async_read; /** async read event */ + void *async_write; /** async write event */ +}; + +KHASH_DECLARE(rdns_requests_hash, int, struct rdns_request *); +#define RDNS_IO_CHANNEL_TAG UINT64_C(0xe190a5ba12f094c8) +/** + * IO channel for a specific DNS server + */ +struct rdns_io_channel { + uint64_t struct_magic; /**< tag for this structure */ + struct rdns_server *srv; + struct rdns_resolver *resolver; + struct sockaddr *saddr; + socklen_t slen; + int sock; /**< persistent socket */ + int flags; /**< see enum rdns_io_channel_flags */ + void *async_io; /** async opaque ptr */ + khash_t(rdns_requests_hash) *requests; + /* + * For DNS replies parsing we use per-channel structure + * which is used for two purposes: + * 1) We read the next DNS header + * 2) We find the corresponding request (if any) + * 3) We read the remaining packet (associated with a request or dangling) + * This structure is filled on each read-readiness for an IO channel + */ + struct rdns_tcp_channel *tcp; + uint64_t uses; + ref_entry_t ref; +}; + +struct rdns_fake_reply_idx { + enum rdns_request_type type; + unsigned len; + char request[0]; +}; + +struct rdns_fake_reply { + enum dns_rcode rcode; + struct rdns_reply_entry *result; + UT_hash_handle hh; + struct rdns_fake_reply_idx key; +}; + + +struct rdns_resolver { + struct rdns_server *servers; + struct rdns_async_context *async; /** async callbacks */ + void *periodic; /** periodic event for resolver */ + struct rdns_upstream_context *ups; + struct rdns_plugin *curve_plugin; + struct rdns_fake_reply *fake_elts; + +#ifdef __GNUC__ + __attribute__((format(printf, 4, 0))) +#endif + rdns_log_function logger; + void *log_data; + enum rdns_log_level log_level; + + uint64_t max_ioc_uses; + void *refresh_ioc_periodic; + + bool async_binded; + bool initialized; + bool enable_dnssec; + int flags; + ref_entry_t ref; +}; + +struct dns_query; + +/* Internal DNS structs */ + +enum dns_section { + DNS_S_QD = 0x01, +#define DNS_S_QUESTION DNS_S_QD + + DNS_S_AN = 0x02, +#define DNS_S_ANSWER DNS_S_AN + + DNS_S_NS = 0x04, +#define DNS_S_AUTHORITY DNS_S_NS + + DNS_S_AR = 0x08, +#define DNS_S_ADDITIONAL DNS_S_AR + + DNS_S_ALL = 0x0f +}; +/* enum dns_section */ + +enum dns_opcode { + DNS_OP_QUERY = 0, + DNS_OP_IQUERY = 1, + DNS_OP_STATUS = 2, + DNS_OP_NOTIFY = 4, + DNS_OP_UPDATE = 5, +}; +/* dns_opcode */ + +enum dns_class { + DNS_C_IN = 1, + + DNS_C_ANY = 255 +}; +/* enum dns_class */ + +struct dns_query { + char *qname; + unsigned int qtype :16; + unsigned int qclass :16; +}; + +enum dns_type { + DNS_T_A = RDNS_REQUEST_A, + DNS_T_NS = RDNS_REQUEST_NS, + DNS_T_CNAME = 5, + DNS_T_SOA = RDNS_REQUEST_SOA, + DNS_T_PTR = RDNS_REQUEST_PTR, + DNS_T_MX = RDNS_REQUEST_MX, + DNS_T_TXT = RDNS_REQUEST_TXT, + DNS_T_AAAA = RDNS_REQUEST_AAAA, + DNS_T_SRV = RDNS_REQUEST_SRV, + DNS_T_OPT = 41, + DNS_T_SSHFP = 44, + DNS_T_TLSA = RDNS_REQUEST_TLSA, + DNS_T_SPF = RDNS_REQUEST_SPF, + DNS_T_ALL = RDNS_REQUEST_ANY +}; +/* enum dns_type */ + +static const char dns_rcodes[][32] = { + [RDNS_RC_NOERROR] = "no error", + [RDNS_RC_FORMERR] = "query format error", + [RDNS_RC_SERVFAIL] = "server fail", + [RDNS_RC_NXDOMAIN] = "no records with this name", + [RDNS_RC_NOTIMP] = "not implemented", + [RDNS_RC_REFUSED] = "query refused", + [RDNS_RC_YXDOMAIN] = "YXDOMAIN", + [RDNS_RC_YXRRSET] = "YXRRSET", + [RDNS_RC_NXRRSET] = "NXRRSET", + [RDNS_RC_NOTAUTH] = "not authorized", + [RDNS_RC_NOTZONE] = "no such zone", + [RDNS_RC_TIMEOUT] = "query timed out", + [RDNS_RC_NETERR] = "network error", + [RDNS_RC_NOREC] = "requested record is not found" +}; + +static const char dns_types[][16] = { + [RDNS_REQUEST_A] = "A request", + [RDNS_REQUEST_NS] = "NS request", + [RDNS_REQUEST_PTR] = "PTR request", + [RDNS_REQUEST_MX] = "MX request", + [RDNS_REQUEST_TXT] = "TXT request", + [RDNS_REQUEST_SRV] = "SRV request", + [RDNS_REQUEST_SPF] = "SPF request", + [RDNS_REQUEST_AAAA] = "AAAA request", + [RDNS_REQUEST_TLSA] = "TLSA request", + [RDNS_REQUEST_ANY] = "ANY request" +}; + + +#endif /* DNS_PRIVATE_H_ */ diff --git a/contrib/librdns/logger.c b/contrib/librdns/logger.c new file mode 100644 index 0000000..c9ed2d9 --- /dev/null +++ b/contrib/librdns/logger.c @@ -0,0 +1,53 @@ +/* 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 <stdio.h> +#include "dns_private.h" +#include "logger.h" + +void +rdns_logger_internal (void *log_data, enum rdns_log_level level, + const char *function, const char *format, + va_list args) +{ + struct rdns_resolver *resolver = log_data; + + if (level <= resolver->log_level) { + fprintf (stderr, "rdns: %s: ", function); + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + } +} + +void rdns_logger_helper (struct rdns_resolver *resolver, + enum rdns_log_level level, + const char *function, const char *format, ...) +{ + va_list va; + + if (resolver->logger != NULL) { + va_start (va, format); + resolver->logger (resolver->log_data, level, function, format, va); + va_end (va); + } +} diff --git a/contrib/librdns/logger.h b/contrib/librdns/logger.h new file mode 100644 index 0000000..8072876 --- /dev/null +++ b/contrib/librdns/logger.h @@ -0,0 +1,48 @@ +/* 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. + */ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +#include <stdarg.h> +#include "dns_private.h" + +#ifdef __GNUC__ +__attribute__((format(printf, 4, 0))) +#endif +void rdns_logger_internal (void *log_data, enum rdns_log_level level, + const char *function, const char *format, + va_list args); + +#ifdef __GNUC__ +__attribute__((format(printf, 4, 5))) +#endif +void rdns_logger_helper (struct rdns_resolver *resolver, + enum rdns_log_level level, + const char *function, const char *format, ...); + +#define rdns_err(...) do { rdns_logger_helper (resolver, RDNS_LOG_ERROR, __func__, __VA_ARGS__); } while (0) +#define rdns_warn(...) do { rdns_logger_helper (resolver, RDNS_LOG_WARNING, __func__, __VA_ARGS__); } while (0) +#define rdns_info(...) do { rdns_logger_helper (resolver, RDNS_LOG_INFO, __func__, __VA_ARGS__); } while (0) +#define rdns_debug(...) do { rdns_logger_helper (resolver, RDNS_LOG_DEBUG, __func__, __VA_ARGS__); } while (0) + +#endif /* LOGGER_H_ */ diff --git a/contrib/librdns/packet.c b/contrib/librdns/packet.c new file mode 100644 index 0000000..59a919e --- /dev/null +++ b/contrib/librdns/packet.c @@ -0,0 +1,288 @@ +/* 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 (label_len) { + *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 = NULL; + 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); + uclabel = NULL; + + 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; + free (uclabel); + + 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 kh_rdns_compression_hash_s **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; +} diff --git a/contrib/librdns/packet.h b/contrib/librdns/packet.h new file mode 100644 index 0000000..44e16a5 --- /dev/null +++ b/contrib/librdns/packet.h @@ -0,0 +1,61 @@ +/* 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. + */ +#ifndef PACKET_H_ +#define PACKET_H_ + +#include <stdbool.h> +#include <stdint.h> +#include "dns_private.h" + +struct kh_rdns_compression_hash_s; + +/** + * Allocate dns packet suitable to handle up to `namelen` name + * @param req request + * @param namelen requested name + */ +void rdns_allocate_packet (struct rdns_request* req, unsigned int namelen); + +/** + * Add basic header to the dns packet + * @param req + */ +void rdns_make_dns_header (struct rdns_request *req, unsigned int qcount); + + +/** + * Add a resource record to the DNS packet + * @param req request + * @param name requested name + * @param type type of resource record + */ +bool rdns_add_rr (struct rdns_request *req, const char *name, unsigned int len, + enum dns_type type, struct kh_rdns_compression_hash_s **comp); + +/** + * Add EDNS0 section + * @param req + */ +bool rdns_add_edns0 (struct rdns_request *req); + +#endif /* PACKET_H_ */ diff --git a/contrib/librdns/parse.c b/contrib/librdns/parse.c new file mode 100644 index 0000000..f9025e1 --- /dev/null +++ b/contrib/librdns/parse.c @@ -0,0 +1,461 @@ +/* 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 "parse.h" +#include "logger.h" + +static uint8_t * +rdns_decompress_label (uint8_t *begin, uint16_t *len, uint16_t max) +{ + uint16_t offset = (*len); + + if (offset > max) { + return NULL; + } + *len = *(begin + offset); + return begin + offset; +} + +#define UNCOMPRESS_DNS_OFFSET(p) (((*(p)) ^ DNS_COMPRESSION_BITS) << 8) + *((p) + 1) + +uint8_t * +rdns_request_reply_cmp (struct rdns_request *req, uint8_t *in, int len) +{ + uint8_t *p, *c, *l1, *l2; + uint16_t len1, len2; + int decompressed = 0; + struct rdns_resolver *resolver = req->resolver; + + /* QR format: + * labels - len:octets + * null label - 0 + * class - 2 octets + * type - 2 octets + */ + + /* In p we would store current position in reply and in c - position in request */ + p = in; + c = req->packet + req->pos; + + for (;;) { + /* Get current label */ + len1 = *p; + len2 = *c; + if (p - in > len) { + rdns_info ("invalid dns reply"); + return NULL; + } + /* This may be compressed, so we need to decompress it */ + if (len1 & DNS_COMPRESSION_BITS) { + len1 = UNCOMPRESS_DNS_OFFSET(p); + l1 = rdns_decompress_label (in, &len1, len); + if (l1 == NULL) { + return NULL; + } + decompressed ++; + l1 ++; + p += 2; + } + else { + l1 = ++p; + p += len1; + } + if (len2 & DNS_COMPRESSION_BITS) { + len2 = UNCOMPRESS_DNS_OFFSET(c); + l2 = rdns_decompress_label (c, &len2, len); + if (l2 == NULL) { + rdns_info ("invalid DNS pointer, cannot decompress"); + return NULL; + } + decompressed ++; + l2 ++; + c += 2; + } + else { + l2 = ++c; + c += len2; + } + if (len1 != len2) { + return NULL; + } + if (len1 == 0) { + break; + } + + if (memcmp (l1, l2, len1) != 0) { + return NULL; + } + if (decompressed == 2) { + break; + } + } + + /* p now points to the end of QR section */ + /* Compare class and type */ + if (memcmp (p, c, sizeof (uint16_t) * 2) == 0) { + req->pos = c - req->packet + sizeof (uint16_t) * 2; + return p + sizeof (uint16_t) * 2; + } + return NULL; +} + +#define MAX_RECURSION_LEVEL 10 + +bool +rdns_parse_labels (struct rdns_resolver *resolver, + uint8_t *in, char **target, uint8_t **pos, struct rdns_reply *rep, + int *remain, bool make_name) +{ + uint16_t namelen = 0; + uint8_t *p = *pos, *begin = *pos, *l, *t, *end = *pos + *remain, *new_pos = *pos; + uint16_t llen; + int length = *remain, new_remain = *remain; + int ptrs = 0, labels = 0; + bool got_compression = false; + + /* First go through labels and calculate name length */ + while (p - begin < length) { + if (ptrs > MAX_RECURSION_LEVEL) { + rdns_info ("dns pointers are nested too much"); + return false; + } + llen = *p; + if (llen == 0) { + if (!got_compression) { + /* In case of compression we have already decremented the processing position */ + new_remain -= sizeof (uint8_t); + new_pos += sizeof (uint8_t); + } + break; + } + else if ((llen & DNS_COMPRESSION_BITS)) { + if (end - p > 1) { + ptrs ++; + llen = UNCOMPRESS_DNS_OFFSET(p); + l = rdns_decompress_label (in, &llen, end - in); + if (l == NULL) { + rdns_info ("invalid DNS pointer"); + return false; + } + if (!got_compression) { + /* Our label processing is finished actually */ + new_remain -= sizeof (uint16_t); + new_pos += sizeof (uint16_t); + got_compression = true; + } + if (l < in || l > begin + length) { + rdns_info ("invalid pointer in DNS packet"); + return false; + } + begin = l; + length = end - begin; + p = l + *l + 1; + namelen += *l; + labels ++; + } + else { + rdns_info ("DNS packet has incomplete compressed label, input length: %d bytes, remain: %d", + *remain, new_remain); + return false; + } + } + else { + namelen += llen; + p += llen + 1; + labels ++; + if (!got_compression) { + new_remain -= llen + 1; + new_pos += llen + 1; + } + } + } + + if (!make_name) { + goto end; + } + *target = malloc (namelen + labels + 3); + t = (uint8_t *)*target; + p = *pos; + begin = *pos; + length = *remain; + /* Now copy labels to name */ + while (p - begin < length) { + llen = *p; + if (llen == 0) { + break; + } + else if (llen & DNS_COMPRESSION_BITS) { + llen = UNCOMPRESS_DNS_OFFSET(p); + l = rdns_decompress_label (in, &llen, end - in); + + if (l == NULL) { + goto end; + } + + begin = l; + length = end - begin; + p = l + *l + 1; + memcpy (t, l + 1, *l); + t += *l; + *t ++ = '.'; + } + else { + memcpy (t, p + 1, *p); + t += *p; + *t ++ = '.'; + p += *p + 1; + } + } + if (t > (uint8_t *)*target) { + *(t - 1) = '\0'; + } + else { + /* Handle empty labels */ + **target = '\0'; + } +end: + *remain = new_remain; + *pos = new_pos; + + return true; +} + +#define GET8(x) do {(x) = ((*p)); p += sizeof (uint8_t); *remain -= sizeof (uint8_t); } while(0) +#define GET16(x) do {(x) = ((*p) << 8) + *(p + 1); p += sizeof (uint16_t); *remain -= sizeof (uint16_t); } while(0) +#define GET32(x) do {(x) = ((*p) << 24) + ((*(p + 1)) << 16) + ((*(p + 2)) << 8) + *(p + 3); p += sizeof (uint32_t); *remain -= sizeof (uint32_t); } while(0) +#define SKIP(type) do { p += sizeof(type); *remain -= sizeof(type); } while (0) + +int +rdns_parse_rr (struct rdns_resolver *resolver, + uint8_t *in, struct rdns_reply_entry *elt, uint8_t **pos, + struct rdns_reply *rep, int *remain) +{ + uint8_t *p = *pos, parts; + uint16_t type, datalen, txtlen, copied; + int32_t ttl; + bool parsed = false; + + /* Skip the whole name */ + if (!rdns_parse_labels (resolver, in, NULL, &p, rep, remain, false)) { + rdns_info ("bad RR name"); + return -1; + } + if (*remain < (int)sizeof (uint16_t) * 6) { + rdns_info ("stripped dns reply: %d bytes remain; domain %s", *remain, + rep->requested_name); + return -1; + } + GET16 (type); + /* Skip class */ + SKIP (uint16_t); + GET32 (ttl); + GET16 (datalen); + elt->type = type; + /* Now p points to RR data */ + switch (type) { + case DNS_T_A: + if (!(datalen & 0x3) && datalen <= *remain) { + memcpy (&elt->content.a.addr, p, sizeof (struct in_addr)); + p += datalen; + *remain -= datalen; + parsed = true; + } + else { + rdns_info ("corrupted A record; domain: %s", rep->requested_name); + return -1; + } + break; + case DNS_T_AAAA: + if (datalen == sizeof (struct in6_addr) && datalen <= *remain) { + memcpy (&elt->content.aaa.addr, p, sizeof (struct in6_addr)); + p += datalen; + *remain -= datalen; + parsed = true; + } + else { + rdns_info ("corrupted AAAA record; domain %s", rep->requested_name); + return -1; + } + break; + case DNS_T_PTR: + if (! rdns_parse_labels (resolver, in, &elt->content.ptr.name, &p, + rep, remain, true)) { + rdns_info ("invalid labels in PTR record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_NS: + if (! rdns_parse_labels (resolver, in, &elt->content.ns.name, &p, + rep, remain, true)) { + rdns_info ("invalid labels in NS record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_SOA: + if (! rdns_parse_labels (resolver, in, &elt->content.soa.mname, &p, + rep, remain, true)) { + rdns_info ("invalid labels in SOA record; domain %s", rep->requested_name); + return -1; + } + if (! rdns_parse_labels (resolver, in, &elt->content.soa.admin, &p, + rep, remain, true)) { + rdns_info ("invalid labels in SOA record; domain %s", rep->requested_name); + return -1; + } + if (*remain >= sizeof(int32_t) * 5) { + GET32 (elt->content.soa.serial); + GET32 (elt->content.soa.refresh); + GET32 (elt->content.soa.retry); + GET32 (elt->content.soa.expire); + GET32 (elt->content.soa.minimum); + } + else { + rdns_info ("invalid data in SOA record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_MX: + GET16 (elt->content.mx.priority); + if (! rdns_parse_labels (resolver, in, &elt->content.mx.name, &p, + rep, remain, true)) { + rdns_info ("invalid labels in MX record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_TXT: + case DNS_T_SPF: + if (datalen <= *remain) { + elt->content.txt.data = malloc(datalen + 1); + if (elt->content.txt.data == NULL) { + rdns_err ("failed to allocate %d bytes for TXT record; domain %s", + (int) datalen + 1, rep->requested_name); + return -1; + } + /* Now we should compose data from parts */ + copied = 0; + parts = 0; + while (copied + parts < datalen && *remain > 0) { + txtlen = *p; + if (txtlen + copied + parts <= datalen && *remain >= txtlen + 1) { + parts++; + memcpy (elt->content.txt.data + copied, p + 1, txtlen); + copied += txtlen; + p += txtlen + 1; + *remain -= txtlen + 1; + } + else { + + if (txtlen + copied + parts > datalen) { + /* Incorrect datalen reported ! */ + rdns_err ("incorrect txtlen (%d) > datalen (%d) reported; domain %s", + (txtlen + copied + parts), datalen, + rep->requested_name); + return -1; + } + + /* Reported equal to the actual data copied */ + break; + } + } + *(elt->content.txt.data + copied) = '\0'; + parsed = true; + elt->type = RDNS_REQUEST_TXT; + } + else { + rdns_info ("stripped data in TXT record (%d bytes available, %d requested); " + "domain %s", (int)*remain, (int)datalen, rep->requested_name); + return -1; + } + break; + case DNS_T_SRV: + if (p - *pos > (int)(*remain - sizeof (uint16_t) * 3)) { + rdns_info ("stripped dns reply while reading SRV record; domain %s", rep->requested_name); + return -1; + } + GET16 (elt->content.srv.priority); + GET16 (elt->content.srv.weight); + GET16 (elt->content.srv.port); + if (! rdns_parse_labels (resolver, in, &elt->content.srv.target, + &p, rep, remain, true)) { + rdns_info ("invalid labels in SRV record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + case DNS_T_TLSA: + if (p - *pos > (int)(*remain - sizeof (uint8_t) * 3) || datalen <= 3) { + rdns_info ("stripped dns reply while reading TLSA record; domain %s", rep->requested_name); + return -1; + } + + if (datalen > *remain) { + rdns_info ("too large datalen; domain %s", rep->requested_name); + return -1; + } + + GET8 (elt->content.tlsa.usage); + GET8 (elt->content.tlsa.selector); + GET8 (elt->content.tlsa.match_type); + datalen -= 3; + + elt->content.tlsa.data = malloc (datalen); + if (elt->content.tlsa.data == NULL) { + rdns_err ("failed to allocate %d bytes for TLSA record; domain %s", + (int)datalen + 1, rep->requested_name); + return -1; + } + + elt->content.tlsa.datalen = datalen; + memcpy (elt->content.tlsa.data, p, datalen); + p += datalen; + *remain -= datalen; + parsed = true; + break; + case DNS_T_CNAME: + if (! rdns_parse_labels (resolver, in, &elt->content.cname.name, &p, + rep, remain, true)) { + rdns_info ("invalid labels in CNAME record; domain %s", rep->requested_name); + return -1; + } + parsed = true; + break; + default: + rdns_info ("unexpected RR type: %d; domain %s", type, rep->requested_name); + p += datalen; + *remain -= datalen; + break; + } + *pos = p; + + if (parsed) { + elt->ttl = ttl; + return 1; + } + return 0; +} diff --git a/contrib/librdns/parse.h b/contrib/librdns/parse.h new file mode 100644 index 0000000..ed8cd70 --- /dev/null +++ b/contrib/librdns/parse.h @@ -0,0 +1,65 @@ +/* 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. + */ +#ifndef PARSE_H_ +#define PARSE_H_ + +#include "dns_private.h" + +/** + * Compare request and reply checking names + * @param req request object + * @param in incoming packet + * @param len length of the incoming packet + * @return new position in the incoming packet or NULL if request is not equal to reply + */ +uint8_t * rdns_request_reply_cmp (struct rdns_request *req, uint8_t *in, int len); + +/** + * Parse labels in the packet + * @param in incoming packet + * @param target target to write the parsed label (out) + * @param pos output position in the packet (it/out) + * @param rep dns reply + * @param remain remaining bytes (in/out) + * @param make_name create name or just skip to the next label + * @return true if a label has been successfully parsed + */ +bool rdns_parse_labels (struct rdns_resolver *resolver, + uint8_t *in, char **target, + uint8_t **pos, struct rdns_reply *rep, + int *remain, bool make_name); + +/** + * Parse resource record + * @param in incoming packet + * @param elt new reply entry + * @param pos output position in the packet (it/out) + * @param rep dns reply + * @param remain remaining bytes (in/out) + * @return 1 if rr has been parsed, 0 if rr has been skipped and -1 if there was a parsing error + */ +int rdns_parse_rr (struct rdns_resolver *resolver, + uint8_t *in, struct rdns_reply_entry *elt, uint8_t **pos, + struct rdns_reply *rep, int *remain); + +#endif /* PARSE_H_ */ diff --git a/contrib/librdns/punycode.c b/contrib/librdns/punycode.c new file mode 100644 index 0000000..61091b2 --- /dev/null +++ b/contrib/librdns/punycode.c @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2014, Vsevolod Stakhov + * Copyright (c) 2004, 2006, 2007, 2008 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 THE INSTITUTE OR CONTRIBUTORS 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 "dns_private.h" +static const unsigned event_loop = 36; +static const unsigned t_min = 1; +static const unsigned t_max = 26; +static const unsigned skew = 38; +static const unsigned damp = 700; +static const unsigned initial_n = 128; +static const unsigned initial_bias = 72; +/* Punycode utility */ +static unsigned int +digit (unsigned n) +{ + static const char ascii[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + return ascii[n]; +} + +static unsigned int +adapt (unsigned int delta, unsigned int numpoints, int first) +{ + unsigned int k; + + if (first) { + delta = delta / damp; + } + else { + delta /= 2; + } + delta += delta / numpoints; + k = 0; + while (delta > ((event_loop - t_min) * t_max) / 2) { + delta /= event_loop - t_min; + k += event_loop; + } + return k + (((event_loop - t_min + 1) * delta) / (delta + skew)); +} + +/** + * Convert an UCS4 string to a puny-coded DNS label string suitable + * when combined with delimiters and other labels for DNS lookup. + * + * @param in an UCS4 string to convert + * @param in_len the length of in. + * @param out the resulting puny-coded string. The string is not NULL + * terminated. + * @param out_len before processing out_len should be the length of + * the out variable, after processing it will be the length of the out + * string. + * + * @return returns 0 on success, an wind error code otherwise + */ + +bool +rdns_punycode_label_toascii (const uint32_t *in, size_t in_len, char *out, + size_t *out_len) +{ + unsigned int n = initial_n; + unsigned int delta = 0; + unsigned int bias = initial_bias; + unsigned int h = 0; + unsigned int b; + unsigned int i; + unsigned int o = 0; + unsigned int m; + + for (i = 0; i < in_len; ++i) { + if (in[i] < 0x80) { + ++h; + if (o >= *out_len) { + return false; + } + out[o++] = in[i]; + } + } + b = h; + if (b > 0) { + if (o >= *out_len) { + return false; + } + out[o++] = 0x2D; + } + /* is this string punycoded */ + if (h < in_len) { + if (o + 4 >= *out_len) { + return false; + } + memmove (out + 4, out, o); + memcpy (out, "xn--", 4); + o += 4; + } + + while (h < in_len) { + m = (unsigned int) -1; + for (i = 0; i < in_len; ++i) { + + if (in[i] < m && in[i] >= n) { + m = in[i]; + } + } + delta += (m - n) * (h + 1); + n = m; + for (i = 0; i < in_len; ++i) { + if (in[i] < n) { + ++delta; + } + else if (in[i] == n) { + unsigned int q = delta; + unsigned int k; + for (k = event_loop;; k += event_loop) { + unsigned int t; + if (k <= bias) { + t = t_min; + } + else if (k >= bias + t_max) { + t = t_max; + } + else { + t = k - bias; + } + if (q < t) { + break; + } + if (o >= *out_len) { + return -1; + } + out[o++] = digit (t + ((q - t) % (event_loop - t))); + q = (q - t) / (event_loop - t); + } + if (o >= *out_len) { + return -1; + } + out[o++] = digit (q); + /* output */ + bias = adapt (delta, h + 1, h == b); + delta = 0; + ++h; + } + } + ++delta; + ++n; + } + + *out_len = o; + return true; +} + +static int +utf8toutf32 (const unsigned char **pp, uint32_t *out, size_t *remain) +{ + const unsigned char *p = *pp; + unsigned c = *p; + size_t reduce; + + if (c & 0x80) { + if ((c & 0xE0) == 0xC0 && *remain >= 2) { + const unsigned c2 = *++p; + reduce = 2; + if ((c2 & 0xC0) == 0x80) { + *out = ((c & 0x1F) << 6) | (c2 & 0x3F); + } + else { + return -1; + } + } + else if ((c & 0xF0) == 0xE0 && *remain >= 3) { + const unsigned c2 = *++p; + if ((c2 & 0xC0) == 0x80) { + const unsigned c3 = *++p; + reduce = 3; + if ((c3 & 0xC0) == 0x80) { + *out = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) + | (c3 & 0x3F); + } + else { + return -1; + } + } + else { + return -1; + } + } + else if ((c & 0xF8) == 0xF0 && *remain >= 4) { + const unsigned c2 = *++p; + if ((c2 & 0xC0) == 0x80) { + const unsigned c3 = *++p; + if ((c3 & 0xC0) == 0x80) { + const unsigned c4 = *++p; + reduce = 4; + if ((c4 & 0xC0) == 0x80) { + *out = ((c & 0x07) << 18) | ((c2 & 0x3F) << 12) + | ((c3 & 0x3F) << 6) | (c4 & 0x3F); + } + else { + return -1; + } + } + else { + return -1; + } + } + else { + return -1; + } + } + else { + return -1; + } + } + else { + *out = c; + reduce = 1; + } + + *pp = ++p; + *remain -= reduce; + + return 0; +} + +/** + * Convert an UTF-8 string to an UCS4 string. + * + * @param in an UTF-8 string to convert. + * @param out the resulting UCS4 string + * @param out_len before processing out_len should be the length of + * the out variable, after processing it will be the length of the out + * string. + * + * @return returns 0 on success, an -1 otherwise + * @ingroup wind + */ + +int +rdns_utf8_to_ucs4 (const char *in, size_t in_len, uint32_t **out, size_t *out_len) +{ + const unsigned char *p; + size_t remain = in_len, olen = 0; + int ret; + uint32_t *res; + + p = (const unsigned char *)in; + while (remain > 0) { + uint32_t u; + + ret = utf8toutf32 (&p, &u, &remain); + if (ret != 0) { + return ret; + } + + olen ++; + } + res = malloc (olen * sizeof (uint32_t)); + if (res == NULL) { + return -1; + } + + p = (const unsigned char *)in; + remain = in_len; + olen = 0; + while (remain > 0) { + uint32_t u; + + (void)utf8toutf32 (&p, &u, &remain); + res[olen++] = u; + } + + *out_len = olen; + *out = res; + return 0; +} diff --git a/contrib/librdns/punycode.h b/contrib/librdns/punycode.h new file mode 100644 index 0000000..300fb92 --- /dev/null +++ b/contrib/librdns/punycode.h @@ -0,0 +1,59 @@ +/* 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. + */ +#ifndef PUNYCODE_H_ +#define PUNYCODE_H_ + +#include <stdbool.h> +#include <stdint.h> + +/** + * Convert an UCS4 string to a puny-coded DNS label string suitable + * when combined with delimiters and other labels for DNS lookup. + * + * @param in an UCS4 string to convert + * @param in_len the length of in. + * @param out the resulting puny-coded string. The string is not NULL + * terminated. + * @param out_len before processing out_len should be the length of + * the out variable, after processing it will be the length of the out + * string. + * + * @return returns 0 on success, an wind error code otherwise + */ +bool rdns_punycode_label_toascii (const uint32_t *in, size_t in_len, char *out, size_t *out_len); +/** + * Convert an UTF-8 string to an UCS4 string. + * + * @param in an UTF-8 string to convert. + * @param out the resulting UCS4 string + * @param out_len before processing out_len should be the length of + * the out variable, after processing it will be the length of the out + * string. + * + * @return returns 0 on success, an -1 otherwise + * @ingroup wind + */ + +int rdns_utf8_to_ucs4 (const char *in, size_t in_len, uint32_t **out, size_t *out_len); + +#endif /* PUNYCODE_H_ */ diff --git a/contrib/librdns/rdns.h b/contrib/librdns/rdns.h new file mode 100644 index 0000000..c0da5ed --- /dev/null +++ b/contrib/librdns/rdns.h @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2013-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 BY AUTHOR ''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. + */ + +#ifndef RDNS_H +#define RDNS_H + +#include <sys/types.h> +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdarg.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct rdns_reply; +struct rdns_request; +struct rdns_io_channel; + +typedef void (*dns_callback_type) (struct rdns_reply *reply, void *arg); + +enum rdns_request_type { + RDNS_REQUEST_INVALID = -1, + RDNS_REQUEST_A = 1, + RDNS_REQUEST_NS = 2, + RDNS_REQUEST_CNAME = 5, + RDNS_REQUEST_SOA = 6, + RDNS_REQUEST_PTR = 12, + RDNS_REQUEST_MX = 15, + RDNS_REQUEST_TXT = 16, + RDNS_REQUEST_SRV = 33, + RDNS_REQUEST_SPF = 99, + RDNS_REQUEST_AAAA = 28, + RDNS_REQUEST_TLSA = 52, + RDNS_REQUEST_ANY = 255 +}; + +union rdns_reply_element_un { + struct { + struct in_addr addr; + } a; + struct { + struct in6_addr addr; + } aaa; + struct { + char *name; + } ptr; + struct { + char *name; + } ns; + struct { + char *name; + uint16_t priority; + } mx; + struct { + char *data; + } txt; + struct { + uint16_t priority; + uint16_t weight; + uint16_t port; + char *target; + } srv; + struct { + char *mname; + char *admin; + uint32_t serial; + int32_t refresh; + int32_t retry; + int32_t expire; + uint32_t minimum; + } soa; + struct { + uint8_t usage; + uint8_t selector; + uint8_t match_type; + uint16_t datalen; + uint8_t *data; + } tlsa; + struct { + char *name; + } cname; +}; + +struct rdns_reply_entry { + union rdns_reply_element_un content; + enum rdns_request_type type; + int32_t ttl; + struct rdns_reply_entry *prev, *next; +}; + + +enum dns_rcode { + RDNS_RC_INVALID = -1, + RDNS_RC_NOERROR = 0, + RDNS_RC_FORMERR = 1, + RDNS_RC_SERVFAIL = 2, + RDNS_RC_NXDOMAIN = 3, + RDNS_RC_NOTIMP = 4, + RDNS_RC_REFUSED = 5, + RDNS_RC_YXDOMAIN = 6, + RDNS_RC_YXRRSET = 7, + RDNS_RC_NXRRSET = 8, + RDNS_RC_NOTAUTH = 9, + RDNS_RC_NOTZONE = 10, + RDNS_RC_TIMEOUT = 11, + RDNS_RC_NETERR = 12, + RDNS_RC_NOREC = 13 +}; + +enum dns_reply_flags { + RDNS_AUTH = (1u << 0u), + RDNS_TRUNCATED = (1u << 1u) +}; + +struct rdns_reply { + struct rdns_request *request; + struct rdns_resolver *resolver; + struct rdns_reply_entry *entries; + const char *requested_name; + enum dns_rcode code; + uint8_t flags; /* see enum dns_reply_flags */ +}; + +typedef void (*rdns_periodic_callback)(void *user_data); + +struct rdns_async_context { + void *data; + void* (*add_read)(void *priv_data, int fd, void *user_data); + void (*del_read)(void *priv_data, void *ev_data); + void* (*add_write)(void *priv_data, int fd, void *user_data); + void (*del_write)(void *priv_data, void *ev_data); + void* (*add_timer)(void *priv_data, double after, void *user_data); + void (*repeat_timer)(void *priv_data, void *ev_data); + void (*del_timer)(void *priv_data, void *ev_data); + void* (*add_periodic)(void *priv_data, double after, + rdns_periodic_callback cb, void *user_data); + void (*del_periodic)(void *priv_data, void *ev_data); + void (*cleanup)(void *priv_data); +}; + +struct rdns_upstream_elt { + void *server; + void *lib_data; +}; + +struct rdns_upstream_context { + void *data; + struct rdns_upstream_elt* (*select)(const char *name, + size_t len, void *ups_data); + struct rdns_upstream_elt* (*select_retransmit)(const char *name, size_t len, + struct rdns_upstream_elt* prev_elt, + void *ups_data); + unsigned int (*count)(void *ups_data); + void (*ok)(struct rdns_upstream_elt *elt, void *ups_data); + void (*fail)(struct rdns_upstream_elt *elt, void *ups_data, const char *reason); +}; + +/** + * Type of rdns plugin + */ +enum rdns_plugin_type { + RDNS_PLUGIN_CURVE = 0 +}; + +typedef ssize_t (*rdns_network_send_callback) (struct rdns_request *req, void *plugin_data, + struct sockaddr *saddr, socklen_t slen); +typedef ssize_t (*rdns_network_recv_callback) (struct rdns_io_channel *ioc, void *buf, + size_t len, void *plugin_data, + struct rdns_request **req_out, + struct sockaddr *saddr, socklen_t slen); +typedef void (*rdns_network_finish_callback) (struct rdns_request *req, void *plugin_data); +typedef void (*rdns_plugin_dtor_callback) (struct rdns_resolver *resolver, void *plugin_data); + +struct rdns_plugin { + enum rdns_plugin_type type; + union { + struct { + rdns_network_send_callback send_cb; + rdns_network_recv_callback recv_cb; + rdns_network_finish_callback finish_cb; + } curve_plugin; + } cb; + rdns_plugin_dtor_callback dtor; + void *data; +}; + +/* + * RDNS logger types + */ +/* + * These types are somehow compatible with glib + */ +enum rdns_log_level { + RDNS_LOG_ERROR = 1 << 3, + RDNS_LOG_WARNING = 1 << 4, + RDNS_LOG_INFO = 1 << 6, + RDNS_LOG_DEBUG = 1 << 7 +}; +typedef void (*rdns_log_function) ( + void *log_data, //!< opaque data pointer + enum rdns_log_level level, //!< level of message + const char *function, //!< calling function + const char *format, //!< format + va_list args //!< set of arguments + ); + +struct rdns_request_name { + char *name; + enum rdns_request_type type; + unsigned int len; +}; + +#define MAX_FAKE_NAME 1000 + +/* + * RDNS API + */ + +enum rdns_resolver_flags { + RDNS_RESOLVER_DEFAULT, + RDNS_RESOLVER_NOIDN = (1u << 0u), +}; + +/** + * Create DNS resolver structure + */ +struct rdns_resolver *rdns_resolver_new (int flags); + +/** + * Bind resolver to specified async context + * @param ctx + */ +void rdns_resolver_async_bind (struct rdns_resolver *resolver, + struct rdns_async_context *ctx); + +/** + * Enable stub dnssec resolver + * @param resolver + */ +void rdns_resolver_set_dnssec (struct rdns_resolver *resolver, bool enabled); + +/** + * Add new DNS server definition to the resolver + * @param resolver resolver object + * @param name name of DNS server (should be ipv4 or ipv6 address) + * @param priority priority (can be 0 for fair round-robin) + * @param io_cnt a number of sockets that are simultaneously opened to this server + * @return opaque pointer that could be used to select upstream + */ +void* rdns_resolver_add_server (struct rdns_resolver *resolver, + const char *name, unsigned int port, + int priority, unsigned int io_cnt); + + +/** + * Load nameservers definition from resolv.conf file + * @param resolver resolver object + * @param path path to resolv.conf file (/etc/resolv.conf typically) + * @return true if resolv.conf has been parsed + */ +bool rdns_resolver_parse_resolv_conf (struct rdns_resolver *resolver, + const char *path); + +typedef bool (*rdns_resolv_conf_cb) (struct rdns_resolver *resolver, + const char *name, unsigned int port, + int priority, unsigned int io_cnt, void *ud); +/** + * Parse nameservers calling the specified callback for each nameserver + * @param resolve resolver object + * @param path path to resolv.conf file (/etc/resolv.conf typically) + * @param cb callback to call + * @param ud userdata for callback + * @return true if resolv.conf has been parsed + */ +bool rdns_resolver_parse_resolv_conf_cb (struct rdns_resolver *resolver, + const char *path, rdns_resolv_conf_cb cb, void *ud); + +/** + * Set an external logger function to log messages from the resolver + * @param resolver resolver object + * @param logger logger callback + * @param log_data opaque data + */ +void rdns_resolver_set_logger (struct rdns_resolver *resolver, + rdns_log_function logger, void *log_data); + +/** + * Set log level for an internal logger (stderr one) + * @param resolver resolver object + * @param level desired log level + */ +void rdns_resolver_set_log_level (struct rdns_resolver *resolver, + enum rdns_log_level level); + +/** + * Set upstream library for selecting DNS upstreams + * @param resolver resolver object + * @param ups_ctx upstream functions + * @param ups_data opaque data + */ +void rdns_resolver_set_upstream_lib (struct rdns_resolver *resolver, + struct rdns_upstream_context *ups_ctx, + void *ups_data); + +/** + * Set maximum number of dns requests to be sent to a socket to be refreshed + * @param resolver resolver object + * @param max_ioc_uses unsigned count of socket usage limit + * @param check_time specifies how often to check for sockets and refresh them + */ +void rdns_resolver_set_max_io_uses (struct rdns_resolver *resolver, + uint64_t max_ioc_uses, double check_time); + +/** + * Register new plugin for rdns resolver + * @param resolver + * @param plugin + */ +void rdns_resolver_register_plugin (struct rdns_resolver *resolver, + struct rdns_plugin *plugin); + +/** + * Add a fake reply for a specified name + * @param resolver + * @param type + * @param name (must not be larger than MAX_FAKE_NAME) + * @param reply + */ +void rdns_resolver_set_fake_reply (struct rdns_resolver *resolver, + const char *name, + enum rdns_request_type type, + enum dns_rcode rcode, + struct rdns_reply_entry *reply); + +/** + * Init DNS resolver + * @param resolver + * @return + */ +bool rdns_resolver_init (struct rdns_resolver *resolver); + +/** + * Decrease refcount for a resolver and free it if refcount is 0 + * @param resolver + */ +void rdns_resolver_release (struct rdns_resolver *resolver); + +/** + * Make a DNS request + * @param resolver resolver object + * @param cb callback to call on resolve completing + * @param ud user data for callback + * @param timeout timeout in seconds + * @param repeats how much time to retransmit query + * @param queries how much RR queries to send + * @param ... -> queries in format: <query_type>[,type_argument[,type_argument...]] + * @return opaque request object or NULL + */ +struct rdns_request* rdns_make_request_full ( + struct rdns_resolver *resolver, + dns_callback_type cb, + void *cbdata, + double timeout, + unsigned int repeats, + unsigned int queries, + ... + ); + +/** + * Get textual presentation of DNS error code + */ +const char *rdns_strerror (enum dns_rcode rcode); + +/** + * Get textual presentation of DNS request type + */ +const char *rdns_strtype (enum rdns_request_type type); + +/** + * Parse string and return request type + * @param str + * @return + */ +enum rdns_request_type rdns_type_fromstr (const char *str); + +/** + * Returns string representing request type + * @param rcode + * @return + */ +const char * +rdns_str_from_type (enum rdns_request_type rcode); + +/** + * Parse string and return error code + * @param str + * @return + */ +enum dns_rcode rdns_rcode_fromstr (const char *str); + +/** + * Increase refcount for a request + * @param req + * @return + */ +struct rdns_request* rdns_request_retain (struct rdns_request *req); + +/** + * Decrease refcount for a request and free it if refcount is 0 + * @param req + */ +void rdns_request_release (struct rdns_request *req); + +/** + * Check whether a request contains `type` request + * @param req request object + * @param type check for a specified type + * @return true if `type` has been requested + */ +bool rdns_request_has_type (struct rdns_request *req, enum rdns_request_type type); + +/** + * Return requested name for a request + * @param req request object + * @return requested name as it was passed to `rdns_make_request` + */ +const struct rdns_request_name* rdns_request_get_name (struct rdns_request *req, + unsigned int *count); + +/** + * Return a DNS server name associated with the request + * @param req request object + * @return name of a DNS server + */ +const char* rdns_request_get_server (struct rdns_request *req); + + +/** + * Return PTR string for a request (ipv4 or ipv6) addresses + * @param str string representation of IP address + * @return name to resolve or NULL if `str` is not an IP address; caller must free result when it is unused + */ +char * rdns_generate_ptr_from_str (const char *str); + +/** + * Format DNS name of the packet punycoding if needed + * @param req request + * @param name name string + * @param namelen length of name + */ +bool rdns_format_dns_name (struct rdns_resolver *resolver, + const char *name, size_t namelen, + char **out, size_t *outlen); + +/* + * Private functions used by async libraries as callbacks + */ + +void rdns_process_read (int fd, void *arg); +void rdns_process_timer (void *arg); +void rdns_process_write (int fd, void *arg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/contrib/librdns/rdns_curve.h b/contrib/librdns/rdns_curve.h new file mode 100644 index 0000000..365e91b --- /dev/null +++ b/contrib/librdns/rdns_curve.h @@ -0,0 +1,75 @@ +/* + * 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 BY AUTHOR ''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. + */ +#ifndef RDNS_CURVE_H_ +#define RDNS_CURVE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct rdns_curve_ctx; + +/** + * Create new dnscurve ctx + * @return + */ +struct rdns_curve_ctx* rdns_curve_ctx_new (double rekey_interval); + +/** + * Add key for server `name` + * @param ctx curve context + * @param name name of server (ip address) + * @param pubkey pubkey bytes (must be `RDSN_CURVE_PUBKEY_LEN`) + */ +void rdns_curve_ctx_add_key (struct rdns_curve_ctx *ctx, + const char *name, const unsigned char *pubkey); + +/** + * Destroy curve context + * @param ctx + */ +void rdns_curve_ctx_destroy (struct rdns_curve_ctx *ctx); + + +/** + * Register DNSCurve plugin (libsodium should be enabled for this) + * @param resolver + * @param ctx + */ +void rdns_curve_register_plugin (struct rdns_resolver *resolver, + struct rdns_curve_ctx *ctx); + +/** + * Create DNSCurve key from the base16 encoded string + * @param hex input hex (must be NULL terminated) + * @return a key or NULL (not NULL terminated) + */ +unsigned char * rdns_curve_key_from_hex (const char *hex); + +#ifdef __cplusplus +} +#endif + +#endif /* RDNS_CURVE_H_ */ diff --git a/contrib/librdns/rdns_ev.h b/contrib/librdns/rdns_ev.h new file mode 100644 index 0000000..35f532a --- /dev/null +++ b/contrib/librdns/rdns_ev.h @@ -0,0 +1,235 @@ +/* + * 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 BY AUTHOR ''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. + */ +#ifndef RDNS_EV_H_ +#define RDNS_EV_H_ + +#include "contrib/libev/ev.h" +#include <stdlib.h> +#include <string.h> +#include "rdns.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static void* rdns_libev_add_read (void *priv_data, int fd, void *user_data); +static void rdns_libev_del_read(void *priv_data, void *ev_data); +static void* rdns_libev_add_write (void *priv_data, int fd, void *user_data); +static void rdns_libev_del_write (void *priv_data, void *ev_data); +static void* rdns_libev_add_timer (void *priv_data, double after, void *user_data); +static void* rdns_libev_add_periodic (void *priv_data, double after, + rdns_periodic_callback cb, void *user_data); +static void rdns_libev_del_periodic (void *priv_data, void *ev_data); +static void rdns_libev_repeat_timer (void *priv_data, void *ev_data); +static void rdns_libev_del_timer (void *priv_data, void *ev_data); + +struct rdns_ev_periodic_cbdata { + ev_timer *ev; + rdns_periodic_callback cb; + void *cbdata; +}; + +static void +rdns_bind_libev (struct rdns_resolver *resolver, struct ev_loop *loop) +{ + static struct rdns_async_context ev_ctx = { + .data = NULL, + .add_read = rdns_libev_add_read, + .del_read = rdns_libev_del_read, + .add_write = rdns_libev_add_write, + .del_write = rdns_libev_del_write, + .add_timer = rdns_libev_add_timer, + .repeat_timer = rdns_libev_repeat_timer, + .del_timer = rdns_libev_del_timer, + .add_periodic = rdns_libev_add_periodic, + .del_periodic = rdns_libev_del_periodic, + .cleanup = NULL + }, *nctx; + void *ptr; + + /* XXX: never got freed */ + ptr = malloc (sizeof (struct rdns_async_context)); + if (ptr != NULL) { + nctx = (struct rdns_async_context *)ptr; + memcpy (ptr, (void *)&ev_ctx, sizeof (struct rdns_async_context)); + nctx->data = (void*)loop; + rdns_resolver_async_bind (resolver, nctx); + } +} + +static void +rdns_libev_read_event (struct ev_loop *loop, ev_io *ev, int revents) +{ + rdns_process_read (ev->fd, ev->data); +} + +static void +rdns_libev_write_event (struct ev_loop *loop, ev_io *ev, int revents) +{ + rdns_process_write(ev->fd, ev->data); +} + +static void +rdns_libev_timer_event (struct ev_loop *loop, ev_timer *ev, int revents) +{ + rdns_process_timer (ev->data); +} + +static void +rdns_libev_periodic_event (struct ev_loop *loop, ev_timer *ev, int revents) +{ + struct rdns_ev_periodic_cbdata *cbdata = (struct rdns_ev_periodic_cbdata *) + ev->data; + cbdata->cb (cbdata->cbdata); +} + +static void* +rdns_libev_add_read (void *priv_data, int fd, void *user_data) +{ + ev_io *ev; + void *ptr; + + ptr = malloc (sizeof (ev_io)); + if (ptr != NULL) { + ev = (ev_io *)ptr; + ev_io_init (ev, rdns_libev_read_event, fd, EV_READ); + ev->data = user_data; + ev_io_start ((struct ev_loop *)priv_data, ev); + } + return ptr; +} + +static void +rdns_libev_del_read (void *priv_data, void *ev_data) +{ + ev_io *ev = (ev_io*)ev_data; + if (ev != NULL) { + ev_io_stop ((struct ev_loop *)priv_data, ev); + free ((void *)ev); + } +} +static void* +rdns_libev_add_write (void *priv_data, int fd, void *user_data) +{ + ev_io *ev; + + ev = (ev_io *)malloc (sizeof (ev_io)); + if (ev != NULL) { + ev_io_init (ev, rdns_libev_write_event, fd, EV_WRITE); + ev->data = user_data; + ev_io_start ((struct ev_loop *)priv_data, ev); + } + return (void *)ev; +} + +static void +rdns_libev_del_write (void *priv_data, void *ev_data) +{ + ev_io *ev = (ev_io *)ev_data; + if (ev != NULL) { + ev_io_stop ((struct ev_loop *)priv_data, ev); + free ((void *)ev); + } +} + +static void* +rdns_libev_add_timer (void *priv_data, double after, void *user_data) +{ + ev_timer *ev; + ev = (ev_timer *)malloc (sizeof (ev_timer)); + if (ev != NULL) { + ev_timer_init (ev, rdns_libev_timer_event, after, after); + ev->data = user_data; + ev_now_update_if_cheap ((struct ev_loop *)priv_data); + ev_timer_start ((struct ev_loop *)priv_data, ev); + } + return (void *)ev; +} + +static void* +rdns_libev_add_periodic (void *priv_data, double after, + rdns_periodic_callback cb, void *user_data) +{ + ev_timer *ev; + struct rdns_ev_periodic_cbdata *cbdata = NULL; + + ev = (ev_timer *)malloc (sizeof (ev_timer)); + if (ev != NULL) { + cbdata = (struct rdns_ev_periodic_cbdata *) + malloc (sizeof (struct rdns_ev_periodic_cbdata)); + if (cbdata != NULL) { + cbdata->cb = cb; + cbdata->cbdata = user_data; + cbdata->ev = ev; + ev_timer_init (ev, rdns_libev_periodic_event, after, after); + ev->data = cbdata; + ev_now_update_if_cheap ((struct ev_loop *)priv_data); + ev_timer_start ((struct ev_loop *)priv_data, ev); + } + else { + free ((void *)ev); + return NULL; + } + } + return (void *)cbdata; +} + +static void +rdns_libev_del_periodic (void *priv_data, void *ev_data) +{ + struct rdns_ev_periodic_cbdata *cbdata = (struct rdns_ev_periodic_cbdata *) + ev_data; + if (cbdata != NULL) { + ev_timer_stop ((struct ev_loop *)priv_data, (ev_timer *)cbdata->ev); + free ((void *)cbdata->ev); + free ((void *)cbdata); + } +} + +static void +rdns_libev_repeat_timer (void *priv_data, void *ev_data) +{ + ev_timer *ev = (ev_timer *)ev_data; + if (ev != NULL) { + ev_now_update_if_cheap ((struct ev_loop *)priv_data); + ev_timer_again ((struct ev_loop *)priv_data, ev); + } +} + +static void +rdns_libev_del_timer (void *priv_data, void *ev_data) +{ + ev_timer *ev = (ev_timer *)ev_data; + if (ev != NULL) { + ev_timer_stop ((struct ev_loop *)priv_data, ev); + free ((void *)ev); + } +} + +#ifdef __cplusplus +} +#endif + +#endif /* RDNS_EV_H_ */ diff --git a/contrib/librdns/rdns_event.h b/contrib/librdns/rdns_event.h new file mode 100644 index 0000000..027181a --- /dev/null +++ b/contrib/librdns/rdns_event.h @@ -0,0 +1,231 @@ +/* + * 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 BY AUTHOR ''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. + */ +#ifndef RDNS_EVENT_H_ +#define RDNS_EVENT_H_ + +#include <event.h> +#include <stdlib.h> +#include <string.h> +#include "rdns.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static void* rdns_libevent_add_read (void *priv_data, int fd, void *user_data); +static void rdns_libevent_del_read(void *priv_data, void *ev_data); +static void* rdns_libevent_add_write (void *priv_data, int fd, void *user_data); +static void rdns_libevent_del_write (void *priv_data, void *ev_data); +static void* rdns_libevent_add_timer (void *priv_data, double after, void *user_data); +static void* rdns_libevent_add_periodic (void *priv_data, double after, + rdns_periodic_callback cb, void *user_data); +static void rdns_libevent_del_periodic (void *priv_data, void *ev_data); +static void rdns_libevent_repeat_timer (void *priv_data, void *ev_data); +static void rdns_libevent_del_timer (void *priv_data, void *ev_data); + +struct rdns_event_periodic_cbdata { + struct event *ev; + rdns_periodic_callback cb; + void *cbdata; +}; + +static void +rdns_bind_libevent (struct rdns_resolver *resolver, struct event_base *ev_base) +{ + struct rdns_async_context ev_ctx = { + .add_read = rdns_libevent_add_read, + .del_read = rdns_libevent_del_read, + .add_write = rdns_libevent_add_write, + .del_write = rdns_libevent_del_write, + .add_timer = rdns_libevent_add_timer, + .add_periodic = rdns_libevent_add_periodic, + .del_periodic = rdns_libevent_del_periodic, + .repeat_timer = rdns_libevent_repeat_timer, + .del_timer = rdns_libevent_del_timer, + .cleanup = NULL + }, *nctx; + + /* XXX: never got freed */ + nctx = malloc (sizeof (struct rdns_async_context)); + if (nctx != NULL) { + memcpy (nctx, &ev_ctx, sizeof (struct rdns_async_context)); + nctx->data = ev_base; + } + rdns_resolver_async_bind (resolver, nctx); +} + +static void +rdns_libevent_read_event (int fd, short what, void *ud) +{ + rdns_process_read (fd, ud); +} + +static void +rdns_libevent_write_event (int fd, short what, void *ud) +{ + rdns_process_write (fd, ud); +} + +static void +rdns_libevent_timer_event (int fd, short what, void *ud) +{ + rdns_process_timer (ud); +} + +static void +rdns_libevent_periodic_event (int fd, short what, void *ud) +{ + struct rdns_event_periodic_cbdata *cbdata = ud; + cbdata->cb (cbdata->cbdata); +} + +static void* +rdns_libevent_add_read (void *priv_data, int fd, void *user_data) +{ + struct event *ev; + ev = malloc (sizeof (struct event)); + if (ev != NULL) { + event_set (ev, fd, EV_READ | EV_PERSIST, rdns_libevent_read_event, user_data); + event_base_set (priv_data, ev); + event_add (ev, NULL); + } + return ev; +} + +static void +rdns_libevent_del_read(void *priv_data, void *ev_data) +{ + struct event *ev = ev_data; + if (ev != NULL) { + event_del (ev); + free (ev); + } +} +static void* +rdns_libevent_add_write (void *priv_data, int fd, void *user_data) +{ + struct event *ev; + ev = malloc (sizeof (struct event)); + if (ev != NULL) { + event_set (ev, fd, EV_WRITE | EV_PERSIST, + rdns_libevent_write_event, user_data); + event_base_set (priv_data, ev); + event_add (ev, NULL); + } + return ev; +} + +static void +rdns_libevent_del_write (void *priv_data, void *ev_data) +{ + struct event *ev = ev_data; + if (ev != NULL) { + event_del (ev); + free (ev); + } +} + +#define rdns_event_double_to_tv(dbl, tv) do { \ + (tv)->tv_sec = (int)(dbl); \ + (tv)->tv_usec = ((dbl) - (int)(dbl))*1000*1000; \ +} while(0) + +static void* +rdns_libevent_add_timer (void *priv_data, double after, void *user_data) +{ + struct event *ev; + struct timeval tv; + ev = malloc (sizeof (struct event)); + if (ev != NULL) { + rdns_event_double_to_tv (after, &tv); + event_set (ev, -1, EV_TIMEOUT|EV_PERSIST, rdns_libevent_timer_event, user_data); + event_base_set (priv_data, ev); + event_add (ev, &tv); + } + return ev; +} + +static void* +rdns_libevent_add_periodic (void *priv_data, double after, + rdns_periodic_callback cb, void *user_data) +{ + struct event *ev; + struct timeval tv; + struct rdns_event_periodic_cbdata *cbdata = NULL; + + ev = malloc (sizeof (struct event)); + if (ev != NULL) { + cbdata = malloc (sizeof (struct rdns_event_periodic_cbdata)); + if (cbdata != NULL) { + rdns_event_double_to_tv (after, &tv); + cbdata->cb = cb; + cbdata->cbdata = user_data; + cbdata->ev = ev; + event_set (ev, -1, EV_TIMEOUT|EV_PERSIST, rdns_libevent_periodic_event, cbdata); + event_base_set (priv_data, ev); + event_add (ev, &tv); + } + else { + free (ev); + return NULL; + } + } + return cbdata; +} + +static void +rdns_libevent_del_periodic (void *priv_data, void *ev_data) +{ + struct rdns_event_periodic_cbdata *cbdata = ev_data; + if (cbdata != NULL) { + event_del (cbdata->ev); + free (cbdata->ev); + free (cbdata); + } +} + +static void +rdns_libevent_repeat_timer (void *priv_data, void *ev_data) +{ + /* XXX: libevent hides timeval, so timeouts are persistent here */ +} + +#undef rdns_event_double_to_tv + +static void +rdns_libevent_del_timer (void *priv_data, void *ev_data) +{ + struct event *ev = ev_data; + if (ev != NULL) { + event_del (ev); + free (ev); + } +} + +#ifdef __cplusplus +} +#endif + +#endif /* RDNS_EV_H_ */ diff --git a/contrib/librdns/ref.h b/contrib/librdns/ref.h new file mode 100644 index 0000000..a8016b1 --- /dev/null +++ b/contrib/librdns/ref.h @@ -0,0 +1,71 @@ +/* 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. + */ +#ifndef REF_H_ +#define REF_H_ + +/** + * @file ref.h + * A set of macros to handle refcounts + */ + +typedef void (*ref_dtor_cb_t)(void *data); + +typedef struct ref_entry_s { + unsigned int refcount; + ref_dtor_cb_t dtor; +} ref_entry_t; + +#define REF_INIT(obj, dtor_cb) do { \ + (obj)->ref.refcount = 0; \ + (obj)->ref.dtor = (ref_dtor_cb_t)(dtor_cb); \ +} while (0) + +#define REF_INIT_RETAIN(obj, dtor_cb) do { \ + (obj)->ref.refcount = 1; \ + (obj)->ref.dtor = (ref_dtor_cb_t)(dtor_cb); \ +} while (0) + +#ifdef HAVE_ATOMIC_BUILTINS +#define REF_RETAIN(obj) do { \ + __sync_add_and_fetch (&(obj)->ref.refcount, 1); \ +} while (0) + +#define REF_RELEASE(obj) do { \ + unsigned int rc = __sync_sub_and_fetch (&(obj)->ref.refcount, 1); \ + if (rc == 0 && (obj)->ref.dtor) { \ + (obj)->ref.dtor (obj); \ + } \ +} while (0) +#else +#define REF_RETAIN(obj) do { \ + (obj)->ref.refcount ++; \ +} while (0) + +#define REF_RELEASE(obj) do { \ + if (--(obj)->ref.refcount == 0 && (obj)->ref.dtor) { \ + (obj)->ref.dtor (obj); \ + } \ +} while (0) +#endif + +#endif /* REF_H_ */ diff --git a/contrib/librdns/resolver.c b/contrib/librdns/resolver.c new file mode 100644 index 0000000..bfcfd0a --- /dev/null +++ b/contrib/librdns/resolver.c @@ -0,0 +1,1577 @@ +/* + * 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 BY AUTHOR ''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 <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <sys/uio.h> + +#include "rdns.h" +#include "dns_private.h" +#include "ottery.h" +#include "util.h" +#include "packet.h" +#include "parse.h" +#include "logger.h" +#include "compression.h" + +__KHASH_IMPL(rdns_requests_hash, kh_inline, int, struct rdns_request *, true, + kh_int_hash_func, kh_int_hash_equal); + +static int +rdns_send_request (struct rdns_request *req, int fd, bool new_req) +{ + ssize_t r; + struct rdns_server *serv = req->io->srv; + struct rdns_resolver *resolver = req->resolver; + struct dns_header *header; + const int max_id_cycles = 32; + khiter_t k; + + /* Find ID collision */ + if (new_req) { + r = 0; + + for (;;) { + k = kh_get(rdns_requests_hash, req->io->requests, req->id); + if (k != kh_end(req->io->requests)) { + /* Check for unique id */ + header = (struct dns_header *) req->packet; + header->qid = rdns_permutor_generate_id(); + req->id = header->qid; + if (++r > max_id_cycles) { + return -1; + } + } + else { + break; + } + } + } + + if (resolver->curve_plugin == NULL) { + if (!IS_CHANNEL_CONNECTED(req->io)) { + r = sendto (fd, req->packet, req->pos, 0, + req->io->saddr, + req->io->slen); + } + else { + r = send (fd, req->packet, req->pos, 0); + } + } + else { + if (!IS_CHANNEL_CONNECTED(req->io)) { + r = resolver->curve_plugin->cb.curve_plugin.send_cb (req, + resolver->curve_plugin->data, + req->io->saddr, + req->io->slen); + } + else { + r = resolver->curve_plugin->cb.curve_plugin.send_cb (req, + resolver->curve_plugin->data, + NULL, + 0); + } + } + if (r == -1) { + if (errno == EAGAIN || errno == EINTR) { + if (new_req) { + /* Write when socket is ready */ + int pr; + + k = kh_put(rdns_requests_hash, req->io->requests, req->id, &pr); + kh_value(req->io->requests, k) = req; + req->async_event = resolver->async->add_write (resolver->async->data, + fd, req); + req->state = RDNS_REQUEST_WAIT_SEND; + } + /* + * If request is already processed then the calling function + * should take care about events processing + */ + return 0; + } + else { + rdns_debug ("send failed: %s for server %s", strerror (errno), serv->name); + return -1; + } + } + else if (!IS_CHANNEL_CONNECTED(req->io)) { + /* Connect socket */ + r = connect (fd, req->io->saddr, req->io->slen); + + if (r == -1) { + rdns_err ("cannot connect after sending request: %s for server %s", + strerror (errno), serv->name); + } + else { + req->io->flags |= RDNS_CHANNEL_CONNECTED; + } + } + + if (new_req) { + /* Add request to hash table */ + int pr; + k = kh_put(rdns_requests_hash, req->io->requests, req->id, &pr); + kh_value(req->io->requests, k) = req; + /* Fill timeout */ + req->async_event = resolver->async->add_timer (resolver->async->data, + req->timeout, req); + req->state = RDNS_REQUEST_WAIT_REPLY; + } + + return 1; +} + + +static struct rdns_request * +rdns_find_dns_request (uint8_t *in, struct rdns_io_channel *ioc) +{ + struct dns_header header; + int id; + struct rdns_resolver *resolver = ioc->resolver; + + memcpy (&header, in, sizeof(header)); + id = header.qid; + khiter_t k = kh_get(rdns_requests_hash, ioc->requests, id); + + if (k == kh_end(ioc->requests)) { + /* No such requests found */ + rdns_debug ("DNS request with id %d has not been found for IO channel", id); + + return NULL; + } + + return kh_value(ioc->requests, k); +} + +static bool +rdns_parse_reply (uint8_t *in, int r, struct rdns_request *req, + struct rdns_reply **_rep) +{ + struct dns_header *header = (struct dns_header *)in; + struct rdns_reply *rep; + struct rdns_reply_entry *elt; + uint8_t *pos, *npos; + struct rdns_resolver *resolver = req->resolver; + uint16_t qdcount; + int type; + bool found = false; + + int i, t; + + /* First check header fields */ + if (header->qr == 0) { + rdns_info ("got request while waiting for reply"); + return false; + } + + qdcount = ntohs (header->qdcount); + + if (qdcount != req->qcount) { + rdns_info ("request has %d queries, reply has %d queries", (int)req->qcount, (int)header->qdcount); + return false; + } + + /* + * Now we have request and query data is now at the end of header, so compare + * request QR section and reply QR section + */ + req->pos = sizeof (struct dns_header); + pos = in + sizeof (struct dns_header); + t = r - sizeof (struct dns_header); + for (i = 0; i < (int)qdcount; i ++) { + if ((npos = rdns_request_reply_cmp (req, pos,t)) == NULL) { + rdns_info ("DNS request with id %d is for different query, ignoring", (int)req->id); + return false; + } + t -= npos - pos; + pos = npos; + } + /* + * Now pos is in answer section, so we should extract data and form reply + */ + rep = rdns_make_reply (req, header->rcode); + + if (header->ad) { + rep->flags |= RDNS_AUTH; + } + + if (header->tc) { + rep->flags |= RDNS_TRUNCATED; + } + + if (rep == NULL) { + rdns_warn ("Cannot allocate memory for reply"); + return false; + } + + type = req->requested_names[0].type; + + if (rep->code == RDNS_RC_NOERROR) { + r -= pos - in; + /* Extract RR records */ + for (i = 0; i < ntohs (header->ancount); i ++) { + elt = malloc (sizeof (struct rdns_reply_entry)); + t = rdns_parse_rr (resolver, in, elt, &pos, rep, &r); + if (t == -1) { + free (elt); + rdns_debug ("incomplete reply"); + break; + } + else if (t == 1) { + DL_APPEND (rep->entries, elt); + if (elt->type == type) { + found = true; + } + } + else { + rdns_debug ("no matching reply for %s", + req->requested_names[0].name); + free (elt); + } + } + } + + if (!found && type != RDNS_REQUEST_ANY) { + /* We have not found the requested RR type */ + if (rep->code == RDNS_RC_NOERROR) { + rep->code = RDNS_RC_NOREC; + } + } + + *_rep = rep; + return true; +} + +static bool +rdns_tcp_maybe_realloc_read_buf (struct rdns_io_channel *ioc) +{ + if (ioc->tcp->read_buf_allocated == 0 && ioc->tcp->next_read_size > 0) { + ioc->tcp->cur_read_buf = malloc(ioc->tcp->next_read_size); + + if (ioc->tcp->cur_read_buf == NULL) { + return false; + } + ioc->tcp->read_buf_allocated = ioc->tcp->next_read_size; + } + else if (ioc->tcp->read_buf_allocated < ioc->tcp->next_read_size) { + /* Need to realloc */ + unsigned next_shift = ioc->tcp->next_read_size; + + if (next_shift < ioc->tcp->read_buf_allocated * 2) { + if (next_shift < UINT16_MAX && ioc->tcp->read_buf_allocated * 2 <= UINT16_MAX) { + next_shift = ioc->tcp->read_buf_allocated * 2; + } + } + void *next_buf = realloc(ioc->tcp->cur_read_buf, next_shift); + + if (next_buf == NULL) { + free (ioc->tcp->cur_read_buf); + ioc->tcp->cur_read_buf = NULL; + return false; + } + + ioc->tcp->cur_read_buf = next_buf; + } + + return true; +} + +static void +rdns_process_tcp_read (int fd, struct rdns_io_channel *ioc) +{ + ssize_t r; + struct rdns_resolver *resolver = ioc->resolver; + + if (ioc->tcp->cur_read == 0) { + /* We have to read size first */ + r = read(fd, &ioc->tcp->next_read_size, sizeof(ioc->tcp->next_read_size)); + + if (r == -1 || r == 0) { + goto err; + } + + ioc->tcp->cur_read += r; + + if (r == sizeof(ioc->tcp->next_read_size)) { + ioc->tcp->next_read_size = ntohs(ioc->tcp->next_read_size); + + /* We have read the size, so we can try read one more time */ + if (!rdns_tcp_maybe_realloc_read_buf(ioc)) { + rdns_err("failed to allocate %d bytes: %s", + (int)ioc->tcp->next_read_size, strerror(errno)); + r = -1; + goto err; + } + } + else { + /* We have read one byte, need to retry... */ + return; + } + } + else if (ioc->tcp->cur_read == 1) { + r = read(fd, ((unsigned char *)&ioc->tcp->next_read_size) + 1, 1); + + if (r == -1 || r == 0) { + goto err; + } + + ioc->tcp->cur_read += r; + ioc->tcp->next_read_size = ntohs(ioc->tcp->next_read_size); + + /* We have read the size, so we can try read one more time */ + if (!rdns_tcp_maybe_realloc_read_buf(ioc)) { + rdns_err("failed to allocate %d bytes: %s", + (int)ioc->tcp->next_read_size, strerror(errno)); + r = -1; + goto err; + } + } + + if (ioc->tcp->next_read_size < sizeof(struct dns_header)) { + /* Truncated reply, reset channel */ + rdns_err("got truncated size: %d on TCP read", ioc->tcp->next_read_size); + r = -1; + errno = EINVAL; + goto err; + } + + /* Try to read the full packet if we can */ + int to_read = ioc->tcp->next_read_size - (ioc->tcp->cur_read - 2); + + if (to_read <= 0) { + /* Internal error */ + rdns_err("internal buffer error on reading!"); + r = -1; + errno = EINVAL; + goto err; + } + + r = read(fd, ioc->tcp->cur_read_buf + (ioc->tcp->cur_read - 2), to_read); + ioc->tcp->cur_read += r; + + if ((ioc->tcp->cur_read - 2) == ioc->tcp->next_read_size) { + /* We have a full packet ready, process it */ + struct rdns_request *req = rdns_find_dns_request (ioc->tcp->cur_read_buf, ioc); + + if (req != NULL) { + struct rdns_reply *rep; + + if (rdns_parse_reply (ioc->tcp->cur_read_buf, + ioc->tcp->next_read_size, req, &rep)) { + UPSTREAM_OK (req->io->srv); + + if (req->resolver->ups && req->io->srv->ups_elt) { + req->resolver->ups->ok (req->io->srv->ups_elt, + req->resolver->ups->data); + } + + req->func (rep, req->arg); + REF_RELEASE (req); + } + } + else { + rdns_warn("unwanted DNS id received over TCP"); + } + + ioc->tcp->next_read_size = 0; + ioc->tcp->cur_read = 0; + + /* Retry read the next packet to avoid unnecessary polling */ + rdns_process_tcp_read (fd, ioc); + } + + return; + +err: + if (r == 0) { + /* Got EOF, just close the socket */ + rdns_debug ("closing TCP channel due to EOF"); + rdns_ioc_tcp_reset (ioc); + } + else if (errno == EINTR || errno == EAGAIN) { + /* We just retry later as there is no real error */ + return; + } + else { + rdns_debug ("closing TCP channel due to IO error: %s", strerror(errno)); + rdns_ioc_tcp_reset (ioc); + } +} + +static void +rdns_process_tcp_connect (int fd, struct rdns_io_channel *ioc) +{ + ioc->flags |= RDNS_CHANNEL_CONNECTED|RDNS_CHANNEL_ACTIVE; + ioc->flags &= ~RDNS_CHANNEL_TCP_CONNECTING; + + if (ioc->tcp->async_read == NULL) { + ioc->tcp->async_read = ioc->resolver->async->add_read(ioc->resolver->async->data, + ioc->sock, ioc); + } +} + +static bool +rdns_reschedule_req_over_tcp (struct rdns_request *req, struct rdns_server *serv) +{ + struct rdns_resolver *resolver; + struct rdns_io_channel *old_ioc = req->io, + *ioc = serv->tcp_io_channels[ottery_rand_uint32 () % serv->tcp_io_cnt]; + + resolver = req->resolver; + + if (ioc != NULL) { + if (!IS_CHANNEL_CONNECTED(ioc)) { + if (!rdns_ioc_tcp_connect(ioc)) { + return false; + } + } + + struct rdns_tcp_output_chain *oc; + + oc = calloc(1, sizeof(*oc) + req->packet_len); + + if (oc == NULL) { + rdns_err("failed to allocate output buffer for TCP ioc: %s", + strerror(errno)); + return false; + } + + oc->write_buf = ((unsigned char *)oc) + sizeof(*oc); + memcpy(oc->write_buf, req->packet, req->packet_len); + oc->next_write_size = htons(req->packet_len); + + DL_APPEND(ioc->tcp->output_chain, oc); + + if (ioc->tcp->async_write == NULL) { + ioc->tcp->async_write = resolver->async->add_write ( + resolver->async->data, + ioc->sock, ioc); + } + + req->state = RDNS_REQUEST_TCP; + /* Switch IO channel from UDP to TCP */ + rdns_request_remove_from_hash (req); + req->io = ioc; + + khiter_t k; + for (;;) { + int pr; + k = kh_put(rdns_requests_hash, ioc->requests, req->id, &pr); + + if (pr == 0) { + /* We have already a request with this id, so we have to regenerate ID */ + req->id = rdns_permutor_generate_id (); + /* Update packet as well */ + uint16_t raw_id = req->id; + memcpy(req->packet, &raw_id, sizeof(raw_id)); + } + else { + break; + } + } + + req->async_event = resolver->async->add_timer (resolver->async->data, + req->timeout, req); + + kh_value(req->io->requests, k) = req; + REF_RELEASE(old_ioc); + REF_RETAIN(ioc); + + return true; + } + + return false; +} + +static void +rdns_process_udp_read (int fd, struct rdns_io_channel *ioc) +{ + struct rdns_resolver *resolver; + struct rdns_request *req = NULL; + ssize_t r; + struct rdns_reply *rep; + uint8_t in[UDP_PACKET_SIZE]; + + resolver = ioc->resolver; + + /* First read packet from socket */ + if (resolver->curve_plugin == NULL) { + r = recv (fd, in, sizeof (in), 0); + if (r > (int)(sizeof (struct dns_header) + sizeof (struct dns_query))) { + req = rdns_find_dns_request (in, ioc); + } + } + else { + r = resolver->curve_plugin->cb.curve_plugin.recv_cb (ioc, in, + sizeof (in), resolver->curve_plugin->data, &req, + ioc->saddr, ioc->slen); + if (req == NULL && + r > (int)(sizeof (struct dns_header) + sizeof (struct dns_query))) { + req = rdns_find_dns_request (in, ioc); + } + } + + if (req != NULL) { + if (rdns_parse_reply (in, r, req, &rep)) { + UPSTREAM_OK (req->io->srv); + + if (req->resolver->ups && req->io->srv->ups_elt) { + req->resolver->ups->ok (req->io->srv->ups_elt, + req->resolver->ups->data); + } + + rdns_request_unschedule (req, true); + + if (!(rep->flags & RDNS_TRUNCATED)) { + req->state = RDNS_REQUEST_REPLIED; + req->func(rep, req->arg); + /* This will free reply as well */ + REF_RELEASE (req); + } + else { + if (req->io->srv->tcp_io_cnt > 0) { + rdns_debug("truncated UDP reply for %s; schedule over TCP", req->requested_names[0].name); + /* Reschedule via TCP */ + if (!rdns_reschedule_req_over_tcp (req, req->io->srv)) { + /* Use truncated reply as we have no other options */ + req->state = RDNS_REQUEST_REPLIED; + req->func(rep, req->arg); + REF_RELEASE (req); + } + else { + /* Remove and free the truncated reply, as we have rescheduled the reply */ + req->reply = NULL; + rdns_reply_free(rep); + } + } + else { + /* No TCP channels available */ + req->state = RDNS_REQUEST_REPLIED; + req->func(rep, req->arg); + /* This will free reply as well */ + REF_RELEASE (req); + } + } + } + } + else { + /* Still want to increase uses */ + ioc->uses ++; + } +} + +void +rdns_process_read (int fd, void *arg) +{ + struct rdns_io_channel *ioc = (struct rdns_io_channel *)arg; + struct rdns_resolver *resolver; + + resolver = ioc->resolver; + + if (IS_CHANNEL_TCP(ioc)) { + if (IS_CHANNEL_CONNECTED(ioc)) { + rdns_process_tcp_read (fd, ioc); + } + else { + rdns_err ("read readiness on non connected TCP channel!"); + } + } + else { + rdns_process_udp_read (fd, ioc); + } +} + +void +rdns_process_timer (void *arg) +{ + struct rdns_request *req = (struct rdns_request *)arg; + struct rdns_reply *rep; + int r; + bool renew = false; + struct rdns_resolver *resolver; + struct rdns_server *serv = NULL; + unsigned cnt; + + req->retransmits --; + resolver = req->resolver; + + if (req->resolver->ups && req->io->srv->ups_elt) { + req->resolver->ups->fail (req->io->srv->ups_elt, + req->resolver->ups->data, "timeout waiting reply"); + } + else { + UPSTREAM_FAIL (req->io->srv, time (NULL)); + } + + if (req->state == RDNS_REQUEST_TCP) { + rep = rdns_make_reply (req, RDNS_RC_TIMEOUT); + rdns_request_unschedule (req, true); + req->state = RDNS_REQUEST_REPLIED; + req->func (rep, req->arg); + REF_RELEASE (req); + + return; + } + + if (req->retransmits == 0) { + + rep = rdns_make_reply (req, RDNS_RC_TIMEOUT); + rdns_request_unschedule (req, true); + req->state = RDNS_REQUEST_REPLIED; + req->func (rep, req->arg); + REF_RELEASE (req); + + return; + } + + if (!IS_CHANNEL_ACTIVE(req->io) || req->retransmits == 1) { + + if (resolver->ups) { + cnt = resolver->ups->count (resolver->ups->data); + } + else { + cnt = 0; + UPSTREAM_FOREACH (resolver->servers, serv) { + cnt ++; + } + } + + if (!IS_CHANNEL_ACTIVE(req->io) || cnt > 1) { + /* Do not reschedule IO requests on inactive sockets */ + rdns_debug ("reschedule request with id: %d", (int)req->id); + rdns_request_unschedule (req, true); + REF_RELEASE (req->io); + + if (resolver->ups) { + struct rdns_upstream_elt *elt; + + elt = resolver->ups->select_retransmit ( + req->requested_names[0].name, + req->requested_names[0].len, + req->io->srv->ups_elt, + resolver->ups->data); + + if (elt) { + serv = elt->server; + serv->ups_elt = elt; + } + else { + UPSTREAM_SELECT_ROUND_ROBIN (resolver->servers, serv); + } + } + else { + UPSTREAM_SELECT_ROUND_ROBIN (resolver->servers, serv); + } + + if (serv == NULL) { + rdns_warn ("cannot find suitable server for request"); + rep = rdns_make_reply (req, RDNS_RC_SERVFAIL); + req->state = RDNS_REQUEST_REPLIED; + req->func (rep, req->arg); + REF_RELEASE (req); + + return; + } + + /* Select random IO channel */ + req->io = serv->io_channels[ottery_rand_uint32 () % serv->io_cnt]; + req->io->uses ++; + REF_RETAIN (req->io); + renew = true; + } + } + + /* + * Note: when `renew` is true, then send_request deals with the + * timers and events itself + */ + r = rdns_send_request (req, req->io->sock, renew); + if (r == 0) { + /* Retransmit one more time */ + if (!renew) { + req->async->del_timer (req->async->data, + req->async_event); + req->async_event = req->async->add_write (req->async->data, + req->io->sock, req); + } + + req->state = RDNS_REQUEST_WAIT_SEND; + } + else if (r == -1) { + if (req->resolver->ups && req->io->srv->ups_elt) { + req->resolver->ups->fail (req->io->srv->ups_elt, + req->resolver->ups->data, "cannot send retransmit after timeout"); + } + else { + UPSTREAM_FAIL (req->io->srv, time (NULL)); + } + + if (!renew) { + req->async->del_timer (req->async->data, + req->async_event); + req->async_event = NULL; + rdns_request_remove_from_hash(req); + } + + /* We have not scheduled timeout actually due to send error */ + rep = rdns_make_reply (req, RDNS_RC_NETERR); + req->state = RDNS_REQUEST_REPLIED; + req->func (rep, req->arg); + REF_RELEASE (req); + } + else { + req->async->repeat_timer (req->async->data, req->async_event); + req->state = RDNS_REQUEST_WAIT_REPLY; + } +} + +static void +rdns_process_periodic (void *arg) +{ + struct rdns_resolver *resolver = (struct rdns_resolver*)arg; + struct rdns_server *serv; + + UPSTREAM_RESCAN (resolver->servers, time (NULL)); + + UPSTREAM_FOREACH (resolver->servers, serv) { + for (int i = 0; i < serv->tcp_io_cnt; i ++) { + if (IS_CHANNEL_CONNECTED(serv->tcp_io_channels[i])) { + /* Disconnect channels with no requests in flight */ + if (kh_size(serv->tcp_io_channels[i]->requests) == 0) { + rdns_debug ("reset inactive TCP connection to %s", serv->name); + rdns_ioc_tcp_reset (serv->tcp_io_channels[i]); + } + } + } + } +} + +static void +rdns_process_ioc_refresh (void *arg) +{ + struct rdns_resolver *resolver = (struct rdns_resolver*)arg; + struct rdns_server *serv; + struct rdns_io_channel *ioc, *nioc; + unsigned int i; + + if (resolver->max_ioc_uses > 0) { + UPSTREAM_FOREACH (resolver->servers, serv) { + for (i = 0; i < serv->io_cnt; i ++) { + ioc = serv->io_channels[i]; + if (ioc->uses > resolver->max_ioc_uses) { + /* Schedule IOC removing */ + nioc = rdns_ioc_new (serv, resolver, false); + + if (nioc == NULL) { + rdns_err ("calloc fails to allocate rdns_io_channel"); + continue; + } + + serv->io_channels[i] = nioc; + rdns_debug ("scheduled io channel for server %s to be refreshed after " + "%lu usages", serv->name, (unsigned long)ioc->uses); + ioc->flags &= ~RDNS_CHANNEL_ACTIVE; + REF_RELEASE (ioc); + } + } + } + } +} + +static void +rdns_process_udp_retransmit (int fd, struct rdns_request *req) +{ + struct rdns_resolver *resolver; + struct rdns_reply *rep; + int r; + + resolver = req->resolver; + + resolver->async->del_write (resolver->async->data, + req->async_event); + req->async_event = NULL; + + if (req->state == RDNS_REQUEST_FAKE) { + /* Reply is ready */ + req->func (req->reply, req->arg); + REF_RELEASE (req); + + return; + } + + r = rdns_send_request (req, fd, false); + + if (r == 0) { + /* Retransmit one more time */ + req->async_event = req->async->add_write (req->async->data, + fd, req); + req->state = RDNS_REQUEST_WAIT_SEND; + } + else if (r == -1) { + if (req->resolver->ups && req->io->srv->ups_elt) { + req->resolver->ups->fail (req->io->srv->ups_elt, + req->resolver->ups->data, "retransmit send failed"); + } + else { + UPSTREAM_FAIL (req->io->srv, time (NULL)); + } + + rep = rdns_make_reply (req, RDNS_RC_NETERR); + req->state = RDNS_REQUEST_REPLIED; + req->func (rep, req->arg); + REF_RELEASE (req); + } + else { + req->async_event = req->async->add_timer (req->async->data, + req->timeout, req); + req->state = RDNS_REQUEST_WAIT_REPLY; + } +} + +static ssize_t +rdns_write_output_chain (struct rdns_io_channel *ioc, struct rdns_tcp_output_chain *oc) +{ + ssize_t r; + struct iovec iov[2]; + int niov, already_written; + int packet_len = ntohs (oc->next_write_size); + + switch (oc->cur_write) { + case 0: + /* Size + DNS request in full */ + iov[0].iov_base = &oc->next_write_size; + iov[0].iov_len = sizeof (oc->next_write_size); + iov[1].iov_base = oc->write_buf; + iov[1].iov_len = packet_len; + niov = 2; + break; + case 1: + /* Partial Size + DNS request in full */ + iov[0].iov_base = ((unsigned char *)&oc->next_write_size) + 1; + iov[0].iov_len = 1; + iov[1].iov_base = oc->write_buf; + iov[1].iov_len = packet_len; + niov = 2; + break; + default: + /* Merely DNS packet */ + already_written = oc->cur_write - 2; + if (packet_len <= already_written) { + errno = EINVAL; + return -1; + } + iov[0].iov_base = oc->write_buf + already_written; + iov[0].iov_len = packet_len - already_written; + niov = 1; + break; + } + + r = writev(ioc->sock, iov, niov); + + if (r > 0) { + oc->cur_write += r; + } + + return r; +} + +static void +rdns_process_tcp_write (int fd, struct rdns_io_channel *ioc) +{ + struct rdns_resolver *resolver = ioc->resolver; + + + /* Try to write as much as we can */ + struct rdns_tcp_output_chain *oc, *tmp; + DL_FOREACH_SAFE(ioc->tcp->output_chain, oc, tmp) { + ssize_t r = rdns_write_output_chain (ioc, oc); + + if (r == -1) { + if (errno == EAGAIN || errno == EINTR) { + /* Write even is persistent */ + return; + } + else { + rdns_err ("error when trying to write request to %s: %s", + ioc->srv->name, strerror (errno)); + rdns_ioc_tcp_reset (ioc); + return; + } + } + else if (ntohs(oc->next_write_size) < oc->cur_write) { + /* Packet has been fully written, remove it */ + DL_DELETE(ioc->tcp->output_chain, oc); + free (oc); /* It also frees write buf */ + ioc->tcp->cur_output_chains --; + } + else { + /* Buffer is not yet processed, stop unless we can continue */ + break; + } + } + + if (ioc->tcp->cur_output_chains == 0) { + /* Unregister write event */ + ioc->resolver->async->del_write (ioc->resolver->async->data, + ioc->tcp->async_write); + ioc->tcp->async_write = NULL; + } +} + +void +rdns_process_write (int fd, void *arg) +{ + /* + * We first need to dispatch *arg to understand what has caused the write + * readiness event. + * The one possibility is that it was a UDP retransmit request, so our + * arg will be struct rdns_request * + * Another possibility is that write event was triggered by some TCP related + * stuff. In this case the only possibility is that our arg is struct rdns_io_channel * + * To distinguish these two cases (due to flaws in the rdns architecture in the first + * place) we compare the first 8 bytes with RDNS_IO_CHANNEL_TAG + */ + uint64_t tag; + + memcpy (&tag, arg, sizeof(tag)); + + if (tag == RDNS_IO_CHANNEL_TAG) { + struct rdns_io_channel *ioc = (struct rdns_io_channel *) arg; + + if (IS_CHANNEL_CONNECTED(ioc)) { + rdns_process_tcp_write(fd, ioc); + } + else { + rdns_process_tcp_connect(fd, ioc); + rdns_process_tcp_write(fd, ioc); + } + } + else { + struct rdns_request *req = (struct rdns_request *) arg; + rdns_process_udp_retransmit(fd, req); + } +} + +struct rdns_server * +rdns_select_request_upstream (struct rdns_resolver *resolver, + struct rdns_request *req, + bool is_retransmit, + struct rdns_server *prev_serv) +{ + struct rdns_server *serv = NULL; + + if (resolver->ups) { + struct rdns_upstream_elt *elt; + + if (is_retransmit && prev_serv) { + elt = resolver->ups->select_retransmit (req->requested_names[0].name, + req->requested_names[0].len, + prev_serv->ups_elt, + resolver->ups->data); + } + else { + elt = resolver->ups->select (req->requested_names[0].name, + req->requested_names[0].len, resolver->ups->data); + } + + if (elt) { + serv = elt->server; + serv->ups_elt = elt; + } + else { + UPSTREAM_SELECT_ROUND_ROBIN (resolver->servers, serv); + } + } + else { + UPSTREAM_SELECT_ROUND_ROBIN (resolver->servers, serv); + } + + return serv; +} + +#define align_ptr(p, a) \ + (guint8 *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1)) + +struct rdns_request* +rdns_make_request_full ( + struct rdns_resolver *resolver, + dns_callback_type cb, + void *cbdata, + double timeout, + unsigned int repeats, + unsigned int queries, + ... + ) +{ + va_list args; + struct rdns_request *req; + struct rdns_server *serv; + int r, type; + unsigned int i, tlen = 0, clen = 0, cur; + size_t olen; + const char *cur_name, *last_name = NULL; + khash_t(rdns_compression_hash) *comp = NULL; + struct rdns_fake_reply *fake_rep = NULL; + char fake_buf[MAX_FAKE_NAME + sizeof (struct rdns_fake_reply_idx) + 16]; + struct rdns_fake_reply_idx *idx; + + if (resolver == NULL || !resolver->initialized) { + if (resolver == NULL) { + return NULL; + } + + rdns_err ("resolver is uninitialized"); + + return NULL; + } + + req = malloc (sizeof (struct rdns_request)); + if (req == NULL) { + rdns_err ("failed to allocate memory for request: %s", + strerror (errno)); + return NULL; + } + + req->resolver = resolver; + req->func = cb; + req->arg = cbdata; + req->reply = NULL; + req->qcount = queries; + req->io = NULL; + req->state = RDNS_REQUEST_NEW; + req->packet = NULL; + req->requested_names = calloc (queries, sizeof (struct rdns_request_name)); + req->async_event = NULL; + + if (req->requested_names == NULL) { + free (req); + rdns_err ("failed to allocate memory for request data: %s", + strerror (errno)); + + return NULL; + } + + req->type = 0; +#ifdef TWEETNACL + req->curve_plugin_data = NULL; +#endif + REF_INIT_RETAIN (req, rdns_request_free); + + /* Calculate packet's total length based on records count */ + va_start (args, queries); + for (i = 0; i < queries * 2; i += 2) { + cur = i / 2; + cur_name = va_arg (args, const char *); + type = va_arg (args, int); + + if (cur_name != NULL) { + clen = strlen (cur_name); + + if (clen == 0) { + rdns_warn ("got empty name to resolve"); + rdns_request_free (req); + return NULL; + } + + if (cur_name[0] == '.') { + /* Skip dots at the begin */ + unsigned int ndots = strspn (cur_name, "."); + + cur_name += ndots; + clen -= ndots; + + if (clen == 0) { + rdns_warn ("got empty name to resolve"); + rdns_request_free (req); + return NULL; + } + } + + if (cur_name[clen - 1] == '.') { + /* Skip trailing dots */ + while (clen >= 1 && cur_name[clen - 1] == '.') { + clen --; + } + + if (clen == 0) { + rdns_warn ("got empty name to resolve"); + rdns_request_free (req); + return NULL; + } + } + + if (last_name == NULL && queries == 1 && clen < MAX_FAKE_NAME) { + /* We allocate structure in the static space */ + idx = (struct rdns_fake_reply_idx *)align_ptr (fake_buf, 16); + idx->type = type; + idx->len = clen; + memcpy (idx->request, cur_name, clen); + HASH_FIND (hh, resolver->fake_elts, idx, sizeof (*idx) + clen, + fake_rep); + + if (fake_rep) { + /* We actually treat it as a short-circuit */ + req->reply = rdns_make_reply (req, fake_rep->rcode); + req->reply->entries = fake_rep->result; + req->state = RDNS_REQUEST_FAKE; + } + } + + last_name = cur_name; + tlen += clen; + } + else if (last_name == NULL) { + rdns_err ("got NULL as the first name to resolve"); + rdns_request_free (req); + return NULL; + } + + if (req->state != RDNS_REQUEST_FAKE) { + if (!rdns_format_dns_name (resolver, last_name, clen, + &req->requested_names[cur].name, &olen)) { + rdns_err ("cannot format %s", last_name); + rdns_request_free (req); + return NULL; + } + + req->requested_names[cur].len = olen; + } + else { + req->requested_names[cur].len = clen; + } + + req->requested_names[cur].type = type; + } + + va_end (args); + + if (req->state != RDNS_REQUEST_FAKE) { + rdns_allocate_packet (req, tlen); + rdns_make_dns_header (req, queries); + + for (i = 0; i < queries; i++) { + cur_name = req->requested_names[i].name; + clen = req->requested_names[i].len; + type = req->requested_names[i].type; + if (queries > 1) { + if (!rdns_add_rr (req, cur_name, clen, type, &comp)) { + rdns_err ("cannot add rr"); + REF_RELEASE (req); + rdns_compression_free(comp); + return NULL; + } + } else { + if (!rdns_add_rr (req, cur_name, clen, type, NULL)) { + rdns_err ("cannot add rr"); + REF_RELEASE (req); + rdns_compression_free(comp); + return NULL; + } + } + } + + rdns_compression_free(comp); + + /* Add EDNS RR */ + rdns_add_edns0 (req); + + req->retransmits = repeats ? repeats : 1; + req->timeout = timeout; + req->state = RDNS_REQUEST_NEW; + } + + req->async = resolver->async; + + serv = rdns_select_request_upstream (resolver, req, false, NULL); + + if (serv == NULL) { + rdns_warn ("cannot find suitable server for request"); + REF_RELEASE (req); + return NULL; + } + + /* Select random IO channel */ + req->io = serv->io_channels[ottery_rand_uint32 () % serv->io_cnt]; + + if (req->state == RDNS_REQUEST_FAKE) { + req->async_event = resolver->async->add_write (resolver->async->data, + req->io->sock, req); + } + else { + /* Now send request to server */ + do { + r = rdns_send_request (req, req->io->sock, true); + + if (r == -1) { + req->retransmits --; /* It must be > 0 */ + + if (req->retransmits > 0) { + if (resolver->ups && serv->ups_elt) { + resolver->ups->fail (serv->ups_elt, resolver->ups->data, + "send IO error"); + } + else { + UPSTREAM_FAIL (serv, time (NULL)); + } + + serv = rdns_select_request_upstream (resolver, req, + true, serv); + + if (serv == NULL) { + rdns_warn ("cannot find suitable server for request"); + REF_RELEASE (req); + return NULL; + } + + req->io = serv->io_channels[ottery_rand_uint32 () % serv->io_cnt]; + } + else { + rdns_info ("cannot send DNS request: %s", strerror (errno)); + REF_RELEASE (req); + + if (resolver->ups && serv->ups_elt) { + resolver->ups->fail (serv->ups_elt, resolver->ups->data, + "send IO error"); + } + else { + UPSTREAM_FAIL (serv, time (NULL)); + } + + return NULL; + } + } + else { + /* All good */ + req->io->uses++; + break; + } + } while (req->retransmits > 0); + } + + REF_RETAIN (req->io); + REF_RETAIN (req->resolver); + + return req; +} + +bool +rdns_resolver_init (struct rdns_resolver *resolver) +{ + unsigned int i; + struct rdns_server *serv; + struct rdns_io_channel *ioc; + + if (!resolver->async_binded) { + rdns_err ("no async backend specified"); + return false; + } + + if (resolver->servers == NULL) { + rdns_err ("no DNS servers defined"); + return false; + } + + /* Now init io channels to all servers */ + UPSTREAM_FOREACH (resolver->servers, serv) { + serv->io_channels = calloc (serv->io_cnt, sizeof (struct rdns_io_channel *)); + + if (serv->io_channels == NULL) { + rdns_err ("cannot allocate memory for the resolver IO channels"); + return false; + } + + for (i = 0; i < serv->io_cnt; i ++) { + ioc = rdns_ioc_new(serv, resolver, false); + + if (ioc == NULL) { + rdns_err ("cannot allocate memory or init the IO channel"); + return false; + } + + serv->io_channels[i] = ioc; + } + + int ntcp_channels = 0; + + /* + * We are more forgiving for TCP IO channels: we can have zero of them + * if DNS is misconfigured and still be able to resolve stuff + */ + serv->tcp_io_channels = calloc (serv->tcp_io_cnt, sizeof (struct rdns_io_channel *)); + if (serv->tcp_io_channels == NULL) { + rdns_err ("cannot allocate memory for the resolver TCP IO channels"); + return false; + } + for (i = 0; i < serv->tcp_io_cnt; i ++) { + ioc = rdns_ioc_new (serv, resolver, true); + + if (ioc == NULL) { + rdns_err ("cannot allocate memory or init the TCP IO channel"); + continue; + } + + serv->tcp_io_channels[ntcp_channels++] = ioc; + } + + serv->tcp_io_cnt = ntcp_channels; + } + + if (resolver->async->add_periodic) { + resolver->periodic = resolver->async->add_periodic (resolver->async->data, + UPSTREAM_REVIVE_TIME, rdns_process_periodic, resolver); + } + + resolver->initialized = true; + + return true; +} + +void +rdns_resolver_register_plugin (struct rdns_resolver *resolver, + struct rdns_plugin *plugin) +{ + if (resolver != NULL && plugin != NULL) { + /* XXX: support only network plugin now, and only a single one */ + if (plugin->type == RDNS_PLUGIN_CURVE) { + resolver->curve_plugin = plugin; + } + } +} + +void * +rdns_resolver_add_server (struct rdns_resolver *resolver, + const char *name, unsigned int port, + int priority, unsigned int io_cnt) +{ + struct rdns_server *serv; + union { + struct in_addr v4; + struct in6_addr v6; + } addr; + + if (inet_pton (AF_INET, name, &addr) == 0 && + inet_pton (AF_INET6, name, &addr) == 0) { + /* Invalid IP */ + return NULL; + } + + if (io_cnt == 0) { + return NULL; + } + if (port == 0 || port > UINT16_MAX) { + return NULL; + } + + serv = calloc (1, sizeof (struct rdns_server)); + if (serv == NULL) { + return NULL; + } + serv->name = strdup (name); + if (serv->name == NULL) { + free (serv); + return NULL; + } + + serv->io_cnt = io_cnt; + /* TODO: make it configurable maybe? */ + serv->tcp_io_cnt = default_tcp_io_cnt; + serv->port = port; + + UPSTREAM_ADD (resolver->servers, serv, priority); + + return serv; +} + +void +rdns_resolver_set_logger (struct rdns_resolver *resolver, + rdns_log_function logger, void *log_data) +{ + resolver->logger = logger; + resolver->log_data = log_data; +} + +void +rdns_resolver_set_log_level (struct rdns_resolver *resolver, + enum rdns_log_level level) +{ + resolver->log_level = level; +} + +void +rdns_resolver_set_upstream_lib (struct rdns_resolver *resolver, + struct rdns_upstream_context *ups_ctx, + void *ups_data) +{ + resolver->ups = ups_ctx; + resolver->ups->data = ups_data; +} + + +void +rdns_resolver_set_max_io_uses (struct rdns_resolver *resolver, + uint64_t max_ioc_uses, double check_time) +{ + if (resolver->refresh_ioc_periodic != NULL) { + resolver->async->del_periodic (resolver->async->data, + resolver->refresh_ioc_periodic); + resolver->refresh_ioc_periodic = NULL; + } + + resolver->max_ioc_uses = max_ioc_uses; + if (check_time > 0.0 && resolver->async->add_periodic) { + resolver->refresh_ioc_periodic = + resolver->async->add_periodic (resolver->async->data, + check_time, rdns_process_ioc_refresh, resolver); + } +} + +static void +rdns_resolver_free (struct rdns_resolver *resolver) +{ + struct rdns_server *serv, *stmp; + struct rdns_io_channel *ioc; + unsigned int i; + + if (resolver->initialized) { + if (resolver->periodic != NULL) { + resolver->async->del_periodic (resolver->async->data, resolver->periodic); + } + if (resolver->refresh_ioc_periodic != NULL) { + resolver->async->del_periodic (resolver->async->data, + resolver->refresh_ioc_periodic); + } + if (resolver->curve_plugin != NULL && resolver->curve_plugin->dtor != NULL) { + resolver->curve_plugin->dtor (resolver, resolver->curve_plugin->data); + } + /* Stop IO watch on all IO channels */ + UPSTREAM_FOREACH_SAFE (resolver->servers, serv, stmp) { + for (i = 0; i < serv->io_cnt; i ++) { + ioc = serv->io_channels[i]; + REF_RELEASE (ioc); + } + for (i = 0; i < serv->tcp_io_cnt; i ++) { + ioc = serv->tcp_io_channels[i]; + REF_RELEASE (ioc); + } + UPSTREAM_DEL (resolver->servers, serv); + free (serv->io_channels); + free (serv->tcp_io_channels); + free (serv->name); + free (serv); + } + } + free (resolver->async); + free (resolver); +} + + +struct rdns_resolver * +rdns_resolver_new (int flags) +{ + struct rdns_resolver *new_resolver; + + new_resolver = calloc (1, sizeof (struct rdns_resolver)); + + REF_INIT_RETAIN (new_resolver, rdns_resolver_free); + + new_resolver->logger = rdns_logger_internal; + new_resolver->log_data = new_resolver; + new_resolver->flags = flags; + + return new_resolver; +} + +void +rdns_resolver_async_bind (struct rdns_resolver *resolver, + struct rdns_async_context *ctx) +{ + if (resolver != NULL && ctx != NULL) { + resolver->async = ctx; + resolver->async_binded = true; + } +} + +void +rdns_resolver_set_dnssec (struct rdns_resolver *resolver, bool enabled) +{ + if (resolver) { + resolver->enable_dnssec = enabled; + } +} + + +void rdns_resolver_set_fake_reply (struct rdns_resolver *resolver, + const char *name, + enum rdns_request_type type, + enum dns_rcode rcode, + struct rdns_reply_entry *reply) +{ + struct rdns_fake_reply *fake_rep; + struct rdns_fake_reply_idx *srch; + unsigned len = strlen (name); + + assert (len < MAX_FAKE_NAME); + srch = malloc (sizeof (*srch) + len); + srch->len = len; + srch->type = type; + memcpy (srch->request, name, len); + + HASH_FIND (hh, resolver->fake_elts, srch, len + sizeof (*srch), fake_rep); + + if (fake_rep) { + /* Append reply to the existing list */ + fake_rep->rcode = rcode; + + if (reply) { + DL_CONCAT (fake_rep->result, reply); + } + } + else { + fake_rep = calloc (1, sizeof (*fake_rep) + len); + + if (fake_rep == NULL) { + abort (); + } + + fake_rep->rcode = rcode; + + memcpy (&fake_rep->key, srch, sizeof (*srch) + len); + + if (reply) { + DL_CONCAT (fake_rep->result, reply); + } + + HASH_ADD (hh, resolver->fake_elts, key, sizeof (*srch) + len, fake_rep); + } + + free (srch); +} diff --git a/contrib/librdns/upstream.h b/contrib/librdns/upstream.h new file mode 100644 index 0000000..a384155 --- /dev/null +++ b/contrib/librdns/upstream.h @@ -0,0 +1,282 @@ +/* + * Copyright 2023 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * 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 BY AUTHOR ''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. + */ + +#ifndef UPSTREAM_H_ +#define UPSTREAM_H_ + +#include <time.h> +#include <stdio.h> + +/** + * @file upstream.h + * The basic macros to define upstream objects + */ + +#ifndef upstream_fatal +#define upstream_fatal(msg) \ + do { \ + perror(msg); \ + exit(-1); \ + } while (0) +#endif + +#ifndef upstream_malloc +#define upstream_malloc(size) malloc(size) +#endif + +#ifndef upstream_free +#define upstream_free(size, ptr) free(ptr) +#endif + +struct upstream_entry_s; +struct upstream_common_data { + void **upstreams; + unsigned int allocated_nelts; + unsigned int nelts; + unsigned int alive; +}; + +typedef struct upstream_entry_s { + unsigned short errors; /**< errors for this upstream */ + unsigned short dead; + unsigned short priority; + unsigned short weight; + time_t time; /**< time of marking */ + void *parent; /**< parent object */ + struct upstream_common_data *common; /**< common data */ + void *next; /**< link to the next */ +} upstream_entry_t; + +/* + * Here we define some reasonable defaults: + * if an upstream has more than `UPSTREAM_MAX_ERRORS` in the period of time + * of `UPSTREAM_ERROR_TIME` then we shut it down for `UPSTREAM_REVIVE_TIME`. + * In this particular case times are 10 seconds for 10 errors and revive in + * 30 seconds. + */ +#ifndef UPSTREAM_REVIVE_TIME +#define UPSTREAM_REVIVE_TIME 30 +#endif +#ifndef UPSTREAM_ERROR_TIME +#define UPSTREAM_ERROR_TIME 10 +#endif +#ifndef UPSTREAM_MAX_ERRORS +#define UPSTREAM_MAX_ERRORS 10 +#endif + +#define UPSTREAM_FAIL(u, now) \ + do { \ + if ((u)->up.time != 0) { \ + if ((now) - (u)->up.time >= UPSTREAM_ERROR_TIME) { \ + if ((u)->up.errors >= UPSTREAM_MAX_ERRORS) { \ + (u)->up.dead = 1; \ + (u)->up.time = now; \ + (u)->up.common->alive--; \ + } \ + else { \ + (u)->up.errors = 1; \ + (u)->up.time = (now); \ + } \ + } \ + else { \ + (u)->up.errors++; \ + } \ + } \ + else { \ + (u)->up.errors++; \ + (u)->up.time = (now); \ + } \ + } while (0) + +#define UPSTREAM_OK(u) \ + do { \ + (u)->up.errors = 0; \ + (u)->up.time = 0; \ + } while (0) + +#define UPSTREAM_ADD(head, u, priority) \ + do { \ + if (head == NULL) { \ + struct upstream_common_data *cd; \ + cd = upstream_malloc(sizeof(struct upstream_common_data)); \ + if (cd == NULL) { \ + upstream_fatal("malloc failed"); \ + } \ + cd->upstreams = upstream_malloc(sizeof(void *) * 8); \ + if (cd == NULL) { \ + upstream_fatal("malloc failed"); \ + } \ + cd->allocated_nelts = 8; \ + cd->nelts = 1; \ + cd->alive = 1; \ + cd->upstreams[0] = (u); \ + (u)->up.common = cd; \ + } \ + else { \ + struct upstream_common_data *cd = (head)->up.common; \ + (u)->up.common = cd; \ + if (cd->nelts == cd->allocated_nelts) { \ + void **nup; \ + nup = upstream_malloc(sizeof(void *) * cd->nelts * 2); \ + if (nup == NULL) { \ + upstream_fatal("malloc failed"); \ + } \ + memcpy(nup, cd->upstreams, cd->nelts * sizeof(void *)); \ + upstream_free(cd->nelts * sizeof(void *), cd->upstreams); \ + cd->upstreams = nup; \ + cd->allocated_nelts *= 2; \ + } \ + cd->upstreams[cd->nelts++] = (u); \ + cd->alive++; \ + } \ + (u)->up.next = (head); \ + (head) = (u); \ + if (priority > 0) { \ + (u)->up.priority = (u)->up.weight = (priority); \ + } \ + else { \ + (u)->up.priority = (u)->up.weight = 65535; \ + } \ + (u)->up.time = 0; \ + (u)->up.errors = 0; \ + (u)->up.dead = 0; \ + (u)->up.parent = (u); \ + } while (0) + +#define UPSTREAM_DEL(head, u) \ + do { \ + if (head != NULL) { \ + struct upstream_common_data *cd = (head)->up.common; \ + if ((u)->up.next != NULL) { \ + (head) = (u)->up.next; \ + cd->nelts--; \ + cd->alive--; \ + } \ + else { \ + upstream_free(cd->allocated_nelts * sizeof(void *), \ + cd->upstreams); \ + upstream_free(sizeof(struct upstream_common_data), cd); \ + (head) = NULL; \ + } \ + } \ + } while (0) + +#define UPSTREAM_FOREACH(head, u) for ((u) = (head); (u) != NULL; (u) = (u)->up.next) +#define UPSTREAM_FOREACH_SAFE(head, u, tmp) \ + for ((u) = (head); \ + (u) != NULL && ((tmp = (u)->up.next) || true); \ + (u) = (tmp)) + +#define UPSTREAM_REVIVE_ALL(head) \ + do { \ + __typeof(head) elt = (head); \ + while (elt != NULL) { \ + elt->up.dead = 0; \ + elt->up.errors = 0; \ + elt->up.time = 0; \ + elt = elt->up.next; \ + } \ + (head)->up.common->alive = (head)->up.common->nelts; \ + } while (0) + +#define UPSTREAM_RESCAN(head, now) \ + do { \ + __typeof(head) elt = (head); \ + if ((head)->up.common->alive == 0) { \ + UPSTREAM_REVIVE_ALL((head)); \ + } \ + else { \ + while (elt != NULL) { \ + if (elt->up.dead) { \ + if ((now) -elt->up.time >= UPSTREAM_REVIVE_TIME) { \ + elt->up.dead = 0; \ + elt->up.errors = 0; \ + elt->up.weight = elt->up.priority; \ + (head)->up.common->alive++; \ + } \ + } \ + else { \ + if ((now) -elt->up.time >= UPSTREAM_ERROR_TIME && \ + elt->up.errors >= UPSTREAM_MAX_ERRORS) { \ + elt->up.dead = 1; \ + elt->up.time = now; \ + (head)->up.common->alive--; \ + } \ + } \ + elt = elt->up.next; \ + } \ + } \ + } while (0) + +#define UPSTREAM_SELECT_ROUND_ROBIN(head, selected) \ + do { \ + __typeof(head) elt = (head); \ + (selected) = NULL; \ + unsigned max_weight = 0; \ + if ((head)->up.common->alive == 0) { \ + UPSTREAM_REVIVE_ALL(head); \ + } \ + while (elt != NULL) { \ + if (!elt->up.dead) { \ + if (elt->up.weight > max_weight) { \ + max_weight = elt->up.weight; \ + (selected) = elt; \ + } \ + } \ + elt = elt->up.next; \ + } \ + if (max_weight == 0) { \ + elt = (head); \ + while (elt != NULL) { \ + elt->up.weight = elt->up.priority; \ + if (!elt->up.dead) { \ + if (elt->up.priority > max_weight) { \ + max_weight = elt->up.priority; \ + (selected) = elt; \ + } \ + } \ + elt = elt->up.next; \ + } \ + } \ + (selected)->up.weight--; \ + } while (0) + +#endif /* UPSTREAM_H_ */ diff --git a/contrib/librdns/util.c b/contrib/librdns/util.c new file mode 100644 index 0000000..0172ce0 --- /dev/null +++ b/contrib/librdns/util.c @@ -0,0 +1,1035 @@ +/* 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 <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <fcntl.h> +#include <ctype.h> + +#include "ottery.h" +#include "util.h" +#include "logger.h" +#include "rdns.h" + +inline void +rdns_request_remove_from_hash (struct rdns_request *req) +{ + /* Remove from id hashes */ + if (req->io) { + khiter_t k; + + k = kh_get(rdns_requests_hash, req->io->requests, req->id); + + if (k != kh_end(req->io->requests)) { + kh_del(rdns_requests_hash, req->io->requests, k); + } + } +} + +static int +rdns_make_socket_nonblocking (int fd) +{ + int ofl; + + ofl = fcntl (fd, F_GETFL, 0); + + if (fcntl (fd, F_SETFL, ofl | O_NONBLOCK) == -1) { + return -1; + } + return 0; +} + +static int +rdns_make_inet_socket (int type, struct addrinfo *addr, struct sockaddr **psockaddr, + socklen_t *psocklen) +{ + int fd = -1; + struct addrinfo *cur; + + cur = addr; + while (cur) { + /* Create socket */ + fd = socket (cur->ai_family, type, 0); + if (fd == -1) { + goto out; + } + + if (rdns_make_socket_nonblocking (fd) < 0) { + goto out; + } + + /* Set close on exec */ + if (fcntl (fd, F_SETFD, FD_CLOEXEC) == -1) { + goto out; + } + + if (psockaddr) { + *psockaddr = cur->ai_addr; + *psocklen = cur->ai_addrlen; + } + break; +out: + if (fd != -1) { + close (fd); + } + fd = -1; + cur = cur->ai_next; + } + + return (fd); +} + +static int +rdns_make_unix_socket (const char *path, struct sockaddr_un *addr, int type) +{ + int fd = -1, serrno; + + if (path == NULL) { + return -1; + } + + addr->sun_family = AF_UNIX; + + memset (addr->sun_path, 0, sizeof (addr->sun_path)); + memccpy (addr->sun_path, path, 0, sizeof (addr->sun_path) - 1); +#ifdef FREEBSD + addr->sun_len = SUN_LEN (addr); +#endif + + fd = socket (PF_LOCAL, type, 0); + + if (fd == -1) { + return -1; + } + + if (rdns_make_socket_nonblocking (fd) < 0) { + goto out; + } + + /* Set close on exec */ + if (fcntl (fd, F_SETFD, FD_CLOEXEC) == -1) { + goto out; + } + + return (fd); + + out: + serrno = errno; + if (fd != -1) { + close (fd); + } + errno = serrno; + return (-1); +} + +/** + * Make a universal socket + * @param credits host, ip or path to unix socket + * @param port port (used for network sockets) + * @param async make this socket asynced + * @param is_server make this socket as server socket + * @param try_resolve try name resolution for a socket (BLOCKING) + */ +int +rdns_make_client_socket (const char *credits, + uint16_t port, + int type, + struct sockaddr **psockaddr, + socklen_t *psocklen) +{ + struct sockaddr_un un; + struct stat st; + struct addrinfo hints, *res; + int r; + char portbuf[8]; + + if (*credits == '/') { + r = stat (credits, &st); + if (r == -1) { + /* Unix socket doesn't exists it must be created first */ + errno = ENOENT; + return -1; + } + else { + if ((st.st_mode & S_IFSOCK) == 0) { + /* Path is not valid socket */ + errno = EINVAL; + return -1; + } + else { + r = rdns_make_unix_socket (credits, &un, type); + + if (r != -1 && psockaddr) { + struct sockaddr *cpy; + + cpy = calloc (1, sizeof (un)); + *psocklen = sizeof (un); + + if (cpy == NULL) { + close (r); + + return -1; + } + + memcpy (cpy, &un, *psocklen); + *psockaddr = cpy; + } + + return r; + } + } + } + else { + /* TCP related part */ + memset (&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = type; /* Type of the socket */ + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV; + + snprintf (portbuf, sizeof (portbuf), "%d", (int)port); + if (getaddrinfo (credits, portbuf, &hints, &res) == 0) { + r = rdns_make_inet_socket (type, res, psockaddr, psocklen); + + if (r != -1 && psockaddr) { + struct sockaddr *cpy; + + cpy = calloc (1, *psocklen); + + if (cpy == NULL) { + close (r); + freeaddrinfo (res); + + return -1; + } + + memcpy (cpy, *psockaddr, *psocklen); + *psockaddr = cpy; + } + + freeaddrinfo (res); + return r; + } + else { + return -1; + } + } + + /* Not reached */ + return -1; +} + +const char * +rdns_strerror (enum dns_rcode rcode) +{ + rcode &= 0xf; + static char numbuf[16]; + + if ('\0' == dns_rcodes[rcode][0]) { + snprintf (numbuf, sizeof (numbuf), "UNKNOWN: %d", (int)rcode); + return numbuf; + } + return dns_rcodes[rcode]; +} + +const char * +rdns_strtype (enum rdns_request_type type) +{ + return dns_types[type]; +} + +enum rdns_request_type +rdns_type_fromstr (const char *str) +{ + if (str) { + if (strcmp (str, "a") == 0) { + return RDNS_REQUEST_A; + } + else if (strcmp (str, "ns") == 0) { + return RDNS_REQUEST_NS; + } + else if (strcmp (str, "soa") == 0) { + return RDNS_REQUEST_SOA; + } + else if (strcmp (str, "ptr") == 0) { + return RDNS_REQUEST_PTR; + } + else if (strcmp (str, "mx") == 0) { + return RDNS_REQUEST_MX; + } + else if (strcmp (str, "srv") == 0) { + return RDNS_REQUEST_SRV; + } + else if (strcmp (str, "txt") == 0) { + return RDNS_REQUEST_TXT; + } + else if (strcmp (str, "spf") == 0) { + return RDNS_REQUEST_SPF; + } + else if (strcmp (str, "aaaa") == 0) { + return RDNS_REQUEST_AAAA; + } + else if (strcmp (str, "tlsa") == 0) { + return RDNS_REQUEST_TLSA; + } + else if (strcmp (str, "cname") == 0) { + return RDNS_REQUEST_CNAME; + } + else if (strcmp (str, "any") == 0) { + return RDNS_REQUEST_ANY; + } + } + + return RDNS_REQUEST_INVALID; +} + +const char * +rdns_str_from_type (enum rdns_request_type rcode) +{ + switch (rcode) { + case RDNS_REQUEST_INVALID: + return "(invalid)"; + case RDNS_REQUEST_A: + return "a"; + case RDNS_REQUEST_NS: + return "ns"; + case RDNS_REQUEST_SOA: + return "soa"; + case RDNS_REQUEST_PTR: + return "ptr"; + case RDNS_REQUEST_MX: + return "mx"; + case RDNS_REQUEST_TXT: + return "txt"; + case RDNS_REQUEST_SRV: + return "srv"; + case RDNS_REQUEST_SPF: + return "spf"; + case RDNS_REQUEST_AAAA: + return "aaaa"; + case RDNS_REQUEST_TLSA: + return "tlsa"; + case RDNS_REQUEST_CNAME: + return "cname"; + case RDNS_REQUEST_ANY: + return "any"; + default: + return "(unknown)"; + } + +} + +enum dns_rcode +rdns_rcode_fromstr (const char *str) +{ + if (str) { + if (strcmp (str, "noerror") == 0) { + return RDNS_RC_NOERROR; + } + else if (strcmp (str, "formerr") == 0) { + return RDNS_RC_FORMERR; + } + else if (strcmp (str, "servfail") == 0) { + return RDNS_RC_SERVFAIL; + } + else if (strcmp (str, "nxdomain") == 0) { + return RDNS_RC_NXDOMAIN; + } + else if (strcmp (str, "notimp") == 0) { + return RDNS_RC_NOTIMP; + } + else if (strcmp (str, "yxdomain") == 0) { + return RDNS_RC_YXDOMAIN; + } + else if (strcmp (str, "yxrrset") == 0) { + return RDNS_RC_YXRRSET; + } + else if (strcmp (str, "nxrrset") == 0) { + return RDNS_RC_NXRRSET; + } + else if (strcmp (str, "notauth") == 0) { + return RDNS_RC_NOTAUTH; + } + else if (strcmp (str, "notzone") == 0) { + return RDNS_RC_NOTZONE; + } + else if (strcmp (str, "timeout") == 0) { + return RDNS_RC_TIMEOUT; + } + else if (strcmp (str, "neterr") == 0) { + return RDNS_RC_NETERR; + } + else if (strcmp (str, "norec") == 0) { + return RDNS_RC_NOREC; + } + } + + return RDNS_RC_INVALID; +} + +uint16_t +rdns_permutor_generate_id (void) +{ + uint16_t id; + + id = ottery_rand_unsigned (); + + return id; +} + +struct rdns_reply * +rdns_make_reply (struct rdns_request *req, enum dns_rcode rcode) +{ + struct rdns_reply *rep; + + rep = malloc (sizeof (struct rdns_reply)); + if (rep != NULL) { + rep->request = req; + rep->resolver = req->resolver; + rep->entries = NULL; + rep->code = rcode; + req->reply = rep; + rep->flags = 0; + rep->requested_name = req->requested_names[0].name; + } + + return rep; +} + +void +rdns_reply_free (struct rdns_reply *rep) +{ + struct rdns_reply_entry *entry, *tmp; + + /* We don't need to free data for faked replies */ + if (!rep->request || rep->request->state != RDNS_REQUEST_FAKE) { + LL_FOREACH_SAFE (rep->entries, entry, tmp) { + switch (entry->type) { + case RDNS_REQUEST_PTR: + free (entry->content.ptr.name); + break; + case RDNS_REQUEST_NS: + free (entry->content.ns.name); + break; + case RDNS_REQUEST_MX: + free (entry->content.mx.name); + break; + case RDNS_REQUEST_TXT: + case RDNS_REQUEST_SPF: + free (entry->content.txt.data); + break; + case RDNS_REQUEST_SRV: + free (entry->content.srv.target); + break; + case RDNS_REQUEST_TLSA: + free (entry->content.tlsa.data); + break; + case RDNS_REQUEST_SOA: + free (entry->content.soa.mname); + free (entry->content.soa.admin); + break; + case RDNS_REQUEST_CNAME: + free(entry->content.cname.name); + break; + default: + break; + } + free (entry); + } + } + + free (rep); +} + +void +rdns_request_free (struct rdns_request *req) +{ + unsigned int i; + + if (req != NULL) { + if (req->packet != NULL) { + free (req->packet); + } + for (i = 0; i < req->qcount; i ++) { + free (req->requested_names[i].name); + } + if (req->requested_names != NULL) { + free (req->requested_names); + } + if (req->reply != NULL) { + rdns_reply_free (req->reply); + } + if (req->async_event) { + if (req->state == RDNS_REQUEST_WAIT_REPLY) { + /* Remove timer */ + req->async->del_timer (req->async->data, + req->async_event); + rdns_request_remove_from_hash(req); + req->async_event = NULL; + } + else if (req->state == RDNS_REQUEST_WAIT_SEND) { + /* Remove retransmit event */ + req->async->del_write (req->async->data, + req->async_event); + rdns_request_remove_from_hash(req); + req->async_event = NULL; + } + else if (req->state == RDNS_REQUEST_FAKE) { + req->async->del_write (req->async->data, + req->async_event); + req->async_event = NULL; + } + } + if (req->state == RDNS_REQUEST_TCP) { + if (req->async_event) { + req->async->del_timer (req->async->data, + req->async_event); + } + + rdns_request_remove_from_hash(req); + } +#ifdef TWEETNACL + if (req->curve_plugin_data != NULL) { + req->resolver->curve_plugin->cb.curve_plugin.finish_cb ( + req, req->resolver->curve_plugin->data); + } +#endif + if (req->io != NULL && req->state > RDNS_REQUEST_NEW) { + REF_RELEASE (req->io); + REF_RELEASE (req->resolver); + } + + free (req); + } +} + +void +rdns_ioc_free (struct rdns_io_channel *ioc) +{ + struct rdns_request *req; + + if (IS_CHANNEL_TCP(ioc)) { + rdns_ioc_tcp_reset(ioc); + } + + kh_foreach_value(ioc->requests, req, { + REF_RELEASE (req); + }); + + if (ioc->async_io) { + ioc->resolver->async->del_read(ioc->resolver->async->data, + ioc->async_io); + } + kh_destroy(rdns_requests_hash, ioc->requests); + + if (ioc->sock != -1) { + close(ioc->sock); + } + + if (ioc->saddr != NULL) { + free(ioc->saddr); + } + + free (ioc); +} + +struct rdns_io_channel * +rdns_ioc_new (struct rdns_server *serv, + struct rdns_resolver *resolver, + bool is_tcp) +{ + struct rdns_io_channel *nioc; + + if (is_tcp) { + nioc = calloc (1, sizeof (struct rdns_io_channel) + + sizeof (struct rdns_tcp_channel)); + } + else { + nioc = calloc (1, sizeof (struct rdns_io_channel)); + } + + if (nioc == NULL) { + rdns_err ("calloc fails to allocate rdns_io_channel"); + return NULL; + } + + nioc->struct_magic = RDNS_IO_CHANNEL_TAG; + nioc->srv = serv; + nioc->resolver = resolver; + + nioc->sock = rdns_make_client_socket (serv->name, serv->port, + is_tcp ? SOCK_STREAM : SOCK_DGRAM, &nioc->saddr, &nioc->slen); + if (nioc->sock == -1) { + rdns_err ("cannot open socket to %s: %s", serv->name, + strerror (errno)); + free (nioc); + return NULL; + } + + if (is_tcp) { + /* We also need to connect a TCP channel and set a TCP buffer */ + nioc->tcp = (struct rdns_tcp_channel *)(((unsigned char *)nioc) + sizeof(*nioc)); + + if (!rdns_ioc_tcp_connect(nioc)) { + rdns_err ("cannot connect TCP socket to %s: %s", serv->name, + strerror (errno)); + close (nioc->sock); + free (nioc); + + return NULL; + } + + nioc->flags |= RDNS_CHANNEL_TCP; + } + else { + nioc->flags |= RDNS_CHANNEL_ACTIVE; + nioc->async_io = resolver->async->add_read(resolver->async->data, + nioc->sock, nioc); + } + + nioc->requests = kh_init(rdns_requests_hash); + REF_INIT_RETAIN (nioc, rdns_ioc_free); + + return nioc; +} + +void +rdns_resolver_release (struct rdns_resolver *resolver) +{ + REF_RELEASE (resolver); +} + +struct rdns_request* +rdns_request_retain (struct rdns_request *req) +{ + REF_RETAIN (req); + return req; +} + +void +rdns_request_unschedule (struct rdns_request *req, bool remove_from_hash) +{ + struct rdns_resolver *resolver = req->resolver; + + switch (req->state) { + case RDNS_REQUEST_WAIT_REPLY: + /* We have a timer pending */ + if (req->async_event) { + req->async->del_timer (req->async->data, + req->async_event); + if (remove_from_hash) { + rdns_request_remove_from_hash(req); + } + req->async_event = NULL; + } + break; + case RDNS_REQUEST_WAIT_SEND: + /* We have write request pending */ + if (req->async_event) { + req->async->del_write (req->async->data, + req->async_event); + /* Remove from id hashes */ + if (remove_from_hash) { + rdns_request_remove_from_hash(req); + } + req->async_event = NULL; + } + break; + case RDNS_REQUEST_TCP: + /* We also have a timer */ + if (req->async_event) { + if (remove_from_hash) { + rdns_request_remove_from_hash(req); + } + + req->async->del_timer(req->async->data, + req->async_event); + + req->async_event = NULL; + } + default: + /* Nothing to unschedule, so blame if we have any event pending */ + if (req->async_event) { + rdns_err("internal error: have unexpected pending async state on stage %d", + req->state); + } + break; + } +} + +void +rdns_request_release (struct rdns_request *req) +{ + rdns_request_unschedule (req, true); + REF_RELEASE (req); +} + +void +rdns_ioc_tcp_reset (struct rdns_io_channel *ioc) +{ + struct rdns_resolver *resolver = ioc->resolver; + + if (IS_CHANNEL_CONNECTED(ioc)) { + if (ioc->tcp->async_write) { + resolver->async->del_write (resolver->async->data, ioc->tcp->async_write); + ioc->tcp->async_write = NULL; + } + if (ioc->tcp->async_read) { + resolver->async->del_read (resolver->async->data, ioc->tcp->async_read); + ioc->tcp->async_read = NULL; + } + + /* Clean all buffers and temporaries */ + if (ioc->tcp->cur_read_buf) { + free (ioc->tcp->cur_read_buf); + ioc->tcp->read_buf_allocated = 0; + ioc->tcp->next_read_size = 0; + ioc->tcp->cur_read = 0; + ioc->tcp->cur_read_buf = NULL; + } + + struct rdns_tcp_output_chain *oc, *tmp; + DL_FOREACH_SAFE(ioc->tcp->output_chain, oc, tmp) { + DL_DELETE (ioc->tcp->output_chain, oc); + free (oc); + } + + ioc->tcp->cur_output_chains = 0; + ioc->tcp->output_chain = NULL; + + ioc->flags &= ~RDNS_CHANNEL_CONNECTED; + } + + /* Remove all requests pending as we are unable to complete them */ + struct rdns_request *req; + kh_foreach_value(ioc->requests, req, { + struct rdns_reply *rep = rdns_make_reply (req, RDNS_RC_NETERR); + /* + * Unschedule request explicitly as we set state to RDNS_REQUEST_REPLIED + * that will prevent timer from being removed on req dtor. + * + * We skip hash removal here, as the hash will be cleared as a single + * operation afterwards. + */ + rdns_request_unschedule(req, false); + req->state = RDNS_REQUEST_REPLIED; + req->func (rep, req->arg); + REF_RELEASE (req); + }); + + if (ioc->sock != -1) { + close (ioc->sock); + ioc->sock = -1; + } + if (ioc->saddr) { + free (ioc->saddr); + ioc->saddr = NULL; + } + + kh_clear(rdns_requests_hash, ioc->requests); +} + +bool +rdns_ioc_tcp_connect (struct rdns_io_channel *ioc) +{ + struct rdns_resolver *resolver = ioc->resolver; + + if (IS_CHANNEL_CONNECTED(ioc)) { + rdns_err ("trying to connect already connected IO channel!"); + return false; + } + + if (ioc->flags & RDNS_CHANNEL_TCP_CONNECTING) { + /* Already connecting channel, ignore connect request */ + + return true; + } + + if (ioc->sock == -1) { + ioc->sock = rdns_make_client_socket (ioc->srv->name, ioc->srv->port, + SOCK_STREAM, &ioc->saddr, &ioc->slen); + if (ioc->sock == -1) { + rdns_err ("cannot open socket to %s: %s", ioc->srv->name, + strerror (errno)); + + if (ioc->saddr) { + free (ioc->saddr); + ioc->saddr = NULL; + } + + return false; + } + } + + int r = connect (ioc->sock, ioc->saddr, ioc->slen); + + if (r == -1) { + if (errno != EAGAIN && errno != EINTR && errno != EINPROGRESS) { + rdns_err ("cannot connect a TCP socket: %s for server %s", + strerror(errno), ioc->srv->name); + close (ioc->sock); + + if (ioc->saddr) { + free (ioc->saddr); + ioc->saddr = NULL; + } + + ioc->sock = -1; + + return false; + } + else { + /* We need to wait for write readiness here */ + if (ioc->tcp->async_write != NULL) { + rdns_err("internal rdns error: write event is already registered on connect"); + } + else { + ioc->tcp->async_write = resolver->async->add_write(resolver->async->data, + ioc->sock, ioc); + } + /* Prevent double connect attempts */ + ioc->flags |= RDNS_CHANNEL_TCP_CONNECTING; + } + } + else { + /* Always be ready to read from a TCP socket */ + ioc->flags |= RDNS_CHANNEL_CONNECTED|RDNS_CHANNEL_ACTIVE; + ioc->flags &= ~RDNS_CHANNEL_TCP_CONNECTING; + ioc->tcp->async_read = resolver->async->add_read(resolver->async->data, + ioc->sock, ioc); + } + + return true; +} + +static bool +rdns_resolver_conf_process_line (struct rdns_resolver *resolver, + const char *line, rdns_resolv_conf_cb cb, void *ud) +{ + const char *p, *c, *end; + bool has_obrace = false, ret; + unsigned int port = dns_port; + char *cpy_buf; + + end = line + strlen (line); + + if (end - line > sizeof ("nameserver") - 1 && + strncmp (line, "nameserver", sizeof ("nameserver") - 1) == 0) { + p = line + sizeof ("nameserver") - 1; + /* Skip spaces */ + while (isspace (*p)) { + p ++; + } + + if (*p == '[') { + has_obrace = true; + p ++; + } + + if (isxdigit (*p) || *p == ':') { + c = p; + while (isxdigit (*p) || *p == ':' || *p == '.') { + p ++; + } + if (has_obrace && *p != ']') { + return false; + } + else if (*p != '\0' && !isspace (*p) && *p != '#') { + return false; + } + + if (has_obrace) { + p ++; + if (*p == ':') { + /* Maybe we have a port definition */ + port = strtoul (p + 1, NULL, 10); + if (port == 0 || port > UINT16_MAX) { + return false; + } + } + } + + cpy_buf = malloc (p - c + 1); + assert (cpy_buf != NULL); + memcpy (cpy_buf, c, p - c); + cpy_buf[p - c] = '\0'; + + if (cb == NULL) { + ret = rdns_resolver_add_server (resolver, cpy_buf, port, 0, + default_io_cnt) != NULL; + } + else { + ret = cb (resolver, cpy_buf, port, 0, + default_io_cnt, ud); + } + + free (cpy_buf); + + return ret; + } + else { + return false; + } + } + /* XXX: skip unknown resolv.conf lines */ + + return false; +} + +bool +rdns_resolver_parse_resolv_conf_cb (struct rdns_resolver *resolver, + const char *path, rdns_resolv_conf_cb cb, void *ud) +{ + FILE *in; + char buf[BUFSIZ]; + char *p; + bool processed = false; + + in = fopen (path, "r"); + + if (in == NULL) { + return false; + } + + while (!feof (in)) { + if (fgets (buf, sizeof (buf) - 1, in) == NULL) { + break; + } + + /* Strip trailing spaces */ + p = buf + strlen (buf) - 1; + while (p > buf && + (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) { + *p-- = '\0'; + } + + if (rdns_resolver_conf_process_line (resolver, buf, cb, ud)) { + processed = true; + } + } + + fclose (in); + + return processed; +} + +bool +rdns_resolver_parse_resolv_conf (struct rdns_resolver *resolver, const char *path) +{ + return rdns_resolver_parse_resolv_conf_cb (resolver, path, NULL, NULL); +} + +bool +rdns_request_has_type (struct rdns_request *req, enum rdns_request_type type) +{ + unsigned int i; + + for (i = 0; i < req->qcount; i ++) { + if (req->requested_names[i].type == type) { + return true; + } + } + + return false; +} + +const struct rdns_request_name * +rdns_request_get_name (struct rdns_request *req, unsigned int *count) +{ + + if (count != NULL) { + *count = req->qcount; + } + return req->requested_names; +} + +const char* +rdns_request_get_server (struct rdns_request *req) +{ + if (req && req->io) { + return req->io->srv->name; + } + + return NULL; +} + +char * +rdns_generate_ptr_from_str (const char *str) +{ + union { + struct in_addr v4; + struct in6_addr v6; + } addr; + char *res = NULL; + unsigned char *bytes; + size_t len; + + if (inet_pton (AF_INET, str, &addr.v4) == 1) { + bytes = (unsigned char *)&addr.v4; + + len = 4 * 4 + sizeof ("in-addr.arpa"); + res = malloc (len); + if (res) { + snprintf (res, len, "%u.%u.%u.%u.in-addr.arpa", + (unsigned)bytes[3]&0xFF, + (unsigned)bytes[2]&0xFF, + (unsigned)bytes[1]&0xFF, + (unsigned)bytes[0]&0xFF); + } + } + else if (inet_pton (AF_INET6, str, &addr.v6) == 1) { + bytes = (unsigned char *)&addr.v6; + + len = 2*32 + sizeof ("ip6.arpa"); + res = malloc (len); + if (res) { + snprintf(res, len, + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." + "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.ip6.arpa", + bytes[15]&0xF, bytes[15] >> 4, bytes[14]&0xF, bytes[14] >> 4, + bytes[13]&0xF, bytes[13] >> 4, bytes[12]&0xF, bytes[12] >> 4, + bytes[11]&0xF, bytes[11] >> 4, bytes[10]&0xF, bytes[10] >> 4, + bytes[9]&0xF, bytes[9] >> 4, bytes[8]&0xF, bytes[8] >> 4, + bytes[7]&0xF, bytes[7] >> 4, bytes[6]&0xF, bytes[6] >> 4, + bytes[5]&0xF, bytes[5] >> 4, bytes[4]&0xF, bytes[4] >> 4, + bytes[3]&0xF, bytes[3] >> 4, bytes[2]&0xF, bytes[2] >> 4, + bytes[1]&0xF, bytes[1] >> 4, bytes[0]&0xF, bytes[0] >> 4); + } + } + + return res; +} diff --git a/contrib/librdns/util.h b/contrib/librdns/util.h new file mode 100644 index 0000000..6f74d8b --- /dev/null +++ b/contrib/librdns/util.h @@ -0,0 +1,99 @@ +/* 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. + */ +#ifndef UTIL_H_ +#define UTIL_H_ + +#include "dns_private.h" + +/** + * Make a universal socket + * @param credits host, ip or path to unix socket + * @param port port (used for network sockets) + * @param type of socket (SOCK_STREAM or SOCK_DGRAM) + */ +int +rdns_make_client_socket (const char *credits, + uint16_t port, + int type, + struct sockaddr **psockaddr, + socklen_t *psocklen); + +/** + * Generate new random DNS id + * @return dns id + */ +uint16_t rdns_permutor_generate_id (void); + + +/** + * Free IO channel + */ +void rdns_ioc_free (struct rdns_io_channel *ioc); + +/** + * Creates a new IO channel + */ +struct rdns_io_channel * rdns_ioc_new (struct rdns_server *srv, + struct rdns_resolver *resolver, + bool is_tcp); + +/** + * Resets inactive/errored TCP chain as recommended by RFC + * @param ioc + */ +void rdns_ioc_tcp_reset (struct rdns_io_channel *ioc); + +/** + * Connect TCP IO channel to a server + * @param ioc + */ +bool rdns_ioc_tcp_connect (struct rdns_io_channel *ioc); + +/** + * Free request + * @param req + */ +void rdns_request_free (struct rdns_request *req); + +/** + * Removes request from a channel's hash (e.g. if needed to migrate to another channel) + * @param req + */ +void rdns_request_remove_from_hash (struct rdns_request *req); + +/** + * Creates a new reply + * @param req + * @param rcode + * @return + */ +struct rdns_reply * rdns_make_reply (struct rdns_request *req, enum dns_rcode rcode); +/** + * Free reply + * @param rep + */ +void rdns_reply_free (struct rdns_reply *rep); + +void rdns_request_unschedule (struct rdns_request *req, bool remove_from_hash); + +#endif /* UTIL_H_ */ |