/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "ottery.h" #include "util.h" #include "logger.h" #include "rdns.h" inline void rdns_request_remove_from_hash (struct rdns_request *req) { /* Remove from id hashes */ if (req->io) { khiter_t k; k = kh_get(rdns_requests_hash, req->io->requests, req->id); if (k != kh_end(req->io->requests)) { kh_del(rdns_requests_hash, req->io->requests, k); } } } static int rdns_make_socket_nonblocking (int fd) { int ofl; ofl = fcntl (fd, F_GETFL, 0); if (fcntl (fd, F_SETFL, ofl | O_NONBLOCK) == -1) { return -1; } return 0; } static int rdns_make_inet_socket (int type, struct addrinfo *addr, struct sockaddr **psockaddr, socklen_t *psocklen) { int fd = -1; struct addrinfo *cur; cur = addr; while (cur) { /* Create socket */ fd = socket (cur->ai_family, type, 0); if (fd == -1) { goto out; } if (rdns_make_socket_nonblocking (fd) < 0) { goto out; } /* Set close on exec */ if (fcntl (fd, F_SETFD, FD_CLOEXEC) == -1) { goto out; } if (psockaddr) { *psockaddr = cur->ai_addr; *psocklen = cur->ai_addrlen; } break; out: if (fd != -1) { close (fd); } fd = -1; cur = cur->ai_next; } return (fd); } static int rdns_make_unix_socket (const char *path, struct sockaddr_un *addr, int type) { int fd = -1, serrno; if (path == NULL) { return -1; } addr->sun_family = AF_UNIX; memset (addr->sun_path, 0, sizeof (addr->sun_path)); memccpy (addr->sun_path, path, 0, sizeof (addr->sun_path) - 1); #ifdef FREEBSD addr->sun_len = SUN_LEN (addr); #endif fd = socket (PF_LOCAL, type, 0); if (fd == -1) { return -1; } if (rdns_make_socket_nonblocking (fd) < 0) { goto out; } /* Set close on exec */ if (fcntl (fd, F_SETFD, FD_CLOEXEC) == -1) { goto out; } return (fd); out: serrno = errno; if (fd != -1) { close (fd); } errno = serrno; return (-1); } /** * Make a universal socket * @param credits host, ip or path to unix socket * @param port port (used for network sockets) * @param async make this socket asynced * @param is_server make this socket as server socket * @param try_resolve try name resolution for a socket (BLOCKING) */ int rdns_make_client_socket (const char *credits, uint16_t port, int type, struct sockaddr **psockaddr, socklen_t *psocklen) { struct sockaddr_un un; struct stat st; struct addrinfo hints, *res; int r; char portbuf[8]; if (*credits == '/') { r = stat (credits, &st); if (r == -1) { /* Unix socket doesn't exists it must be created first */ errno = ENOENT; return -1; } else { if ((st.st_mode & S_IFSOCK) == 0) { /* Path is not valid socket */ errno = EINVAL; return -1; } else { r = rdns_make_unix_socket (credits, &un, type); if (r != -1 && psockaddr) { struct sockaddr *cpy; cpy = calloc (1, sizeof (un)); *psocklen = sizeof (un); if (cpy == NULL) { close (r); return -1; } memcpy (cpy, &un, *psocklen); *psockaddr = cpy; } return r; } } } else { /* TCP related part */ memset (&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = type; /* Type of the socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV; snprintf (portbuf, sizeof (portbuf), "%d", (int)port); if (getaddrinfo (credits, portbuf, &hints, &res) == 0) { r = rdns_make_inet_socket (type, res, psockaddr, psocklen); if (r != -1 && psockaddr) { struct sockaddr *cpy; cpy = calloc (1, *psocklen); if (cpy == NULL) { close (r); freeaddrinfo (res); return -1; } memcpy (cpy, *psockaddr, *psocklen); *psockaddr = cpy; } freeaddrinfo (res); return r; } else { return -1; } } /* Not reached */ return -1; } const char * rdns_strerror (enum dns_rcode rcode) { rcode &= 0xf; static char numbuf[16]; if ('\0' == dns_rcodes[rcode][0]) { snprintf (numbuf, sizeof (numbuf), "UNKNOWN: %d", (int)rcode); return numbuf; } return dns_rcodes[rcode]; } const char * rdns_strtype (enum rdns_request_type type) { return dns_types[type]; } enum rdns_request_type rdns_type_fromstr (const char *str) { if (str) { if (strcmp (str, "a") == 0) { return RDNS_REQUEST_A; } else if (strcmp (str, "ns") == 0) { return RDNS_REQUEST_NS; } else if (strcmp (str, "soa") == 0) { return RDNS_REQUEST_SOA; } else if (strcmp (str, "ptr") == 0) { return RDNS_REQUEST_PTR; } else if (strcmp (str, "mx") == 0) { return RDNS_REQUEST_MX; } else if (strcmp (str, "srv") == 0) { return RDNS_REQUEST_SRV; } else if (strcmp (str, "txt") == 0) { return RDNS_REQUEST_TXT; } else if (strcmp (str, "spf") == 0) { return RDNS_REQUEST_SPF; } else if (strcmp (str, "aaaa") == 0) { return RDNS_REQUEST_AAAA; } else if (strcmp (str, "tlsa") == 0) { return RDNS_REQUEST_TLSA; } else if (strcmp (str, "any") == 0) { return RDNS_REQUEST_ANY; } } return RDNS_REQUEST_INVALID; } const char * rdns_str_from_type (enum rdns_request_type rcode) { switch (rcode) { case RDNS_REQUEST_INVALID: return "(invalid)"; case RDNS_REQUEST_A: return "a"; case RDNS_REQUEST_NS: return "ns"; case RDNS_REQUEST_SOA: return "soa"; case RDNS_REQUEST_PTR: return "ptr"; case RDNS_REQUEST_MX: return "mx"; case RDNS_REQUEST_TXT: return "txt"; case RDNS_REQUEST_SRV: return "srv"; case RDNS_REQUEST_SPF: return "spf"; case RDNS_REQUEST_AAAA: return "aaaa"; case RDNS_REQUEST_TLSA: return "tlsa"; case RDNS_REQUEST_ANY: return "any"; default: return "(unknown)"; } } enum dns_rcode rdns_rcode_fromstr (const char *str) { if (str) { if (strcmp (str, "noerror") == 0) { return RDNS_RC_NOERROR; } else if (strcmp (str, "formerr") == 0) { return RDNS_RC_FORMERR; } else if (strcmp (str, "servfail") == 0) { return RDNS_RC_SERVFAIL; } else if (strcmp (str, "nxdomain") == 0) { return RDNS_RC_NXDOMAIN; } else if (strcmp (str, "notimp") == 0) { return RDNS_RC_NOTIMP; } else if (strcmp (str, "yxdomain") == 0) { return RDNS_RC_YXDOMAIN; } else if (strcmp (str, "yxrrset") == 0) { return RDNS_RC_YXRRSET; } else if (strcmp (str, "nxrrset") == 0) { return RDNS_RC_NXRRSET; } else if (strcmp (str, "notauth") == 0) { return RDNS_RC_NOTAUTH; } else if (strcmp (str, "notzone") == 0) { return RDNS_RC_NOTZONE; } else if (strcmp (str, "timeout") == 0) { return RDNS_RC_TIMEOUT; } else if (strcmp (str, "neterr") == 0) { return RDNS_RC_NETERR; } else if (strcmp (str, "norec") == 0) { return RDNS_RC_NOREC; } } return RDNS_RC_INVALID; } uint16_t rdns_permutor_generate_id (void) { uint16_t id; id = ottery_rand_unsigned (); return id; } struct rdns_reply * rdns_make_reply (struct rdns_request *req, enum dns_rcode rcode) { struct rdns_reply *rep; rep = malloc (sizeof (struct rdns_reply)); if (rep != NULL) { rep->request = req; rep->resolver = req->resolver; rep->entries = NULL; rep->code = rcode; req->reply = rep; rep->flags = 0; rep->requested_name = req->requested_names[0].name; } return rep; } void rdns_reply_free (struct rdns_reply *rep) { struct rdns_reply_entry *entry, *tmp; /* We don't need to free data for faked replies */ if (!rep->request || rep->request->state != RDNS_REQUEST_FAKE) { LL_FOREACH_SAFE (rep->entries, entry, tmp) { switch (entry->type) { case RDNS_REQUEST_PTR: free (entry->content.ptr.name); break; case RDNS_REQUEST_NS: free (entry->content.ns.name); break; case RDNS_REQUEST_MX: free (entry->content.mx.name); break; case RDNS_REQUEST_TXT: case RDNS_REQUEST_SPF: free (entry->content.txt.data); break; case RDNS_REQUEST_SRV: free (entry->content.srv.target); break; case RDNS_REQUEST_TLSA: free (entry->content.tlsa.data); break; case RDNS_REQUEST_SOA: free (entry->content.soa.mname); free (entry->content.soa.admin); break; default: break; } free (entry); } } free (rep); } void rdns_request_free (struct rdns_request *req) { unsigned int i; if (req != NULL) { if (req->packet != NULL) { free (req->packet); } for (i = 0; i < req->qcount; i ++) { free (req->requested_names[i].name); } if (req->requested_names != NULL) { free (req->requested_names); } if (req->reply != NULL) { rdns_reply_free (req->reply); } if (req->async_event) { if (req->state == RDNS_REQUEST_WAIT_REPLY) { /* Remove timer */ req->async->del_timer (req->async->data, req->async_event); rdns_request_remove_from_hash(req); req->async_event = NULL; } else if (req->state == RDNS_REQUEST_WAIT_SEND) { /* Remove retransmit event */ req->async->del_write (req->async->data, req->async_event); rdns_request_remove_from_hash(req); req->async_event = NULL; } else if (req->state == RDNS_REQUEST_FAKE) { req->async->del_write (req->async->data, req->async_event); req->async_event = NULL; } } if (req->state == RDNS_REQUEST_TCP) { if (req->async_event) { req->async->del_timer (req->async->data, req->async_event); } rdns_request_remove_from_hash(req); } #ifdef TWEETNACL if (req->curve_plugin_data != NULL) { req->resolver->curve_plugin->cb.curve_plugin.finish_cb ( req, req->resolver->curve_plugin->data); } #endif if (req->io != NULL && req->state > RDNS_REQUEST_NEW) { REF_RELEASE (req->io); REF_RELEASE (req->resolver); } free (req); } } void rdns_ioc_free (struct rdns_io_channel *ioc) { struct rdns_request *req; if (IS_CHANNEL_TCP(ioc)) { rdns_ioc_tcp_reset(ioc); } kh_foreach_value(ioc->requests, req, { REF_RELEASE (req); }); if (ioc->async_io) { ioc->resolver->async->del_read(ioc->resolver->async->data, ioc->async_io); } kh_destroy(rdns_requests_hash, ioc->requests); if (ioc->sock != -1) { close(ioc->sock); } if (ioc->saddr != NULL) { free(ioc->saddr); } free (ioc); } struct rdns_io_channel * rdns_ioc_new (struct rdns_server *serv, struct rdns_resolver *resolver, bool is_tcp) { struct rdns_io_channel *nioc; if (is_tcp) { nioc = calloc (1, sizeof (struct rdns_io_channel) + sizeof (struct rdns_tcp_channel)); } else { nioc = calloc (1, sizeof (struct rdns_io_channel)); } if (nioc == NULL) { rdns_err ("calloc fails to allocate rdns_io_channel"); return NULL; } nioc->struct_magic = RDNS_IO_CHANNEL_TAG; nioc->srv = serv; nioc->resolver = resolver; nioc->sock = rdns_make_client_socket (serv->name, serv->port, is_tcp ? SOCK_STREAM : SOCK_DGRAM, &nioc->saddr, &nioc->slen); if (nioc->sock == -1) { rdns_err ("cannot open socket to %s: %s", serv->name, strerror (errno)); free (nioc); return NULL; } if (is_tcp) { /* We also need to connect a TCP channel and set a TCP buffer */ nioc->tcp = (struct rdns_tcp_channel *)(((unsigned char *)nioc) + sizeof(*nioc)); if (!rdns_ioc_tcp_connect(nioc)) { rdns_err ("cannot connect TCP socket to %s: %s", serv->name, strerror (errno)); close (nioc->sock); free (nioc); return NULL; } nioc->flags |= RDNS_CHANNEL_TCP; } else { nioc->flags |= RDNS_CHANNEL_ACTIVE; nioc->async_io = resolver->async->add_read(resolver->async->data, nioc->sock, nioc); } nioc->requests = kh_init(rdns_requests_hash); REF_INIT_RETAIN (nioc, rdns_ioc_free); return nioc; } void rdns_resolver_release (struct rdns_resolver *resolver) { REF_RELEASE (resolver); } struct rdns_request* rdns_request_retain (struct rdns_request *req) { REF_RETAIN (req); return req; } void rdns_request_unschedule (struct rdns_request *req) { if (req->async_event) { if (req->state == RDNS_REQUEST_WAIT_REPLY) { req->async->del_timer (req->async->data, req->async_event); rdns_request_remove_from_hash(req); req->async_event = NULL; } else if (req->state == RDNS_REQUEST_WAIT_SEND) { req->async->del_write (req->async->data, req->async_event); /* Remove from id hashes */ rdns_request_remove_from_hash(req); req->async_event = NULL; } } else if (req->state == RDNS_REQUEST_TCP) { rdns_request_remove_from_hash(req); req->async->del_timer(req->async->data, req->async_event); req->async_event = NULL; } } void rdns_request_release (struct rdns_request *req) { rdns_request_unschedule (req); REF_RELEASE (req); } void rdns_ioc_tcp_reset (struct rdns_io_channel *ioc) { struct rdns_resolver *resolver = ioc->resolver; if (IS_CHANNEL_CONNECTED(ioc)) { if (ioc->tcp->async_write) { resolver->async->del_write (resolver->async->data, ioc->tcp->async_write); ioc->tcp->async_write = NULL; } if (ioc->tcp->async_read) { resolver->async->del_read (resolver->async->data, ioc->tcp->async_read); ioc->tcp->async_read = NULL; } /* Clean all buffers and temporaries */ if (ioc->tcp->cur_read_buf) { free (ioc->tcp->cur_read_buf); ioc->tcp->read_buf_allocated = 0; ioc->tcp->next_read_size = 0; ioc->tcp->cur_read = 0; } struct rdns_tcp_output_chain *oc, *tmp; DL_FOREACH_SAFE(ioc->tcp->output_chain, oc, tmp) { DL_DELETE (ioc->tcp->output_chain, oc); free (oc); } ioc->tcp->cur_output_chains = 0; ioc->flags &= ~RDNS_CHANNEL_CONNECTED; } if (ioc->sock != -1) { close (ioc->sock); ioc->sock = -1; } if (ioc->saddr) { free (ioc->saddr); ioc->saddr = NULL; } /* Remove all requests pending as we are unable to complete them */ struct rdns_request *req; kh_foreach_value(ioc->requests, req, { struct rdns_reply *rep = rdns_make_reply (req, RDNS_RC_NETERR); req->state = RDNS_REQUEST_REPLIED; req->func (rep, req->arg); REF_RELEASE (req); }); kh_clear(rdns_requests_hash, ioc->requests); } bool rdns_ioc_tcp_connect (struct rdns_io_channel *ioc) { struct rdns_resolver *resolver = ioc->resolver; if (IS_CHANNEL_CONNECTED(ioc)) { rdns_err ("trying to connect already connected IO channel!"); return false; } if (ioc->flags & RDNS_CHANNEL_TCP_CONNECTING) { /* Already connecting channel, ignore connect request */ return true; } if (ioc->sock == -1) { ioc->sock = rdns_make_client_socket (ioc->srv->name, ioc->srv->port, SOCK_STREAM, &ioc->saddr, &ioc->slen); if (ioc->sock == -1) { rdns_err ("cannot open socket to %s: %s", ioc->srv->name, strerror (errno)); if (ioc->saddr) { free (ioc->saddr); ioc->saddr = NULL; } return false; } } int r = connect (ioc->sock, ioc->saddr, ioc->slen); if (r == -1) { if (errno != EAGAIN && errno != EINTR && errno != EINPROGRESS) { rdns_err ("cannot connect a TCP socket: %s for server %s", strerror(errno), ioc->srv->name); close (ioc->sock); if (ioc->saddr) { free (ioc->saddr); ioc->saddr = NULL; } ioc->sock = -1; return false; } else { /* We need to wait for write readiness here */ if (ioc->tcp->async_write != NULL) { rdns_err("internal rdns error: write event is already registered on connect"); } else { ioc->tcp->async_write = resolver->async->add_write(resolver->async->data, ioc->sock, ioc); } /* Prevent double connect attempts */ ioc->flags |= RDNS_CHANNEL_TCP_CONNECTING; } } else { /* Always be ready to read from a TCP socket */ ioc->flags |= RDNS_CHANNEL_CONNECTED|RDNS_CHANNEL_ACTIVE; ioc->flags &= ~RDNS_CHANNEL_TCP_CONNECTING; ioc->tcp->async_read = resolver->async->add_read(resolver->async->data, ioc->sock, ioc); } return true; } static bool rdns_resolver_conf_process_line (struct rdns_resolver *resolver, const char *line, rdns_resolv_conf_cb cb, void *ud) { const char *p, *c, *end; bool has_obrace = false, ret; unsigned int port = dns_port; char *cpy_buf; end = line + strlen (line); if (end - line > sizeof ("nameserver") - 1 && strncmp (line, "nameserver", sizeof ("nameserver") - 1) == 0) { p = line + sizeof ("nameserver") - 1; /* Skip spaces */ while (isspace (*p)) { p ++; } if (*p == '[') { has_obrace = true; p ++; } if (isxdigit (*p) || *p == ':') { c = p; while (isxdigit (*p) || *p == ':' || *p == '.') { p ++; } if (has_obrace && *p != ']') { return false; } else if (*p != '\0' && !isspace (*p) && *p != '#') { return false; } if (has_obrace) { p ++; if (*p == ':') { /* Maybe we have a port definition */ port = strtoul (p + 1, NULL, 10); if (port == 0 || port > UINT16_MAX) { return false; } } } cpy_buf = malloc (p - c + 1); assert (cpy_buf != NULL); memcpy (cpy_buf, c, p - c); cpy_buf[p - c] = '\0'; if (cb == NULL) { ret = rdns_resolver_add_server (resolver, cpy_buf, port, 0, default_io_cnt) != NULL; } else { ret = cb (resolver, cpy_buf, port, 0, default_io_cnt, ud); } free (cpy_buf); return ret; } else { return false; } } /* XXX: skip unknown resolv.conf lines */ return false; } bool rdns_resolver_parse_resolv_conf_cb (struct rdns_resolver *resolver, const char *path, rdns_resolv_conf_cb cb, void *ud) { FILE *in; char buf[BUFSIZ]; char *p; bool processed = false; in = fopen (path, "r"); if (in == NULL) { return false; } while (!feof (in)) { if (fgets (buf, sizeof (buf) - 1, in) == NULL) { break; } /* Strip trailing spaces */ p = buf + strlen (buf) - 1; while (p > buf && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) { *p-- = '\0'; } if (rdns_resolver_conf_process_line (resolver, buf, cb, ud)) { processed = true; } } fclose (in); return processed; } bool rdns_resolver_parse_resolv_conf (struct rdns_resolver *resolver, const char *path) { return rdns_resolver_parse_resolv_conf_cb (resolver, path, NULL, NULL); } bool rdns_request_has_type (struct rdns_request *req, enum rdns_request_type type) { unsigned int i; for (i = 0; i < req->qcount; i ++) { if (req->requested_names[i].type == type) { return true; } } return false; } const struct rdns_request_name * rdns_request_get_name (struct rdns_request *req, unsigned int *count) { if (count != NULL) { *count = req->qcount; } return req->requested_names; } const char* rdns_request_get_server (struct rdns_request *req) { if (req && req->io) { return req->io->srv->name; } return NULL; } char * rdns_generate_ptr_from_str (const char *str) { union { struct in_addr v4; struct in6_addr v6; } addr; char *res = NULL; unsigned char *bytes; size_t len; if (inet_pton (AF_INET, str, &addr.v4) == 1) { bytes = (unsigned char *)&addr.v4; len = 4 * 4 + sizeof ("in-addr.arpa"); res = malloc (len); if (res) { snprintf (res, len, "%u.%u.%u.%u.in-addr.arpa", (unsigned)bytes[3]&0xFF, (unsigned)bytes[2]&0xFF, (unsigned)bytes[1]&0xFF, (unsigned)bytes[0]&0xFF); } } else if (inet_pton (AF_INET6, str, &addr.v6) == 1) { bytes = (unsigned char *)&addr.v6; len = 2*32 + sizeof ("ip6.arpa"); res = malloc (len); if (res) { snprintf(res, len, "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.ip6.arpa", bytes[15]&0xF, bytes[15] >> 4, bytes[14]&0xF, bytes[14] >> 4, bytes[13]&0xF, bytes[13] >> 4, bytes[12]&0xF, bytes[12] >> 4, bytes[11]&0xF, bytes[11] >> 4, bytes[10]&0xF, bytes[10] >> 4, bytes[9]&0xF, bytes[9] >> 4, bytes[8]&0xF, bytes[8] >> 4, bytes[7]&0xF, bytes[7] >> 4, bytes[6]&0xF, bytes[6] >> 4, bytes[5]&0xF, bytes[5] >> 4, bytes[4]&0xF, bytes[4] >> 4, bytes[3]&0xF, bytes[3] >> 4, bytes[2]&0xF, bytes[2] >> 4, bytes[1]&0xF, bytes[1] >> 4, bytes[0]&0xF, bytes[0] >> 4); } } return res; }