diff options
author | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2015-02-21 18:11:12 +0000 |
---|---|---|
committer | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2015-02-21 18:11:12 +0000 |
commit | 6e121a026ffd681418cc8e43647afacdd13f149a (patch) | |
tree | dc02aa9d0c2a46afc53886f498844bfaa958124d /contrib/librdns | |
parent | f9d2d85d1473ae53c13c99747ef809649f5137bf (diff) | |
download | rspamd-6e121a026ffd681418cc8e43647afacdd13f149a.tar.gz rspamd-6e121a026ffd681418cc8e43647afacdd13f149a.zip |
Move ucl and rdns to contrib.
Diffstat (limited to 'contrib/librdns')
-rw-r--r-- | contrib/librdns/CMakeLists.txt | 10 | ||||
-rw-r--r-- | contrib/librdns/compression.c | 154 | ||||
-rw-r--r-- | contrib/librdns/compression.h | 45 | ||||
-rw-r--r-- | contrib/librdns/curve.c | 472 | ||||
-rw-r--r-- | contrib/librdns/dns_private.h | 262 | ||||
-rw-r--r-- | contrib/librdns/logger.c | 53 | ||||
-rw-r--r-- | contrib/librdns/logger.h | 42 | ||||
-rw-r--r-- | contrib/librdns/packet.c | 271 | ||||
-rw-r--r-- | contrib/librdns/packet.h | 71 | ||||
-rw-r--r-- | contrib/librdns/parse.c | 419 | ||||
-rw-r--r-- | contrib/librdns/parse.h | 65 | ||||
-rw-r--r-- | contrib/librdns/punycode.c | 297 | ||||
-rw-r--r-- | contrib/librdns/punycode.h | 59 | ||||
-rw-r--r-- | contrib/librdns/rdns.h | 372 | ||||
-rw-r--r-- | contrib/librdns/rdns_curve.h | 75 | ||||
-rw-r--r-- | contrib/librdns/rdns_ev.h | 232 | ||||
-rw-r--r-- | contrib/librdns/rdns_event.h | 231 | ||||
-rw-r--r-- | contrib/librdns/ref.h | 71 | ||||
-rw-r--r-- | contrib/librdns/resolver.c | 782 | ||||
-rw-r--r-- | contrib/librdns/upstream.h | 257 | ||||
-rw-r--r-- | contrib/librdns/util.c | 523 | ||||
-rw-r--r-- | contrib/librdns/util.h | 62 |
22 files changed, 4825 insertions, 0 deletions
diff --git a/contrib/librdns/CMakeLists.txt b/contrib/librdns/CMakeLists.txt new file mode 100644 index 000000000..f2cd9a3e1 --- /dev/null +++ b/contrib/librdns/CMakeLists.txt @@ -0,0 +1,10 @@ +SET(LIBRDNSSRC util.c + logger.c + compression.c + punycode.c + curve.c + parse.c + packet.c + resolver.c) + +ADD_LIBRARY(rdns STATIC ${LIBRDNSSRC})
\ No newline at end of file diff --git a/contrib/librdns/compression.c b/contrib/librdns/compression.c new file mode 100644 index 000000000..ac31a92c3 --- /dev/null +++ b/contrib/librdns/compression.c @@ -0,0 +1,154 @@ +/* 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" + +static struct rdns_compression_entry * +rdns_can_compress (const char *pos, struct rdns_compression_entry *comp) +{ + struct rdns_compression_entry *res; + + HASH_FIND_STR (comp, pos, res); + + return res; +} + +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, + struct rdns_compression_entry **comp, int offset) +{ + struct rdns_compression_entry *new; + + assert (offset >= 0); + new = malloc (sizeof (*new)); + if (new != NULL) { + new->label = pos; + new->offset = offset; + HASH_ADD_KEYPTR (hh, *comp, pos, (end - pos), new); + } +} + +void +rnds_compression_free (struct rdns_compression_entry *comp) +{ + struct rdns_compression_entry *cur, *tmp; + + if (comp) { + free (comp->hh.tbl->buckets); + free (comp->hh.tbl); + + HASH_ITER (hh, comp, cur, tmp) { + free (cur); + } + } +} + +bool +rdns_write_name_compressed (struct rdns_request *req, + const char *name, unsigned int namelen, + struct rdns_compression_entry **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_compression_entry *head = NULL, *test; + struct rdns_resolver *resolver = req->resolver; + uint16_t pointer; + + if (comp != NULL) { + head = *comp; + } + + while (pos < end && remain > 0) { + if (head != NULL) { + test = rdns_can_compress (pos, head); + if (test != NULL) { + 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 != NULL) { + 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 000000000..adae2f3ef --- /dev/null +++ b/contrib/librdns/compression.h @@ -0,0 +1,45 @@ +/* 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 "uthash.h" +#include "dns_private.h" + +struct rdns_compression_entry { + const char *label; /**< label packed */ + unsigned int offset; /**< offset in the packet */ + UT_hash_handle hh; /**< hash handle */ +}; + +/** + * 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, + struct rdns_compression_entry **comp); + +void rnds_compression_free (struct rdns_compression_entry *comp); + +#endif /* COMPRESSION_H_ */ diff --git a/contrib/librdns/curve.c b/contrib/librdns/curve.c new file mode 100644 index 000000000..4c2a64135 --- /dev/null +++ b/contrib/librdns/curve.c @@ -0,0 +1,472 @@ +/* + * 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 <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); +} + +#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 000000000..d23f8454d --- /dev/null +++ b/contrib/librdns/dns_private.h @@ -0,0 +1,262 @@ +/* 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 "uthash.h" +#include "utlist.h" +#include "rdns.h" +#include "upstream.h" +#include "ref.h" + +static const unsigned base = 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; + +static const int dns_port = 53; +static const int default_io_cnt = 8; + +#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" + +/** + * Represents DNS server + */ +struct rdns_server { + char *name; + unsigned int port; + unsigned int io_cnt; + + struct rdns_io_channel **io_channels; + upstream_entry_t up; +}; + +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_NEW = 0, + RDNS_REQUEST_REGISTERED = 1, + RDNS_REQUEST_SENT, + RDNS_REQUEST_REPLIED + } state; + + uint8_t *packet; + off_t pos; + unsigned int packet_len; + + dns_callback_type func; + void *arg; + + void *async_event; + +#ifdef TWEETNACL + void *curve_plugin_data; +#endif + + UT_hash_handle hh; + ref_entry_t ref; +}; + +/** + * IO channel for a specific DNS server + */ +struct rdns_io_channel { + struct rdns_server *srv; + struct rdns_resolver *resolver; + int sock; /**< persistent socket */ + bool active; + void *async_io; /** async opaque ptr */ + struct rdns_request *requests; /**< requests in flight */ + uint64_t uses; + ref_entry_t ref; +}; + + +struct rdns_resolver { + struct rdns_server *servers; + struct rdns_io_channel *io_channels; /**< hash of io chains indexed by socket */ + struct rdns_async_context *async; /** async callbacks */ + void *periodic; /** periodic event for resolver */ + + struct rdns_plugin *curve_plugin; + + 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; + ref_entry_t ref; +}; + +struct dns_header; +struct dns_query; + +/* Internal DNS structs */ + +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 unused:3; + 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 unused :3; + unsigned int ra :1; +#endif + + unsigned int qdcount :16; + unsigned int ancount :16; + unsigned int nscount :16; + unsigned int arcount :16; +}; + +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 000000000..c9ed2d926 --- /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 000000000..91108f4e3 --- /dev/null +++ b/contrib/librdns/logger.h @@ -0,0 +1,42 @@ +/* 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" + +void rdns_logger_internal (void *log_data, enum rdns_log_level level, + const char *function, const char *format, + va_list args); + +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, __FUNCTION__, __VA_ARGS__); } while (0) +#define rdns_warn(...) do { rdns_logger_helper (resolver, RDNS_LOG_WARNING, __FUNCTION__, __VA_ARGS__); } while (0) +#define rdns_info(...) do { rdns_logger_helper (resolver, RDNS_LOG_INFO, __FUNCTION__, __VA_ARGS__); } while (0) +#define rdns_debug(...) do { rdns_logger_helper (resolver, RDNS_LOG_DEBUG, __FUNCTION__, __VA_ARGS__); } while (0) + +#endif /* LOGGER_H_ */ diff --git a/contrib/librdns/packet.c b/contrib/librdns/packet.c new file mode 100644 index 000000000..88e51dfba --- /dev/null +++ b/contrib/librdns/packet.c @@ -0,0 +1,271 @@ +/* Copyright (c) 2014, Vsevolod Stakhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "rdns.h" +#include "dns_private.h" +#include "punycode.h" +#include "packet.h" +#include "util.h" +#include "logger.h" +#include "compression.h" + +void +rdns_allocate_packet (struct rdns_request* req, unsigned int namelen) +{ + namelen += 96 + 2 + 4 + 11; /* EDNS0 RR */ + req->packet = malloc (namelen); + req->pos = 0; + req->packet_len = namelen; +} + + +void +rdns_make_dns_header (struct rdns_request *req, unsigned int qcount) +{ + struct dns_header *header; + + /* Set DNS header values */ + header = (struct dns_header *)req->packet; + memset (header, 0 , sizeof (struct dns_header)); + header->qid = rdns_permutor_generate_id (); + header->rd = 1; + header->qdcount = htons (qcount); + header->arcount = htons (1); + req->pos += sizeof (struct dns_header); + req->id = header->qid; +} + +static bool +rdns_maybe_punycode_label (const uint8_t *begin, + uint8_t const **dot, size_t *label_len) +{ + bool ret = false; + const uint8_t *p = begin; + + *dot = NULL; + + while (*p) { + if (*p == '.') { + *dot = p; + break; + } + else if ((*p) & 0x80) { + ret = true; + } + p ++; + } + + if (*p) { + *label_len = p - begin; + } + else { + *label_len = p - begin; + } + + return ret; +} + +bool +rdns_format_dns_name (struct rdns_resolver *resolver, const char *in, + size_t inlen, + char **out, size_t *outlen) +{ + const uint8_t *dot; + const uint8_t *p = in, *end = in + inlen; + char *o; + int labels = 0; + size_t label_len, olen, remain; + uint32_t *uclabel; + size_t punylabel_len, uclabel_len; + char tmp_label[DNS_D_MAXLABEL]; + bool need_encode = false; + + if (inlen == 0) { + inlen = strlen (in); + } + + /* Check for non-ascii characters */ + while (p != end) { + if (*p >= 0x80) { + need_encode = true; + } + else if (*p == '.') { + labels ++; + } + p ++; + } + + if (!need_encode) { + *out = malloc (inlen + 1); + if (*out == NULL) { + return false; + } + o = *out; + memcpy (o, in, inlen); + o[inlen] = '\0'; + *outlen = inlen; + + return true; + } + + /* We need to encode */ + + p = in; + olen = inlen + 1 + sizeof ("xn--") * labels; + *out = malloc (olen); + + 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"); + free (*out); + return false; + } + + free (uclabel); + + if (dot) { + p = dot + 1; + } + else { + break; + } + } + else { + break; + } + } + else { + if (dot) { + if (label_len > DNS_D_MAXLABEL) { + rdns_info ("dns name component is longer than 63 bytes, should be stripped"); + label_len = DNS_D_MAXLABEL; + } + if (remain < label_len + 1) { + rdns_info ("no buffer remain for punycoding query"); + return false; + } + 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"); + return false; + } + 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"); + return false; + } + } + *o = '\0'; + + *outlen = o - *out; + + return true; +} + +bool +rdns_add_rr (struct rdns_request *req, const char *name, unsigned int len, + enum dns_type type, struct rdns_compression_entry **comp) +{ + uint16_t *p; + + if (!rdns_write_name_compressed (req, name, len, comp)) { + return false; + } + p = (uint16_t *)(req->packet + req->pos); + *p++ = htons (type); + *p = htons (DNS_C_IN); + req->pos += sizeof (uint16_t) * 2; + + return true; +} + +bool +rdns_add_edns0 (struct rdns_request *req) +{ + uint8_t *p8; + uint16_t *p16; + + p8 = (uint8_t *)(req->packet + req->pos); + *p8 = '\0'; /* Name is root */ + p16 = (uint16_t *)(req->packet + req->pos + 1); + *p16++ = htons (DNS_T_OPT); + /* UDP packet length */ + *p16++ = htons (UDP_PACKET_SIZE); + /* Extended rcode 00 00 */ + *p16++ = 0; + /* Z 10000000 00000000 to allow dnssec, disabled currently */ + *p16++ = 0; + /* Length */ + *p16 = 0; + 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 000000000..f4c09b900 --- /dev/null +++ b/contrib/librdns/packet.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 PACKET_H_ +#define PACKET_H_ + +#include <stdbool.h> +#include <stdint.h> +#include "dns_private.h" + +struct rdns_compression_entry; + +/** + * 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); + + +/** + * Format DNS name of the packet + * @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); + +/** + * 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 rdns_compression_entry **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 000000000..e9527eaa4 --- /dev/null +++ b/contrib/librdns/parse.c @@ -0,0 +1,419 @@ +/* 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); + 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", *remain); + 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"); + 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"); + 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"); + 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"); + 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 NS record"); + return -1; + } + if (! rdns_parse_labels (resolver, in, &elt->content.soa.admin, &p, + rep, remain, true)) { + rdns_info ("invalid labels in NS record"); + return -1; + } + 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); + 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"); + return -1; + } + parsed = true; + break; + case DNS_T_TXT: + case DNS_T_SPF: + elt->content.txt.data = malloc (datalen + 1); + if (elt->content.txt.data == NULL) { + rdns_err ("failed to allocate %d bytes for TXT record", (int)datalen + 1); + return -1; + } + /* Now we should compose data from parts */ + copied = 0; + parts = 0; + while (copied + parts < datalen) { + txtlen = *p; + if (txtlen + copied + parts <= datalen) { + parts ++; + memcpy (elt->content.txt.data + copied, p + 1, txtlen); + copied += txtlen; + p += txtlen + 1; + *remain -= txtlen + 1; + } + else { + break; + } + } + *(elt->content.txt.data + copied) = '\0'; + parsed = true; + elt->type = RDNS_REQUEST_TXT; + break; + case DNS_T_SRV: + if (p - *pos > (int)(*remain - sizeof (uint16_t) * 3)) { + rdns_info ("stripped dns reply while reading SRV record"); + 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"); + 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"); + 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", (int)datalen + 1); + 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: + /* Skip cname records */ + p += datalen; + *remain -= datalen; + break; + default: + rdns_debug ("unexpected RR type: %d", type); + 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 000000000..ed8cd7057 --- /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 000000000..6ce348495 --- /dev/null +++ b/contrib/librdns/punycode.c @@ -0,0 +1,297 @@ +/* + * 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" + +/* 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 > ((base - t_min) * t_max) / 2) { + delta /= base - t_min; + k += base; + } + return k + (((base - 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 = base;; k += base) { + 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) % (base - t))); + q = (q - t) / (base - 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 000000000..300fb92a6 --- /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 000000000..a1cfad4d3 --- /dev/null +++ b/contrib/librdns/rdns.h @@ -0,0 +1,372 @@ +/* + * 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_A = 1, + RDNS_REQUEST_NS = 2, + 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 rdns_reply_entry { + union rdns_reply_element_un content; + uint16_t type; + int32_t ttl; + struct rdns_reply_entry *prev, *next; +}; + + +enum dns_rcode { + 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 +}; + +struct rdns_reply { + struct rdns_request *request; + struct rdns_resolver *resolver; + struct rdns_reply_entry *entries; + const char *requested_name; + enum dns_rcode code; +}; + +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); +}; + +/** + * Type of rdns plugin + */ +enum rdns_plugin_type { + RDNS_PLUGIN_CURVE = 0//!< use the specified plugin instead of send/recv functions +}; + +typedef ssize_t (*rdns_network_send_callback) (struct rdns_request *req, void *plugin_data); +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); +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; +}; + +/* + * RDNS API + */ + +/** + * Create DNS resolver structure + */ +struct rdns_resolver *rdns_resolver_new (void); + +/** + * Bind resolver to specified async context + * @param ctx + */ +void rdns_resolver_async_bind (struct rdns_resolver *resolver, + struct rdns_async_context *ctx); + +/** + * 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 true if a server has been added to resolver + */ +bool 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); + +/** + * 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 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); + +/** + * 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); + +/** + * 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 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); + +/* + * 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_retransmit (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 000000000..365e91bb1 --- /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 000000000..1b3554bc1 --- /dev/null +++ b/contrib/librdns/rdns_ev.h @@ -0,0 +1,232 @@ +/* + * 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 <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_retransmit (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_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_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_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 000000000..b3fc64aed --- /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_retransmit (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 000000000..a8016b16d --- /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 000000000..9741f7bf9 --- /dev/null +++ b/contrib/librdns/resolver.c @@ -0,0 +1,782 @@ +/* + * 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 "rdns.h" +#include "dns_private.h" +#include "ottery.h" +#include "util.h" +#include "packet.h" +#include "parse.h" +#include "logger.h" +#include "compression.h" + +static int +rdns_send_request (struct rdns_request *req, int fd, bool new_req) +{ + int r; + struct rdns_server *serv = req->io->srv; + struct rdns_resolver *resolver = req->resolver; + struct rdns_request *tmp; + struct dns_header *header; + const int max_id_cycles = 32; + + /* Find ID collision */ + if (new_req) { + r = 0; + HASH_FIND_INT (req->io->requests, &req->id, tmp); + while (tmp != NULL) { + /* 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; + } + HASH_FIND_INT (req->io->requests, &req->id, tmp); + } + } + + if (resolver->curve_plugin == NULL) { + r = send (fd, req->packet, req->pos, 0); + } + else { + r = resolver->curve_plugin->cb.curve_plugin.send_cb (req, + resolver->curve_plugin->data); + } + if (r == -1) { + if (errno == EAGAIN || errno == EINTR) { + if (new_req) { + /* Write when socket is ready */ + HASH_ADD_INT (req->io->requests, id, req); + req->async_event = resolver->async->add_write (resolver->async->data, + fd, req); + } + /* + * 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; + } + } + + if (new_req) { + /* Add request to hash table */ + HASH_ADD_INT (req->io->requests, id, req); + /* Fill timeout */ + req->async_event = resolver->async->add_timer (resolver->async->data, + req->timeout, req); + req->state = RDNS_REQUEST_SENT; + } + + return 1; +} + + +static 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; + } + + return rep; +} + +static struct rdns_request * +rdns_find_dns_request (uint8_t *in, struct rdns_io_channel *ioc) +{ + struct dns_header *header = (struct dns_header *)in; + struct rdns_request *req; + int id; + struct rdns_resolver *resolver = ioc->resolver; + + id = header->qid; + HASH_FIND_INT (ioc->requests, &id, req); + if (req == NULL) { + /* No such requests found */ + rdns_debug ("DNS request with id %d has not been found for IO channel", (int)id); + } + + return req; +} + +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 (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 */ + rep->code = RDNS_RC_NOREC; + } + + *_rep = rep; + return true; +} + +static void +rdns_request_unschedule (struct rdns_request *req) +{ + req->async->del_timer (req->async->data, + req->async_event); + /* Remove from id hashes */ + HASH_DEL (req->io->requests, req); +} + +void +rdns_process_read (int fd, void *arg) +{ + struct rdns_io_channel *ioc = arg; + 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 = read (fd, in, sizeof (in)); + 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); + 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); + req->state = RDNS_REQUEST_REPLIED; + rdns_request_unschedule (req); + req->func (rep, req->arg); + REF_RELEASE (req); + } + } + else { + /* Still want to increase uses */ + ioc->uses ++; + } +} + +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; + + req->retransmits --; + resolver = req->resolver; + + if (req->retransmits == 0) { + UPSTREAM_FAIL (req->io->srv, time (NULL)); + rep = rdns_make_reply (req, RDNS_RC_TIMEOUT); + req->state = RDNS_REQUEST_REPLIED; + rdns_request_unschedule (req); + req->func (rep, req->arg); + REF_RELEASE (req); + + return; + } + + if (!req->io->active) { + /* Do not reschedule IO requests on inactive sockets */ + rdns_debug ("reschedule request with id: %d", (int)req->id); + rdns_request_unschedule (req); + REF_RELEASE (req->io); + + 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); + } + + /* Select random IO channel */ + req->io = serv->io_channels[ottery_rand_uint32 () % serv->io_cnt]; + req->io->uses ++; + REF_RETAIN (req->io); + renew = true; + } + + r = rdns_send_request (req, req->io->sock, renew); + if (r == 0) { + /* Retransmit one more time */ + 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_REGISTERED; + } + else if (r == -1) { + UPSTREAM_FAIL (req->io->srv, time (NULL)); + rep = rdns_make_reply (req, RDNS_RC_NETERR); + req->state = RDNS_REQUEST_REPLIED; + rdns_request_unschedule (req); + req->func (rep, req->arg); + REF_RELEASE (req); + } + else { + req->async->repeat_timer (req->async->data, req->async_event); + } +} + +static void +rdns_process_periodic (void *arg) +{ + struct rdns_resolver *resolver = (struct rdns_resolver*)arg; + + UPSTREAM_RESCAN (resolver->servers, time (NULL)); +} + +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 = calloc (1, sizeof (struct rdns_io_channel)); + if (nioc == NULL) { + rdns_err ("calloc fails to allocate rdns_io_channel"); + continue; + } + nioc->sock = rdns_make_client_socket (serv->name, serv->port, + SOCK_DGRAM); + if (nioc->sock == -1) { + rdns_err ("cannot open socket to %s: %s", serv->name, + strerror (errno)); + free (nioc); + continue; + } + nioc->srv = serv; + nioc->active = true; + nioc->resolver = resolver; + nioc->async_io = resolver->async->add_read (resolver->async->data, + nioc->sock, nioc); + REF_INIT_RETAIN (nioc, rdns_ioc_free); + 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->active = false; + REF_RELEASE (ioc); + } + } + } + } +} + +void +rdns_process_retransmit (int fd, void *arg) +{ + struct rdns_request *req = (struct rdns_request *)arg; + struct rdns_resolver *resolver; + struct rdns_reply *rep; + int r; + + resolver = req->resolver; + + resolver->async->del_write (resolver->async->data, + req->async_event); + + 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_REGISTERED; + } + else if (r == -1) { + 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_SENT; + } +} + +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; + struct rdns_compression_entry *comp = NULL; + + if (!resolver->initialized) { + return NULL; + } + + req = malloc (sizeof (struct rdns_request)); + if (req == NULL) { + 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)); + if (req->requested_names == NULL) { + free (req); + 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 *); + if (cur_name != NULL) { + last_name = cur_name; + clen = strlen (cur_name); + if (clen == 0) { + rdns_info ("got empty name to resolve"); + rdns_request_free (req); + return NULL; + } + tlen += clen; + } + else if (last_name == NULL) { + rdns_info ("got NULL as the first name to resolve"); + rdns_request_free (req); + return NULL; + } + + if (!rdns_format_dns_name (resolver, last_name, clen, + &req->requested_names[cur].name, &olen)) { + rdns_request_free (req); + return NULL; + } + + type = va_arg (args, int); + req->requested_names[cur].type = type; + req->requested_names[cur].len = olen; + } + va_end (args); + + 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)) { + REF_RELEASE (req); + rnds_compression_free (comp); + return NULL; + } + } + else { + if (!rdns_add_rr (req, cur_name, clen, type, NULL)) { + REF_RELEASE (req); + rnds_compression_free (comp); + return NULL; + } + } + } + + rnds_compression_free (comp); + + /* Add EDNS RR */ + rdns_add_edns0 (req); + + req->retransmits = repeats; + req->timeout = timeout; + req->state = RDNS_REQUEST_NEW; + req->async = resolver->async; + + UPSTREAM_SELECT_ROUND_ROBIN (resolver->servers, serv); + + 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]; + req->io->uses ++; + + /* Now send request to server */ + r = rdns_send_request (req, req->io->sock, true); + + if (r == -1) { + REF_RELEASE (req); + return NULL; + } + + 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) { + return false; + } + + if (resolver->servers == NULL) { + 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 *)); + for (i = 0; i < serv->io_cnt; i ++) { + ioc = calloc (1, sizeof (struct rdns_io_channel)); + if (ioc == NULL) { + rdns_err ("cannot allocate memory for the resolver"); + return false; + } + ioc->sock = rdns_make_client_socket (serv->name, serv->port, SOCK_DGRAM); + ioc->active = true; + if (ioc->sock == -1) { + rdns_err ("cannot open socket to %s:%d %s", serv->name, serv->port, strerror (errno)); + free (ioc); + return false; + } + else { + ioc->srv = serv; + ioc->resolver = resolver; + ioc->async_io = resolver->async->add_read (resolver->async->data, + ioc->sock, ioc); + REF_INIT_RETAIN (ioc, rdns_ioc_free); + serv->io_channels[i] = ioc; + } + } + } + + 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; + } + } +} + +bool +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 false; + } + + if (io_cnt == 0) { + return false; + } + if (port == 0 || port > UINT16_MAX) { + return false; + } + + serv = calloc (1, sizeof (struct rdns_server)); + if (serv == NULL) { + return false; + } + serv->name = strdup (name); + if (serv->name == NULL) { + free (serv); + return false; + } + + serv->io_cnt = io_cnt; + serv->port = port; + + UPSTREAM_ADD (resolver->servers, serv, priority); + + return true; +} + +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_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); + } + serv->io_cnt = 0; + UPSTREAM_DEL (resolver->servers, serv); + free (serv->io_channels); + free (serv->name); + free (serv); + } + } + free (resolver->async); + free (resolver); +} + + +struct rdns_resolver * +rdns_resolver_new (void) +{ + struct rdns_resolver *new; + + new = calloc (1, sizeof (struct rdns_resolver)); + + REF_INIT_RETAIN (new, rdns_resolver_free); + + new->logger = rdns_logger_internal; + new->log_data = new; + + return new; +} + +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; + } +} diff --git a/contrib/librdns/upstream.h b/contrib/librdns/upstream.h new file mode 100644 index 000000000..9646f89aa --- /dev/null +++ b/contrib/librdns/upstream.h @@ -0,0 +1,257 @@ +/* + * 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; \ + int alive = 0; \ + 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; \ + } \ + alive ++; \ + } \ + 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 000000000..7d9dc98d4 --- /dev/null +++ b/contrib/librdns/util.c @@ -0,0 +1,523 @@ +/* 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" + +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) +{ + int fd, r, s_error; + socklen_t optlen; + 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; + } + + r = connect (fd, cur->ai_addr, cur->ai_addrlen); + + if (r == -1) { + if (errno != EINPROGRESS) { + goto out; + } + } + else { + /* Still need to check SO_ERROR on socket */ + optlen = sizeof (s_error); + getsockopt (fd, SOL_SOCKET, SO_ERROR, (void *)&s_error, &optlen); + if (s_error) { + errno = s_error; + goto out; + } + } + 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, s_error, r, serrno; + socklen_t optlen; + + 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; + } + + r = connect (fd, (struct sockaddr *)addr, SUN_LEN (addr)); + + if (r == -1) { + if (errno != EINPROGRESS) { + goto out; + } + } + else { + /* Still need to check SO_ERROR on socket */ + optlen = sizeof (s_error); + getsockopt (fd, SOL_SOCKET, SO_ERROR, (void *)&s_error, &optlen); + if (s_error) { + errno = s_error; + 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_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 { + return rdns_make_unix_socket (credits, &un, type); + } + } + } + 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 ((r = getaddrinfo (credits, portbuf, &hints, &res)) == 0) { + r = rdns_make_inet_socket (type, res); + 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]; +} + +uint16_t +rdns_permutor_generate_id (void) +{ + uint16_t id; + + id = ottery_rand_unsigned (); + + return id; +} + + +void +rdns_reply_free (struct rdns_reply *rep) +{ + struct rdns_reply_entry *entry, *tmp; + + 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; + } + 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->state >= RDNS_REQUEST_SENT && + req->state < RDNS_REQUEST_REPLIED) { + /* Remove timer */ + req->async->del_timer (req->async->data, + req->async_event); + /* Remove from id hashes */ + HASH_DEL (req->io->requests, req); + } + else if (req->state == RDNS_REQUEST_REGISTERED) { + /* Remove retransmit event */ + req->async->del_write (req->async->data, + req->async_event); + } +#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, *rtmp; + + HASH_ITER (hh, ioc->requests, req, rtmp) { + REF_RELEASE (req); + } + ioc->resolver->async->del_read (ioc->resolver->async->data, + ioc->async_io); + close (ioc->sock); + free (ioc); +} + +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_release (struct rdns_request *req) +{ + REF_RELEASE (req); +} + +static bool +rdns_resolver_conf_process_line (struct rdns_resolver *resolver, char *line) +{ + char *p, *c; + bool has_obrace = false; + unsigned int port = dns_port; + + if (strncmp (line, "nameserver", sizeof ("nameserver") - 1) == 0) { + p = line + sizeof ("nameserver") - 1; + /* Skip spaces */ + while (*p == ' ' || *p == '\t') { + 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' && *p != '\n') { + return false; + } + *p = '\0'; + 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; + } + } + } + + return rdns_resolver_add_server (resolver, c, port, 0, default_io_cnt); + } + else { + return false; + } + } + /* XXX: skip unknown resolv.conf lines */ + + return true; +} + +bool +rdns_resolver_parse_resolv_conf (struct rdns_resolver *resolver, const char *path) +{ + FILE *in; + char buf[BUFSIZ]; + + in = fopen (path, "r"); + + if (in == NULL) { + return false; + } + + while (!feof (in)) { + if (fgets (buf, sizeof (buf), in) == NULL) { + break; + } + if (!rdns_resolver_conf_process_line (resolver, buf)) { + rdns_warn ("rdns_resolver_parse_resolv_conf: cannot parse line: %s", buf); + fclose (in); + return false; + } + } + + fclose (in); + return true; +} + +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; +} + +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 000000000..035f49a19 --- /dev/null +++ b/contrib/librdns/util.h @@ -0,0 +1,62 @@ +/* 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); + +/** + * 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); + +/** + * Free request + * @param req + */ +void rdns_request_free (struct rdns_request *req); + +/** + * Free reply + * @param rep + */ +void rdns_reply_free (struct rdns_reply *rep); + +#endif /* UTIL_H_ */ |