diff options
Diffstat (limited to 'src/rexmpp_dns.c')
-rw-r--r-- | src/rexmpp_dns.c | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/src/rexmpp_dns.c b/src/rexmpp_dns.c new file mode 100644 index 0000000..d56aa10 --- /dev/null +++ b/src/rexmpp_dns.c @@ -0,0 +1,485 @@ +/** + @file rexmpp_dns.c + @brief DNS helper functions + @author defanor <defanor@uberspace.net> + @date 2020 + @copyright MIT license. +*/ + +#include <memory.h> +#include <stdlib.h> +#include <syslog.h> + +#include "config.h" + +#if defined(USE_UNBOUND) +#include <unbound.h> +#elif defined(USE_CARES) +#include <ares.h> +#else +#endif +#include <netdb.h> + + +#include "rexmpp.h" +#include "rexmpp_dns.h" + + +struct rexmpp_dns_query_cb_data { + rexmpp_t *s; + dns_query_cb_t cb; + void *ptr; +}; + + + +/* https://tools.ietf.org/html/rfc1035#section-3.1 */ + +int rexmpp_dns_parse_qname (char *in, int in_len, char *out, int out_len) { + int i = 0; + while (i < in_len && in[i]) { + if (i + in[i] < in_len && i + in[i] < out_len) { + memcpy(out + i, in + i + 1, in[i]); + i += in[i]; + out[i] = '.'; + i++; + out[i] = '\0'; + } else { + return -1; + } + } + return i; +} + +int rexmpp_parse_srv (char *in, int in_len, struct rexmpp_dns_srv *out) { + if (in_len < 7 || in_len > 255 + 6) { + return -1; + } + out->priority = in[0] * 0x100 + in[1]; + out->weight = in[2] * 0x100 + in[3]; + out->port = in[4] * 0x100 + in[5]; + if (rexmpp_dns_parse_qname(in + 6, in_len - 6, out->target, 255) < 0) { + return -1; + } + return 0; +} + + +#ifndef USE_RUST +void rexmpp_dns_result_free (rexmpp_dns_result_t *result) { + if (result->data != NULL) { + int i; + for (i = 0; result->data[i] != NULL; i++) { + free(result->data[i]); + } + free(result->data); + result->data = NULL; + } + if (result->len != NULL) { + free(result->len); + result->len = NULL; + } + free(result); +} +#endif + +rexmpp_dns_result_t *result_from_hostent (struct hostent *hostinfo) { + rexmpp_dns_result_t *r = malloc(sizeof(rexmpp_dns_result_t)); + if (r == NULL) { + return NULL; + } + r->secure = 0; + int i, size = 0; + while (hostinfo->h_addr_list[size] != NULL) { + size++; + } + r->data = malloc(sizeof(void *) * (size + 1)); + if (r->data == NULL) { + free(r); + return NULL; + } + r->len = malloc(sizeof(int) * size); + if (r->len == NULL) { + free(r->data); + free(r); + return NULL; + } + for (i = 0; i < size; i++) { + r->len[i] = hostinfo->h_length; + r->data[i] = malloc(r->len[i]); + if (r->data[i] != NULL) { + memcpy(r->data[i], hostinfo->h_addr_list[i], hostinfo->h_length); + } else { + return r; + } + } + r->data[size] = NULL; + return r; +} + + +int rexmpp_dns_ctx_init (rexmpp_t *s) { +#if defined(USE_UNBOUND) + int err; + s->resolver = ub_ctx_create(); + if (s->resolver == NULL) { + rexmpp_log(s, LOG_CRIT, "Failed to create resolver context"); + return 1; + } + err = ub_ctx_resolvconf(s->resolver, NULL); + if (err != 0) { + rexmpp_log(s, LOG_WARNING, "Failed to read resolv.conf: %s", + ub_strerror(err)); + } + err = ub_ctx_hosts(s->resolver, NULL); + if (err != 0) { + rexmpp_log(s, LOG_WARNING, "Failed to read hosts file: %s", + ub_strerror(err)); + } + err = ub_ctx_add_ta_file(s->resolver, DNSSEC_TRUST_ANCHOR_FILE); + if (err != 0) { + rexmpp_log(s, LOG_WARNING, "Failed to set root key file for DNSSEC: %s", + ub_strerror(err)); + } + return 0; +#elif defined(USE_CARES) + int err = ares_library_init(ARES_LIB_INIT_ALL); + if (err != 0) { + rexmpp_log(s, LOG_CRIT, "ares library initialisation error: %s", + ares_strerror(err)); + return 1; + } + err = ares_init(&(s->resolver)); + if (err) { + rexmpp_log(s, LOG_CRIT, "ares channel initialisation error: %s", + ares_strerror(err)); + ares_library_cleanup(); + return 1; + } + return 0; +#else + s->resolver = NULL; + return 0; +#endif +} + +void rexmpp_dns_ctx_cleanup (rexmpp_t *s) { + (void)s; + return; +} + +void rexmpp_dns_ctx_deinit (rexmpp_t *s) { +#if defined(USE_UNBOUND) + if (s->resolver != NULL) { + ub_ctx_delete(s->resolver); + s->resolver = NULL; + } +#elif defined(USE_CARES) + if (s->resolver != NULL) { + ares_destroy(s->resolver); + s->resolver = NULL; + } + ares_library_cleanup(); +#else + (void)s; +#endif +} + +int rexmpp_dns_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { +#if defined(USE_UNBOUND) + (void)write_fds; + int max_fd = ub_fd(s->resolver) + 1; + if (max_fd != 0) { + FD_SET(max_fd - 1, read_fds); + } + return max_fd; +#elif defined(USE_CARES) + return ares_fds(s->resolver, read_fds, write_fds); +#else + (void)s; + (void)read_fds; + (void)write_fds; + return 0; +#endif +} + +struct timespec * rexmpp_dns_timeout (rexmpp_t *s, + struct timespec *max_tv, + struct timespec *tv) +{ +#if defined(USE_UNBOUND) + (void)s; + (void)tv; + return max_tv; +#elif defined(USE_CARES) + struct timeval tv_ms; + struct timeval *max_tv_ms = NULL; + if (max_tv != NULL) { + max_tv_ms = &tv_ms; + tv_ms.tv_sec = tv->tv_sec; + tv_ms.tv_usec = tv->tv_nsec / 1000; + } + struct timeval *ret_ms = ares_timeout(s->resolver, max_tv_ms, &tv_ms); + if (ret_ms == max_tv_ms) { + return max_tv; + } else { + tv->tv_sec = tv_ms.tv_sec; + tv->tv_nsec = tv_ms.tv_usec * 1000; + return tv; + } +#else + (void)s; + (void)max_tv; + (void)tv; + return max_tv; +#endif +} + +#if defined(USE_UNBOUND) +void rexmpp_dns_cb (void *ptr, + int err, + struct ub_result *result) +{ + struct rexmpp_dns_query_cb_data *d = ptr; + rexmpp_t *s = d->s; + + if (err != 0) { + rexmpp_log(s, LOG_WARNING, "DNS query failure: %s", + ub_strerror(err)); + ub_resolve_free(result); + d->cb(s, d->ptr, NULL); + free(d); + return; + } + + if (result->bogus) { + rexmpp_log(s, LOG_WARNING, + "Received a bogus DNS resolution result"); + ub_resolve_free(result); + d->cb(s, d->ptr, NULL); + return; + } + + if (! result->havedata) { + rexmpp_log(s, LOG_DEBUG, "No data in the query result"); + ub_resolve_free(result); + d->cb(s, d->ptr, NULL); + free(d); + return; + } + + int i, size = 0; + while (result->data[size] != NULL) { + size++; + } + rexmpp_dns_result_t *res = malloc(sizeof(rexmpp_dns_result_t)); + if (res == NULL) { + rexmpp_log(s, LOG_DEBUG, + "Failed to allocate memory for a DNS resolution result"); + ub_resolve_free(result); + d->cb(s, d->ptr, NULL); + free(d); + return; + } + res->data = calloc((size + 1), sizeof(void *)); + if (res->data == NULL) { + rexmpp_log(s, LOG_DEBUG, + "Failed to allocate memory for a DNS resolution result's data"); + free(res); + ub_resolve_free(result); + d->cb(s, d->ptr, NULL); + free(d); + return; + } + res->len = malloc(sizeof(int) * size); + if (res->len == NULL) { + rexmpp_log(s, LOG_DEBUG, + "Failed to allocate memory for a DNS resolution result's len"); + free(res->data); + free(res); + ub_resolve_free(result); + d->cb(s, d->ptr, NULL); + free(d); + return; + } + for (i = 0; i < size; i++) { + if (result->qtype == 33) { + /* SRV */ + res->len[i] = sizeof(rexmpp_dns_srv_t); + res->data[i] = malloc(res->len[i]); + if (res->data[i] == NULL) { + rexmpp_log(s, LOG_ERR, + "Failed to allocate memory for an SRV record"); + d->cb(s, d->ptr, res); + rexmpp_dns_result_free(res); + free(d); + return; + } + int err = rexmpp_parse_srv(result->data[i], result->len[i], + (rexmpp_dns_srv_t*)res->data[i]); + if (err) { + rexmpp_log(s, LOG_WARNING, "Failed to parse an SRV record"); + d->cb(s, d->ptr, res); + rexmpp_dns_result_free(res); + free(d); + return; + } + } else { + /* Non-SRV, for now that's just A or AAAA */ + res->len[i] = result->len[i]; + res->data[i] = malloc(res->len[i]); + if (res->data[i] == NULL) { + rexmpp_log(s, LOG_WARNING, + "Failed to allocate memory for a DNS result's data record"); + } else { + memcpy(res->data[i], result->data[i], res->len[i]); + } + } + } + res->data[size] = NULL; + res->secure = result->secure; + ub_resolve_free(result); + d->cb(s, d->ptr, res); + free(d); +} +#elif defined(USE_CARES) +void rexmpp_dns_cb (void *ptr, + int err, + int timeouts, + unsigned char *abuf, + int alen) +{ + (void)timeouts; + struct rexmpp_dns_query_cb_data *d = ptr; + rexmpp_t *s = d->s; + if (err != ARES_SUCCESS) { + rexmpp_log(s, LOG_WARNING, "A DNS query failure: %s", + ares_strerror(err)); + d->cb(s, d->ptr, NULL); + free(d); + return; + } + /* c-ares won't just tell us the type, but it does check for it in + the parsing functions, so we just try them out. */ + struct hostent *hostinfo; + struct ares_srv_reply *srv, *cur_srv; + if (ares_parse_a_reply(abuf, alen, &hostinfo, NULL, NULL) == ARES_SUCCESS || + ares_parse_aaaa_reply(abuf, alen, &hostinfo, NULL, NULL) == ARES_SUCCESS) { + rexmpp_dns_result_t *r = result_from_hostent(hostinfo); + ares_free_hostent(hostinfo); + d->cb(s, d->ptr, r); + } else if (ares_parse_srv_reply(abuf, alen, &srv) == ARES_SUCCESS) { + int i, size; + for (size = 0, cur_srv = srv; cur_srv != NULL; size++, cur_srv = cur_srv->next); + rexmpp_dns_result_t *r = malloc(sizeof(rexmpp_dns_result_t)); + r->secure = 0; + r->data = malloc(sizeof(void*) * (size + 1)); + r->len = malloc(sizeof(int) * size); + for (cur_srv = srv, i = 0; i < size; i++, cur_srv = cur_srv->next) { + r->len[i] = sizeof(rexmpp_dns_srv_t); + rexmpp_dns_srv_t *r_srv = malloc(sizeof(rexmpp_dns_srv_t)); + r_srv->priority = cur_srv->priority; + r_srv->weight = cur_srv->weight; + r_srv->port = cur_srv->port; + strncpy(r_srv->target, cur_srv->host, 255); + r_srv->target[255] = '\0'; + r->data[i] = r_srv; + } + r->data[size] = NULL; + ares_free_data(srv); + d->cb(s, d->ptr, r); + } else { + rexmpp_log(s, LOG_ERR, "Failed to parse a query"); + d->cb(s, d->ptr, NULL); + } + free(d); +} +#endif + + +int rexmpp_dns_resolve (rexmpp_t *s, + const char *query, + int rrtype, + int rrclass, + void* ptr, + dns_query_cb_t callback) +{ +#if defined(USE_UNBOUND) + struct rexmpp_dns_query_cb_data *d = + malloc(sizeof(struct rexmpp_dns_query_cb_data)); + if (d == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a DNS query"); + return 1; + } + d->s = s; + d->cb = callback; + d->ptr = ptr; + int err = ub_resolve_async(s->resolver, query, rrtype, rrclass, + d, rexmpp_dns_cb, NULL); + if (err) { + rexmpp_log(s, LOG_ERR, "Failed to query %s: %s", + query, ub_strerror(err)); + return 1; + } +#elif defined(USE_CARES) + struct rexmpp_dns_query_cb_data *d = + malloc(sizeof(struct rexmpp_dns_query_cb_data)); + if (d == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to allocate memory for a DNS query"); + return 1; + } + d->s = s; + d->cb = callback; + d->ptr = ptr; + ares_query(s->resolver, query, rrclass, rrtype, rexmpp_dns_cb, d); +#else + if (rrclass == 1) { + if (rrtype == 1 || rrtype == 28) { + struct hostent *hostinfo = gethostbyname(query); + if (hostinfo == NULL) { + rexmpp_log(s, LOG_ERR, "Failed to lookup %s", query); + callback(s, ptr, NULL); + } else { + rexmpp_dns_result_t *r = result_from_hostent(hostinfo); + callback(s, ptr, r); + } + } else if (rrtype == 33) { + rexmpp_log(s, LOG_WARNING, "rexmpp is built without SRV lookup support"); + callback(s, ptr, NULL); + } else { + rexmpp_log(s, LOG_ERR, "A DNS lookup of unrecognized type is requested"); + callback(s, ptr, NULL); + return -1; + } + } else { + rexmpp_log(s, LOG_ERR, "A DNS lookup of unrecognized class is requested"); + callback(s, ptr, NULL); + return -1; + } +#endif + return 0; +} + +int rexmpp_dns_process (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) { +#if defined(USE_UNBOUND) + (void)read_fds; + (void)write_fds; + if (ub_poll(s->resolver)) { + int err = ub_process(s->resolver); + if (err != 0) { + rexmpp_log(s, LOG_ERR, "DNS query processing error: %s", + ub_strerror(err)); + return 1; + } + } + return 0; +#elif defined(USE_CARES) + ares_process(s->resolver, read_fds, write_fds); + return 0; +#else + (void)s; + (void)read_fds; + (void)write_fds; + return 0; +#endif +} |