10463 lines
237 KiB
C
10463 lines
237 KiB
C
/* ==========================================================================
|
||
* dns.c - Recursive, Reentrant DNS Resolver.
|
||
* --------------------------------------------------------------------------
|
||
* Copyright (c) 2008, 2009, 2010, 2012-2016 William Ahern
|
||
*
|
||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||
* copy of this software and associated documentation files (the
|
||
* "Software"), to deal in the Software without restriction, including
|
||
* without limitation the rights to use, copy, modify, merge, publish,
|
||
* distribute, sublicense, and/or sell copies of the Software, and to permit
|
||
* persons to whom the Software is furnished to do so, subject to the
|
||
* following conditions:
|
||
*
|
||
* The above copyright notice and this permission notice shall be included
|
||
* in all copies or substantial portions of the Software.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||
* USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
* ==========================================================================
|
||
*/
|
||
#if !defined(__FreeBSD__) && !defined(__sun)
|
||
#ifndef _XOPEN_SOURCE
|
||
#define _XOPEN_SOURCE 600
|
||
#endif
|
||
|
||
#undef _DEFAULT_SOURCE
|
||
#define _DEFAULT_SOURCE
|
||
|
||
#undef _BSD_SOURCE
|
||
#define _BSD_SOURCE
|
||
|
||
#undef _DARWIN_C_SOURCE
|
||
#define _DARWIN_C_SOURCE
|
||
|
||
#undef _NETBSD_SOURCE
|
||
#define _NETBSD_SOURCE
|
||
#endif
|
||
|
||
#include <limits.h> /* INT_MAX */
|
||
#include <stddef.h> /* offsetof() */
|
||
#ifdef _WIN32
|
||
#define uint32_t unsigned int
|
||
#else
|
||
#include <stdint.h> /* uint32_t */
|
||
#endif
|
||
#include <stdlib.h> /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */
|
||
#include <stdio.h> /* FILE fopen(3) fclose(3) getc(3) rewind(3) */
|
||
#include <string.h> /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */
|
||
#include <strings.h> /* strcasecmp(3) strncasecmp(3) */
|
||
#include <ctype.h> /* isspace(3) isdigit(3) */
|
||
#include <time.h> /* time_t time(2) difftime(3) */
|
||
#include <signal.h> /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */
|
||
#include <errno.h> /* errno EINVAL ENOENT */
|
||
#include <stdatomic.h> /* atomic_init atomic_load_explicit atomic_fetch_and_explicit atomic_fetch_or_explicit memory_order_relaxed */
|
||
#undef NDEBUG
|
||
#include <assert.h> /* assert(3) */
|
||
|
||
#if _WIN32
|
||
#ifndef FD_SETSIZE
|
||
#define FD_SETSIZE 256
|
||
#endif
|
||
#include <winsock2.h>
|
||
#include <ws2tcpip.h>
|
||
#else
|
||
#include <sys/types.h> /* FD_SETSIZE socklen_t */
|
||
#include <sys/select.h> /* FD_ZERO FD_SET fd_set select(2) */
|
||
#include <sys/socket.h> /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */
|
||
#ifdef __SWITCH__
|
||
#include <sys/socket.h>
|
||
#elif defined(AF_UNIX)
|
||
#include <sys/un.h> /* struct sockaddr_un */
|
||
#endif
|
||
#include <fcntl.h> /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */
|
||
#include <unistd.h> /* _POSIX_THREADS gethostname(3) close(2) */
|
||
#include <poll.h> /* POLLIN POLLOUT */
|
||
#include <netinet/in.h> /* struct sockaddr_in struct sockaddr_in6 */
|
||
#include <arpa/inet.h> /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */
|
||
#include <netdb.h> /* struct addrinfo */
|
||
#endif
|
||
|
||
#include "dns.h"
|
||
|
||
|
||
/*
|
||
* C O M P I L E R V E R S I O N & F E A T U R E D E T E C T I O N
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#define DNS_GNUC_2VER(M, m, p) (((M) * 10000) + ((m) * 100) + (p))
|
||
#define DNS_GNUC_PREREQ(M, m, p) (__GNUC__ > 0 && DNS_GNUC_2VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) >= DNS_GNUC_2VER((M), (m), (p)))
|
||
|
||
#define DNS_MSC_2VER(M, m, p) ((((M) + 6) * 10000000) + ((m) * 1000000) + (p))
|
||
#define DNS_MSC_PREREQ(M, m, p) (_MSC_VER_FULL > 0 && _MSC_VER_FULL >= DNS_MSC_2VER((M), (m), (p)))
|
||
|
||
#define DNS_SUNPRO_PREREQ(M, m, p) (__SUNPRO_C > 0 && __SUNPRO_C >= 0x ## M ## m ## p)
|
||
|
||
#if defined __has_builtin
|
||
#define dns_has_builtin(x) __has_builtin(x)
|
||
#else
|
||
#define dns_has_builtin(x) 0
|
||
#endif
|
||
|
||
#if defined __has_extension
|
||
#define dns_has_extension(x) __has_extension(x)
|
||
#else
|
||
#define dns_has_extension(x) 0
|
||
#endif
|
||
|
||
#ifndef HAVE___ASSUME
|
||
#define HAVE___ASSUME DNS_MSC_PREREQ(8,0,0)
|
||
#endif
|
||
|
||
#ifndef HAVE___BUILTIN_TYPES_COMPATIBLE_P
|
||
#define HAVE___BUILTIN_TYPES_COMPATIBLE_P (DNS_GNUC_PREREQ(3,1,1) || __clang__)
|
||
#endif
|
||
|
||
#ifndef HAVE___BUILTIN_UNREACHABLE
|
||
#define HAVE___BUILTIN_UNREACHABLE (DNS_GNUC_PREREQ(4,5,0) || dns_has_builtin(__builtin_unreachable))
|
||
#endif
|
||
|
||
#ifndef HAVE_PRAGMA_MESSAGE
|
||
#define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4,4,0) || __clang__ || _MSC_VER)
|
||
#endif
|
||
|
||
|
||
/*
|
||
* C O M P I L E R A N N O T A T I O N S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#if __GNUC__
|
||
#define DNS_NOTUSED __attribute__((unused))
|
||
#define DNS_NORETURN __attribute__((noreturn))
|
||
#else
|
||
#define DNS_NOTUSED
|
||
#define DNS_NORETURN
|
||
#endif
|
||
|
||
#if __clang__
|
||
#pragma clang diagnostic push
|
||
#pragma clang diagnostic ignored "-Wunused-parameter"
|
||
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
|
||
#elif DNS_GNUC_PREREQ(4,6,0)
|
||
#pragma GCC diagnostic push
|
||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||
#endif
|
||
|
||
|
||
/*
|
||
* S T A N D A R D M A C R O S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#if HAVE___BUILTIN_TYPES_COMPATIBLE_P
|
||
#define dns_same_type(a, b, def) __builtin_types_compatible_p(__typeof__ (a), __typeof__ (b))
|
||
#else
|
||
#define dns_same_type(a, b, def) (def)
|
||
#endif
|
||
#define dns_isarray(a) (!dns_same_type((a), (&(a)[0]), 0))
|
||
/* NB: "_" field silences Sun Studio "zero-sized struct/union" error diagnostic */
|
||
#define dns_inline_assert(cond) ((void)(sizeof (struct { int:-!(cond); int _; })))
|
||
|
||
#if HAVE___ASSUME
|
||
#define dns_assume(cond) __assume(cond)
|
||
#elif HAVE___BUILTIN_UNREACHABLE
|
||
#define dns_assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
|
||
#else
|
||
#define dns_assume(cond) do { (void)(cond); } while (0)
|
||
#endif
|
||
|
||
#ifndef lengthof
|
||
#define lengthof(a) (dns_inline_assert(dns_isarray(a)), (sizeof (a) / sizeof (a)[0]))
|
||
#endif
|
||
|
||
#ifndef endof
|
||
#define endof(a) (dns_inline_assert(dns_isarray(a)), &(a)[lengthof((a))])
|
||
#endif
|
||
|
||
|
||
/*
|
||
* M I S C E L L A N E O U S C O M P A T
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#if _WIN32 || _WIN64
|
||
#define PRIuZ "Iu"
|
||
#else
|
||
#define PRIuZ "zu"
|
||
#endif
|
||
|
||
#ifndef DNS_THREAD_SAFE
|
||
#if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0
|
||
#define DNS_THREAD_SAFE 1
|
||
#else
|
||
#define DNS_THREAD_SAFE 0
|
||
#endif
|
||
#endif
|
||
|
||
#ifndef HAVE__STATIC_ASSERT
|
||
#define HAVE__STATIC_ASSERT \
|
||
(dns_has_extension(c_static_assert) || DNS_GNUC_PREREQ(4,6,0) || \
|
||
__C11FEATURES__ || __STDC_VERSION__ >= 201112L)
|
||
#endif
|
||
|
||
#ifndef HAVE_STATIC_ASSERT
|
||
#if (defined static_assert) && \
|
||
(!DNS_GNUC_PREREQ(0,0,0) || DNS_GNUC_PREREQ(4,6,0)) /* glibc doesn't check GCC version */
|
||
#define HAVE_STATIC_ASSERT 1
|
||
#else
|
||
#define HAVE_STATIC_ASSERT 0
|
||
#endif
|
||
#endif
|
||
|
||
#if HAVE_STATIC_ASSERT
|
||
#define dns_static_assert(cond, msg) static_assert(cond, msg)
|
||
#elif HAVE__STATIC_ASSERT
|
||
#define dns_static_assert(cond, msg) _Static_assert(cond, msg)
|
||
#else
|
||
#define dns_static_assert(cond, msg) extern char DNS_PP_XPASTE(dns_assert_, __LINE__)[sizeof (int[1 - 2*!(cond)])]
|
||
#endif
|
||
|
||
|
||
/*
|
||
* D E B U G M A C R O S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#if DNS_MAIN || DNS_DEBUG
|
||
#define DNS_TRACE 1
|
||
#else
|
||
#define DNS_TRACE 0
|
||
#endif
|
||
|
||
int *dns_debug_p(void) {
|
||
static int debug;
|
||
|
||
return &debug;
|
||
} /* dns_debug_p() */
|
||
|
||
#if DNS_DEBUG
|
||
|
||
#if DNS_MAIN
|
||
#undef DNS_DEBUG
|
||
#define DNS_DEBUG dns_debug
|
||
#endif
|
||
|
||
#define DNS_SAY_(fmt, ...) \
|
||
do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0)
|
||
#define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n")
|
||
#define DNS_HAI DNS_SAY("HAI")
|
||
|
||
#define DNS_SHOW_(P, fmt, ...) do { \
|
||
if (DNS_DEBUG > 1) { \
|
||
fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n"); \
|
||
fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__); \
|
||
dns_p_dump((P), stderr); \
|
||
fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n"); \
|
||
} \
|
||
} while (0)
|
||
|
||
#define DNS_SHOW(...) DNS_SHOW_(__VA_ARGS__, "")
|
||
|
||
#else /* !DNS_DEBUG */
|
||
|
||
#undef DNS_DEBUG
|
||
#define DNS_DEBUG 0
|
||
|
||
#define DNS_SAY(...)
|
||
#define DNS_HAI
|
||
#define DNS_SHOW(...)
|
||
|
||
#endif /* DNS_DEBUG */
|
||
|
||
#define DNS_CARP(...) DNS_SAY(__VA_ARGS__)
|
||
|
||
/*
|
||
* D E B U G T R A C E O U T P U T
|
||
*
|
||
* Added by Carlo Wood
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#if DNS_TRACE
|
||
static int indentation = -1;
|
||
char const* indent()
|
||
{
|
||
static char spaces[60];
|
||
if (indentation == -1)
|
||
{
|
||
indentation = 0;
|
||
for (unsigned int i = 0; i < sizeof(spaces) - 1; i += 2)
|
||
{
|
||
spaces[i] = '|';
|
||
spaces[i + 1] = ' ';
|
||
}
|
||
}
|
||
return spaces + sizeof(spaces) - indentation;
|
||
}
|
||
|
||
#define ENTERING(fmt) do { \
|
||
printf("%sEntering " fmt "\n", indent()); \
|
||
indentation += 2; \
|
||
} while (0)
|
||
#define ENTERING1(fmt, ...) do { \
|
||
printf("%sEntering " fmt "\n", indent(), __VA_ARGS__); \
|
||
indentation += 2; \
|
||
} while (0)
|
||
#define LEAVING(fmt) do { \
|
||
indentation -= 2; \
|
||
printf("%sLeaving " fmt "\n", indent()); \
|
||
} while (0)
|
||
#define LEAVING1(fmt, ...) do { \
|
||
indentation -= 2; \
|
||
printf("%sLeaving " fmt "\n", indent(), __VA_ARGS__); \
|
||
} while (0)
|
||
#define CALLING(fmt) do { \
|
||
printf("%sCalling " fmt "\n", indent()); \
|
||
} while (0)
|
||
#define CALLING1(fmt, ...) do { \
|
||
printf("%sCalling " fmt "\n", indent(), __VA_ARGS__); \
|
||
} while (0)
|
||
#else
|
||
#define ENTERING(fmt)
|
||
#define ENTERING1(fmt, ...)
|
||
#define LEAVING(fmt)
|
||
#define LEAVING1(fmt, ...)
|
||
#define CALLING(fmt)
|
||
#define CALLING1(fmt, ...)
|
||
#endif
|
||
|
||
/*
|
||
* V E R S I O N R O U T I N E S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
const char *dns_vendor(void) {
|
||
return DNS_VENDOR;
|
||
} /* dns_vendor() */
|
||
|
||
|
||
int dns_v_rel(void) {
|
||
return DNS_V_REL;
|
||
} /* dns_v_rel() */
|
||
|
||
|
||
int dns_v_abi(void) {
|
||
return DNS_V_ABI;
|
||
} /* dns_v_abi() */
|
||
|
||
|
||
int dns_v_api(void) {
|
||
return DNS_V_API;
|
||
} /* dns_v_api() */
|
||
|
||
|
||
/*
|
||
* E R R O R R O U T I N E S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#if _WIN32
|
||
|
||
#define DNS_EINTR WSAEINTR
|
||
#define DNS_EINPROGRESS WSAEINPROGRESS
|
||
#define DNS_EISCONN WSAEISCONN
|
||
#define DNS_EWOULDBLOCK WSAEWOULDBLOCK
|
||
#define DNS_EALREADY WSAEALREADY
|
||
#define DNS_EAGAIN EAGAIN
|
||
#define DNS_ETIMEDOUT WSAETIMEDOUT
|
||
|
||
#define dns_syerr() ((int)GetLastError())
|
||
#define dns_soerr() ((int)WSAGetLastError())
|
||
|
||
#else
|
||
|
||
#define DNS_EINTR EINTR
|
||
#define DNS_EINPROGRESS EINPROGRESS
|
||
#define DNS_EISCONN EISCONN
|
||
#define DNS_EWOULDBLOCK EWOULDBLOCK
|
||
#define DNS_EALREADY EALREADY
|
||
#define DNS_EAGAIN EAGAIN
|
||
#define DNS_ETIMEDOUT ETIMEDOUT
|
||
|
||
#define dns_syerr() errno
|
||
#define dns_soerr() errno
|
||
|
||
#endif
|
||
|
||
|
||
const char *dns_strerror(int error) {
|
||
switch (error) {
|
||
case DNS_ENOBUFS:
|
||
return "DNS packet buffer too small";
|
||
case DNS_EILLEGAL:
|
||
return "Illegal DNS RR name or data";
|
||
case DNS_EORDER:
|
||
return "Attempt to push RR out of section order";
|
||
case DNS_ESECTION:
|
||
return "Invalid section specified";
|
||
case DNS_EUNKNOWN:
|
||
return "Unknown DNS error";
|
||
case DNS_EADDRESS:
|
||
return "Invalid textual address form";
|
||
case DNS_ENOQUERY:
|
||
return "Bad execution state (missing query packet)";
|
||
case DNS_ENOANSWER:
|
||
return "Bad execution state (missing answer packet)";
|
||
case DNS_EFETCHED:
|
||
return "Answer already fetched";
|
||
case DNS_ESERVICE:
|
||
return "The service passed was not recognized for the specified hints";
|
||
case DNS_ENONAME:
|
||
return "The name does not resolve for the supplied parameters";
|
||
case DNS_EFAIL:
|
||
return "A non-recoverable error occurred when attempting to resolve the name";
|
||
case DNS_EEMPTY:
|
||
return "List of returned IP numbers is empty";
|
||
default:
|
||
return strerror(error);
|
||
} /* switch() */
|
||
} /* dns_strerror() */
|
||
|
||
|
||
/*
|
||
* A T O M I C R O U T I N E S
|
||
*
|
||
* Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we
|
||
* can use the preprocessor to detect API and, more importantly, ISA
|
||
* support. We want to avoid linking headaches where the API depends on an
|
||
* external library if the ISA (e.g. i386) doesn't support lockless
|
||
* operation.
|
||
*
|
||
* TODO: Support C11's atomic API. Although that may require some finesse
|
||
* with how we define some public types, such as dns_atomic_t and struct
|
||
* dns_resolv_conf.
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#ifndef HAVE___ATOMIC_FETCH_ADD
|
||
#ifdef __ATOMIC_RELAXED
|
||
#define HAVE___ATOMIC_FETCH_ADD 1
|
||
#else
|
||
#define HAVE___ATOMIC_FETCH_ADD 0
|
||
#endif
|
||
#endif
|
||
|
||
#ifndef HAVE___ATOMIC_FETCH_SUB
|
||
#define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD
|
||
#endif
|
||
|
||
#ifndef DNS_ATOMIC_FETCH_ADD
|
||
#if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2
|
||
#define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED)
|
||
#else
|
||
#pragma message("no atomic_fetch_add available")
|
||
#define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++)
|
||
#endif
|
||
#endif
|
||
|
||
#ifndef DNS_ATOMIC_FETCH_SUB
|
||
#if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2
|
||
#define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED)
|
||
#else
|
||
#pragma message("no atomic_fetch_sub available")
|
||
#define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--)
|
||
#endif
|
||
#endif
|
||
|
||
static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) {
|
||
return DNS_ATOMIC_FETCH_ADD(i);
|
||
} /* dns_atomic_fetch_add() */
|
||
|
||
|
||
static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) {
|
||
return DNS_ATOMIC_FETCH_SUB(i);
|
||
} /* dns_atomic_fetch_sub() */
|
||
|
||
|
||
/*
|
||
* C R Y P T O R O U T I N E S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
/*
|
||
* P R N G
|
||
*/
|
||
|
||
#ifndef DNS_RANDOM
|
||
#if defined(HAVE_ARC4RANDOM) \
|
||
|| defined(__OpenBSD__) \
|
||
|| defined(__FreeBSD__) \
|
||
|| defined(__NetBSD__) \
|
||
|| defined(__APPLE__)
|
||
#define DNS_RANDOM arc4random
|
||
#elif __linux
|
||
#define DNS_RANDOM random
|
||
#else
|
||
#define DNS_RANDOM rand
|
||
#endif
|
||
#endif
|
||
|
||
#define DNS_RANDOM_arc4random 1
|
||
#define DNS_RANDOM_random 2
|
||
#define DNS_RANDOM_rand 3
|
||
#define DNS_RANDOM_RAND_bytes 4
|
||
|
||
#define DNS_RANDOM_OPENSSL (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM))
|
||
|
||
#if DNS_RANDOM_OPENSSL
|
||
#include <openssl/rand.h>
|
||
#endif
|
||
|
||
static unsigned dns_random_(void) {
|
||
#if DNS_RANDOM_OPENSSL
|
||
unsigned r;
|
||
_Bool ok;
|
||
|
||
ok = (1 == RAND_bytes((unsigned char *)&r, sizeof r));
|
||
assert(ok && "1 == RAND_bytes()");
|
||
|
||
return r;
|
||
#else
|
||
return DNS_RANDOM();
|
||
#endif
|
||
} /* dns_random_() */
|
||
|
||
dns_random_f **dns_random_p(void) {
|
||
static dns_random_f *random_f = &dns_random_;
|
||
|
||
return &random_f;
|
||
} /* dns_random_p() */
|
||
|
||
|
||
/*
|
||
* P E R M U T A T I O N G E N E R A T O R
|
||
*/
|
||
|
||
#define DNS_K_TEA_KEY_SIZE 16
|
||
#define DNS_K_TEA_BLOCK_SIZE 8
|
||
#define DNS_K_TEA_CYCLES 32
|
||
#define DNS_K_TEA_MAGIC 0x9E3779B9U
|
||
|
||
struct dns_k_tea {
|
||
uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
|
||
unsigned cycles;
|
||
}; /* struct dns_k_tea */
|
||
|
||
|
||
static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) {
|
||
memcpy(tea->key, key, sizeof tea->key);
|
||
|
||
tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES;
|
||
} /* dns_k_tea_init() */
|
||
|
||
|
||
static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) {
|
||
uint32_t y, z, sum, n;
|
||
|
||
y = v[0];
|
||
z = v[1];
|
||
sum = 0;
|
||
|
||
for (n = 0; n < tea->cycles; n++) {
|
||
sum += DNS_K_TEA_MAGIC;
|
||
y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]);
|
||
z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]);
|
||
}
|
||
|
||
w[0] = y;
|
||
w[1] = z;
|
||
|
||
return /* void */;
|
||
} /* dns_k_tea_encrypt() */
|
||
|
||
|
||
/*
|
||
* Permutation generator, based on a Luby-Rackoff Feistel construction.
|
||
*
|
||
* Specifically, this is a generic balanced Feistel block cipher using TEA
|
||
* (another block cipher) as the pseudo-random function, F. At best it's as
|
||
* strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or
|
||
* perhaps Bernstein's Salsa20 core; I am naively trying to keep things
|
||
* simple.
|
||
*
|
||
* The generator can create a permutation of any set of numbers, as long as
|
||
* the size of the set is an even power of 2. This limitation arises either
|
||
* out of an inherent property of balanced Feistel constructions, or by my
|
||
* own ignorance. I'll tackle an unbalanced construction after I wrap my
|
||
* head around Schneier and Kelsey's paper.
|
||
*
|
||
* CAVEAT EMPTOR. IANAC.
|
||
*/
|
||
#define DNS_K_PERMUTOR_ROUNDS 8
|
||
|
||
struct dns_k_permutor {
|
||
unsigned stepi, length, limit;
|
||
unsigned shift, mask, rounds;
|
||
|
||
struct dns_k_tea tea;
|
||
}; /* struct dns_k_permutor */
|
||
|
||
|
||
static inline unsigned dns_k_permutor_powof(unsigned n) {
|
||
unsigned m, i = 0;
|
||
|
||
for (m = 1; m < n; m <<= 1, i++)
|
||
;;
|
||
|
||
return i;
|
||
} /* dns_k_permutor_powof() */
|
||
|
||
static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) {
|
||
uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
|
||
unsigned width, i;
|
||
|
||
p->stepi = 0;
|
||
|
||
p->length = (high - low) + 1;
|
||
p->limit = high;
|
||
|
||
width = dns_k_permutor_powof(p->length);
|
||
width += width % 2;
|
||
|
||
p->shift = width / 2;
|
||
p->mask = (1U << p->shift) - 1;
|
||
p->rounds = DNS_K_PERMUTOR_ROUNDS;
|
||
|
||
for (i = 0; i < lengthof(key); i++)
|
||
key[i] = dns_random();
|
||
|
||
dns_k_tea_init(&p->tea, key, 0);
|
||
|
||
return /* void */;
|
||
} /* dns_k_permutor_init() */
|
||
|
||
|
||
static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) {
|
||
uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)];
|
||
|
||
memset(in, '\0', sizeof in);
|
||
|
||
in[0] = k;
|
||
in[1] = x;
|
||
|
||
dns_k_tea_encrypt(&p->tea, in, out);
|
||
|
||
return p->mask & out[0];
|
||
} /* dns_k_permutor_F() */
|
||
|
||
|
||
static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) {
|
||
unsigned l[2], r[2];
|
||
unsigned i;
|
||
|
||
i = 0;
|
||
l[i] = p->mask & (n >> p->shift);
|
||
r[i] = p->mask & (n >> 0);
|
||
|
||
do {
|
||
l[(i + 1) % 2] = r[i % 2];
|
||
r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]);
|
||
|
||
i++;
|
||
} while (i < p->rounds - 1);
|
||
|
||
return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
|
||
} /* dns_k_permutor_E() */
|
||
|
||
|
||
DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) {
|
||
unsigned l[2], r[2];
|
||
unsigned i;
|
||
|
||
i = p->rounds - 1;
|
||
l[i % 2] = p->mask & (n >> p->shift);
|
||
r[i % 2] = p->mask & (n >> 0);
|
||
|
||
do {
|
||
i--;
|
||
|
||
r[i % 2] = l[(i + 1) % 2];
|
||
l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]);
|
||
} while (i > 0);
|
||
|
||
return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
|
||
} /* dns_k_permutor_D() */
|
||
|
||
|
||
static unsigned dns_k_permutor_step(struct dns_k_permutor *p) {
|
||
unsigned n;
|
||
|
||
do {
|
||
n = dns_k_permutor_E(p, p->stepi++);
|
||
} while (n >= p->length);
|
||
|
||
return n + (p->limit + 1 - p->length);
|
||
} /* dns_k_permutor_step() */
|
||
|
||
|
||
/*
|
||
* Simple permutation box. Useful for shuffling rrsets from an iterator.
|
||
* Uses AES s-box to provide good diffusion.
|
||
*
|
||
* Seems to pass muster under runs test.
|
||
*
|
||
* $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done
|
||
* $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }'
|
||
* library(lawstat)
|
||
* runs.test(scan(file="/tmp/out"))
|
||
* EOF
|
||
*/
|
||
static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) {
|
||
static const unsigned char sbox[256] =
|
||
{ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
|
||
0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
||
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
|
||
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
||
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
|
||
0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
||
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
|
||
0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
||
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
|
||
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
||
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
|
||
0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
||
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
|
||
0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
||
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
|
||
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
||
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
|
||
0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
||
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
|
||
0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
||
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
|
||
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
||
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
|
||
0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
||
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
|
||
0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
||
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
|
||
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
||
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
|
||
0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
|
||
0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
|
||
unsigned char a, b;
|
||
unsigned i;
|
||
|
||
a = 0xff & (n >> 0);
|
||
b = 0xff & (n >> 8);
|
||
|
||
for (i = 0; i < 4; i++) {
|
||
a ^= 0xff & s;
|
||
a = sbox[a] ^ b;
|
||
b = sbox[b] ^ a;
|
||
s >>= 8;
|
||
}
|
||
|
||
return ((0xff00 & (a << 8)) | (0x00ff & (b << 0)));
|
||
} /* dns_k_shuffle16() */
|
||
|
||
/*
|
||
* S T A T E M A C H I N E R O U T I N E S
|
||
*
|
||
* Application code should define DNS_SM_RESTORE and DNS_SM_SAVE, and the
|
||
* local variable pc.
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#define DNS_SM_ENTER \
|
||
do { \
|
||
static const int pc0 = __LINE__; \
|
||
DNS_SM_RESTORE; \
|
||
switch (pc0 + pc) { \
|
||
case __LINE__: (void)0
|
||
|
||
#define DNS_SM_SAVE_AND_DO(do_statement) \
|
||
do { \
|
||
pc = __LINE__ - pc0; \
|
||
DNS_SM_SAVE; \
|
||
do_statement; \
|
||
case __LINE__: (void)0; \
|
||
} while (0)
|
||
|
||
#define DNS_SM_YIELD(rv) \
|
||
DNS_SM_SAVE_AND_DO(return (rv))
|
||
|
||
#define DNS_SM_EXIT \
|
||
do { goto leave; } while (0)
|
||
|
||
#define DNS_SM_LEAVE \
|
||
leave: (void)0; \
|
||
DNS_SM_SAVE_AND_DO(break); \
|
||
} \
|
||
} while (0)
|
||
|
||
/*
|
||
* U T I L I T Y R O U T I N E S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#define DNS_MAXINTERVAL 300
|
||
|
||
struct dns_clock {
|
||
time_t sample, elapsed;
|
||
}; /* struct dns_clock */
|
||
|
||
static void dns_begin(struct dns_clock *clk) {
|
||
clk->sample = time(0);
|
||
clk->elapsed = 0;
|
||
} /* dns_begin() */
|
||
|
||
static time_t dns_elapsed(struct dns_clock *clk) {
|
||
time_t curtime;
|
||
|
||
if ((time_t)-1 == time(&curtime))
|
||
return clk->elapsed;
|
||
|
||
if (curtime > clk->sample)
|
||
clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL);
|
||
|
||
clk->sample = curtime;
|
||
|
||
return clk->elapsed;
|
||
} /* dns_elapsed() */
|
||
|
||
|
||
DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) {
|
||
size_t n = 0;
|
||
|
||
while (*src++ && n < m)
|
||
++n;
|
||
|
||
return n;
|
||
} /* dns_strnlen() */
|
||
|
||
|
||
DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) {
|
||
size_t len = dns_strnlen(src, max), n;
|
||
|
||
if (lim > 0) {
|
||
n = DNS_PP_MIN(lim - 1, len);
|
||
memcpy(dst, src, n);
|
||
dst[n] = '\0';
|
||
}
|
||
|
||
return len;
|
||
} /* dns_strnlcpy() */
|
||
|
||
|
||
#if (defined AF_UNIX && !defined _WIN32 && !defined(__SWITCH__))
|
||
#define DNS_HAVE_SOCKADDR_UN 1
|
||
#else
|
||
#define DNS_HAVE_SOCKADDR_UN 0
|
||
#endif
|
||
|
||
static size_t dns_af_len(int af) {
|
||
static const size_t table[AF_MAX] = {
|
||
[AF_INET6] = sizeof (struct sockaddr_in6),
|
||
[AF_INET] = sizeof (struct sockaddr_in),
|
||
#if DNS_HAVE_SOCKADDR_UN
|
||
[AF_UNIX] = sizeof (struct sockaddr_un),
|
||
#endif
|
||
};
|
||
|
||
return table[af];
|
||
} /* dns_af_len() */
|
||
|
||
#define dns_sa_family(sa) (((struct sockaddr *)(sa))->sa_family)
|
||
|
||
#define dns_sa_len(sa) dns_af_len(dns_sa_family(sa))
|
||
|
||
|
||
#define DNS_SA_NOPORT &dns_sa_noport
|
||
static unsigned short dns_sa_noport;
|
||
|
||
static unsigned short *dns_sa_port(int af, void *sa) {
|
||
switch (af) {
|
||
case AF_INET6:
|
||
return &((struct sockaddr_in6 *)sa)->sin6_port;
|
||
case AF_INET:
|
||
return &((struct sockaddr_in *)sa)->sin_port;
|
||
default:
|
||
return DNS_SA_NOPORT;
|
||
}
|
||
} /* dns_sa_port() */
|
||
|
||
|
||
static void *dns_sa_addr(int af, const void *sa, socklen_t *size) {
|
||
switch (af) {
|
||
case AF_INET6: {
|
||
struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr;
|
||
|
||
if (size)
|
||
*size = sizeof *in6;
|
||
|
||
return in6;
|
||
}
|
||
case AF_INET: {
|
||
struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr;
|
||
|
||
if (size)
|
||
*size = sizeof *in;
|
||
|
||
return in;
|
||
}
|
||
default:
|
||
if (size)
|
||
*size = 0;
|
||
|
||
return 0;
|
||
}
|
||
} /* dns_sa_addr() */
|
||
|
||
|
||
#if DNS_HAVE_SOCKADDR_UN
|
||
#define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path)
|
||
#endif
|
||
|
||
DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) {
|
||
switch (dns_sa_family(sa)) {
|
||
#if DNS_HAVE_SOCKADDR_UN
|
||
case AF_UNIX: {
|
||
char *path = ((struct sockaddr_un *)sa)->sun_path;
|
||
|
||
if (size)
|
||
*size = dns_strnlen(path, DNS_SUNPATHMAX);
|
||
|
||
return path;
|
||
}
|
||
#endif
|
||
default:
|
||
if (size)
|
||
*size = 0;
|
||
|
||
return NULL;
|
||
}
|
||
} /* dns_sa_path() */
|
||
|
||
|
||
static int dns_sa_cmp(void *a, void *b) {
|
||
int cmp, af;
|
||
|
||
if ((cmp = dns_sa_family(a) - dns_sa_family(b)))
|
||
return cmp;
|
||
|
||
switch ((af = dns_sa_family(a))) {
|
||
case AF_INET: {
|
||
struct in_addr *a4, *b4;
|
||
|
||
if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
|
||
return cmp;
|
||
|
||
a4 = dns_sa_addr(af, a, NULL);
|
||
b4 = dns_sa_addr(af, b, NULL);
|
||
|
||
if (ntohl(a4->s_addr) < ntohl(b4->s_addr))
|
||
return -1;
|
||
if (ntohl(a4->s_addr) > ntohl(b4->s_addr))
|
||
return 1;
|
||
|
||
return 0;
|
||
}
|
||
case AF_INET6: {
|
||
struct in6_addr *a6, *b6;
|
||
size_t i;
|
||
|
||
if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
|
||
return cmp;
|
||
|
||
a6 = dns_sa_addr(af, a, NULL);
|
||
b6 = dns_sa_addr(af, b, NULL);
|
||
|
||
/* XXX: do we need to use in6_clearscope()? */
|
||
for (i = 0; i < sizeof a6->s6_addr; i++) {
|
||
if ((cmp = a6->s6_addr[i] - b6->s6_addr[i]))
|
||
return cmp;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
#if DNS_HAVE_SOCKADDR_UN
|
||
case AF_UNIX: {
|
||
char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path];
|
||
|
||
dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX);
|
||
dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX);
|
||
|
||
return strcmp(a_path, b_path);
|
||
}
|
||
#endif
|
||
default:
|
||
return -1;
|
||
}
|
||
} /* dns_sa_cmp() */
|
||
|
||
|
||
#if _WIN32
|
||
static int dns_inet_pton(int af, const void *src, void *dst) {
|
||
union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
|
||
|
||
u.sin.sin_family = af;
|
||
|
||
if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &(int){ sizeof u }))
|
||
return -1;
|
||
|
||
switch (af) {
|
||
case AF_INET6:
|
||
*(struct in6_addr *)dst = u.sin6.sin6_addr;
|
||
|
||
return 1;
|
||
case AF_INET:
|
||
*(struct in_addr *)dst = u.sin.sin_addr;
|
||
|
||
return 1;
|
||
default:
|
||
return 0;
|
||
}
|
||
} /* dns_inet_pton() */
|
||
|
||
static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) {
|
||
union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
|
||
|
||
/* NOTE: WSAAddressToString will print .sin_port unless zeroed. */
|
||
memset(&u, 0, sizeof u);
|
||
|
||
u.sin.sin_family = af;
|
||
|
||
switch (af) {
|
||
case AF_INET6:
|
||
u.sin6.sin6_addr = *(struct in6_addr *)src;
|
||
break;
|
||
case AF_INET:
|
||
u.sin.sin_addr = *(struct in_addr *)src;
|
||
|
||
break;
|
||
default:
|
||
return 0;
|
||
}
|
||
|
||
if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim))
|
||
return 0;
|
||
|
||
return dst;
|
||
} /* dns_inet_ntop() */
|
||
#else
|
||
#define dns_inet_pton(...) inet_pton(__VA_ARGS__)
|
||
#define dns_inet_ntop(...) inet_ntop(__VA_ARGS__)
|
||
#endif
|
||
|
||
|
||
static dns_error_t dns_pton(int af, const void *src, void *dst) {
|
||
switch (dns_inet_pton(af, src, dst)) {
|
||
case 1:
|
||
return 0;
|
||
case -1:
|
||
return dns_soerr();
|
||
default:
|
||
return DNS_EADDRESS;
|
||
}
|
||
} /* dns_pton() */
|
||
|
||
|
||
static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) {
|
||
return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr();
|
||
} /* dns_ntop() */
|
||
|
||
|
||
size_t dns_strlcpy(char *dst, const char *src, size_t lim) {
|
||
char *d = dst;
|
||
char *e = &dst[lim];
|
||
const char *s = src;
|
||
|
||
if (d < e) {
|
||
do {
|
||
if ('\0' == (*d++ = *s++))
|
||
return s - src - 1;
|
||
} while (d < e);
|
||
|
||
d[-1] = '\0';
|
||
}
|
||
|
||
while (*s++ != '\0')
|
||
;;
|
||
|
||
return s - src - 1;
|
||
} /* dns_strlcpy() */
|
||
|
||
|
||
size_t dns_strlcat(char *dst, const char *src, size_t lim) {
|
||
char *d = memchr(dst, '\0', lim);
|
||
char *e = &dst[lim];
|
||
const char *s = src;
|
||
const char *p;
|
||
|
||
if (d && d < e) {
|
||
do {
|
||
if ('\0' == (*d++ = *s++))
|
||
return d - dst - 1;
|
||
} while (d < e);
|
||
|
||
d[-1] = '\0';
|
||
}
|
||
|
||
p = s;
|
||
|
||
while (*s++ != '\0')
|
||
;;
|
||
|
||
return lim + (s - p - 1);
|
||
} /* dns_strlcat() */
|
||
|
||
|
||
#if _WIN32
|
||
|
||
static char *dns_strsep(char **sp, const char *delim) {
|
||
char *p;
|
||
|
||
if (!(p = *sp))
|
||
return 0;
|
||
|
||
*sp += strcspn(p, delim);
|
||
|
||
if (**sp != '\0') {
|
||
**sp = '\0';
|
||
++*sp;
|
||
} else
|
||
*sp = NULL;
|
||
|
||
return p;
|
||
} /* dns_strsep() */
|
||
|
||
#else
|
||
#define dns_strsep(...) strsep(__VA_ARGS__)
|
||
#endif
|
||
|
||
|
||
#if _WIN32
|
||
#define strcasecmp(...) _stricmp(__VA_ARGS__)
|
||
#define strncasecmp(...) _strnicmp(__VA_ARGS__)
|
||
#endif
|
||
|
||
|
||
static inline _Bool dns_isalpha(unsigned char c) {
|
||
return isalpha(c);
|
||
} /* dns_isalpha() */
|
||
|
||
static inline _Bool dns_isdigit(unsigned char c) {
|
||
return isdigit(c);
|
||
} /* dns_isdigit() */
|
||
|
||
static inline _Bool dns_isalnum(unsigned char c) {
|
||
return isalnum(c);
|
||
} /* dns_isalnum() */
|
||
|
||
static inline _Bool dns_isspace(unsigned char c) {
|
||
return isspace(c);
|
||
} /* dns_isspace() */
|
||
|
||
|
||
static int dns_poll(int fd, short events, int timeout) {
|
||
ENTERING1("dns_poll(%d, %hd, %d)", fd, events, timeout);
|
||
|
||
fd_set rset, wset;
|
||
|
||
if (!events)
|
||
{
|
||
LEAVING("dns_poll (no events) = 0");
|
||
return 0;
|
||
}
|
||
|
||
assert(fd >= 0 && (unsigned)fd < FD_SETSIZE);
|
||
|
||
FD_ZERO(&rset);
|
||
FD_ZERO(&wset);
|
||
|
||
if (events & DNS_POLLIN)
|
||
FD_SET(fd, &rset);
|
||
|
||
if (events & DNS_POLLOUT)
|
||
FD_SET(fd, &wset);
|
||
|
||
CALLING("select()");
|
||
select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &(struct timeval){ timeout, 0 } : NULL);
|
||
|
||
LEAVING("dns_poll (select returned) = 0");
|
||
return 0;
|
||
} /* dns_poll() */
|
||
|
||
|
||
#if !_WIN32
|
||
DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) {
|
||
#if DNS_THREAD_SAFE
|
||
return pthread_sigmask(how, set, oset);
|
||
#else
|
||
return (0 == sigprocmask(how, set, oset))? 0 : errno;
|
||
#endif
|
||
} /* dns_sigmask() */
|
||
#endif
|
||
|
||
|
||
static long dns_send(int fd, const void *src, size_t lim, int flags) {
|
||
ENTERING1("dns_send(%d, %p, %lu, 0x%x)", fd, src, lim, flags);
|
||
|
||
#if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE
|
||
CALLING("send() [1]");
|
||
ssize_t res = send(fd, src, lim, flags);
|
||
LEAVING1("dns_send = %ld", res);
|
||
return res;
|
||
#elif defined MSG_NOSIGNAL
|
||
CALLING("send() [2]");
|
||
ssize_t res = send(fd, src, lim, flags|MSG_NOSIGNAL);
|
||
LEAVING1("dns_send = %ld", res);
|
||
return res;
|
||
#elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */
|
||
/*
|
||
* SIGPIPE handling similar to the approach described in
|
||
* http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html
|
||
*/
|
||
sigset_t pending, blocked, piped;
|
||
long count;
|
||
int saved, error;
|
||
|
||
sigemptyset(&pending);
|
||
sigpending(&pending);
|
||
|
||
if (!sigismember(&pending, SIGPIPE)) {
|
||
sigemptyset(&piped);
|
||
sigaddset(&piped, SIGPIPE);
|
||
sigemptyset(&blocked);
|
||
|
||
if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked)))
|
||
goto error;
|
||
}
|
||
|
||
CALLING("send() [3]");
|
||
count = send(fd, src, lim, flags);
|
||
|
||
if (!sigismember(&pending, SIGPIPE)) {
|
||
saved = errno;
|
||
|
||
if (count == -1 && errno == EPIPE) {
|
||
CALLING("sigtimedwait()");
|
||
while (-1 == sigtimedwait(&piped, NULL, &(struct timespec){ 0, 0 }) && errno == EINTR)
|
||
;;
|
||
}
|
||
|
||
if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL)))
|
||
goto error;
|
||
|
||
errno = saved;
|
||
}
|
||
|
||
LEAVING1("dns_send = %ld", count);
|
||
return count;
|
||
error:
|
||
errno = error;
|
||
|
||
return -1;
|
||
#else
|
||
#error "unable to suppress SIGPIPE"
|
||
return send(fd, src, lim, flags);
|
||
#endif
|
||
} /* dns_send() */
|
||
|
||
|
||
#define DNS_FOPEN_STDFLAGS "rwabt+"
|
||
|
||
static dns_error_t dns_fopen_addflag(char *dst, const char *src, size_t lim, int fc) {
|
||
char *p = dst, *pe = dst + lim;
|
||
|
||
/* copy standard flags */
|
||
while (*src && strchr(DNS_FOPEN_STDFLAGS, *src)) {
|
||
if (!(p < pe))
|
||
return ENOMEM;
|
||
*p++ = *src++;
|
||
}
|
||
|
||
/* append flag to standard flags */
|
||
if (!(p < pe))
|
||
return ENOMEM;
|
||
*p++ = fc;
|
||
|
||
/* copy remaining mode string, including '\0' */
|
||
do {
|
||
if (!(p < pe))
|
||
return ENOMEM;
|
||
} while ((*p++ = *src++));
|
||
|
||
return 0;
|
||
} /* dns_fopen_addflag() */
|
||
|
||
static FILE *dns_fopen(const char *path, const char *mode, dns_error_t *_error) {
|
||
ENTERING1("dns_fopen(\"%s\", \"%s\", %p)", path, mode, _error);
|
||
FILE *fp;
|
||
char mode_cloexec[32];
|
||
int error;
|
||
|
||
assert(path && mode && *mode);
|
||
if (!*path) {
|
||
error = EINVAL;
|
||
goto error;
|
||
}
|
||
|
||
#if _WIN32 || _WIN64
|
||
if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'N')))
|
||
goto error;
|
||
CALLING("fopen() [1]");
|
||
if (!(fp = fopen(path, mode_cloexec)))
|
||
goto syerr;
|
||
#else
|
||
if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'e')))
|
||
goto error;
|
||
CALLING("fopen() [2]");
|
||
if (!(fp = fopen(path, mode_cloexec))) {
|
||
if (errno != EINVAL)
|
||
goto syerr;
|
||
CALLING("fopen() [3]");
|
||
if (!(fp = fopen(path, mode)))
|
||
goto syerr;
|
||
}
|
||
#endif
|
||
|
||
LEAVING("dns_fopen()");
|
||
return fp;
|
||
syerr:
|
||
error = dns_syerr();
|
||
error:
|
||
*_error = error;
|
||
|
||
LEAVING("dns_fopen() = NULL (error)");
|
||
return NULL;
|
||
} /* dns_fopen() */
|
||
|
||
|
||
/*
|
||
* F I X E D - S I Z E D B U F F E R R O U T I N E S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#define DNS_B_INIT(src, n) { \
|
||
(unsigned char *)(src), \
|
||
(unsigned char *)(src), \
|
||
(unsigned char *)(src) + (n), \
|
||
}
|
||
|
||
#define DNS_B_FROM(src, n) DNS_B_INIT((src), (n))
|
||
#define DNS_B_INTO(src, n) DNS_B_INIT((src), (n))
|
||
|
||
struct dns_buf {
|
||
const unsigned char *base;
|
||
unsigned char *p;
|
||
const unsigned char *pe;
|
||
dns_error_t error;
|
||
size_t overflow;
|
||
}; /* struct dns_buf */
|
||
|
||
static inline size_t
|
||
dns_b_tell(struct dns_buf *b)
|
||
{
|
||
return b->p - b->base;
|
||
}
|
||
|
||
static inline dns_error_t
|
||
dns_b_setoverflow(struct dns_buf *b, size_t n, dns_error_t error)
|
||
{
|
||
b->overflow += n;
|
||
return b->error = error;
|
||
}
|
||
|
||
DNS_NOTUSED static struct dns_buf *
|
||
dns_b_into(struct dns_buf *b, void *src, size_t n)
|
||
{
|
||
*b = (struct dns_buf)DNS_B_INTO(src, n);
|
||
|
||
return b;
|
||
}
|
||
|
||
static dns_error_t
|
||
dns_b_putc(struct dns_buf *b, unsigned char uc)
|
||
{
|
||
if (!(b->p < b->pe))
|
||
return dns_b_setoverflow(b, 1, DNS_ENOBUFS);
|
||
|
||
*b->p++ = uc;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static dns_error_t
|
||
dns_b_pputc(struct dns_buf *b, unsigned char uc, size_t p)
|
||
{
|
||
size_t pe = b->pe - b->base;
|
||
if (pe <= p)
|
||
return dns_b_setoverflow(b, p - pe + 1, DNS_ENOBUFS);
|
||
|
||
*((unsigned char *)b->base + p) = uc;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static inline dns_error_t
|
||
dns_b_put16(struct dns_buf *b, uint16_t u)
|
||
{
|
||
return dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
|
||
}
|
||
|
||
static inline dns_error_t
|
||
dns_b_pput16(struct dns_buf *b, uint16_t u, size_t p)
|
||
{
|
||
if (dns_b_pputc(b, u >> 8, p) || dns_b_pputc(b, u >> 0, p + 1))
|
||
return b->error;
|
||
|
||
return 0;
|
||
}
|
||
|
||
DNS_NOTUSED static inline dns_error_t
|
||
dns_b_put32(struct dns_buf *b, uint32_t u)
|
||
{
|
||
return dns_b_putc(b, u >> 24), dns_b_putc(b, u >> 16),
|
||
dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
|
||
}
|
||
|
||
static dns_error_t
|
||
dns_b_put(struct dns_buf *b, const void *src, size_t len)
|
||
{
|
||
size_t n = DNS_PP_MIN((size_t)(b->pe - b->p), len);
|
||
|
||
memcpy(b->p, src, n);
|
||
b->p += n;
|
||
|
||
if (n < len)
|
||
return dns_b_setoverflow(b, len - n, DNS_ENOBUFS);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static dns_error_t
|
||
dns_b_puts(struct dns_buf *b, const void *src)
|
||
{
|
||
return dns_b_put(b, src, strlen(src));
|
||
}
|
||
|
||
DNS_NOTUSED static inline dns_error_t
|
||
dns_b_fmtju(struct dns_buf *b, const uintmax_t u, const unsigned width)
|
||
{
|
||
size_t digits, padding, overflow;
|
||
uintmax_t r;
|
||
unsigned char *tp, *te, tc;
|
||
|
||
digits = 0;
|
||
r = u;
|
||
do {
|
||
digits++;
|
||
r /= 10;
|
||
} while (r);
|
||
|
||
padding = width - DNS_PP_MIN(digits, width);
|
||
overflow = (digits + padding) - DNS_PP_MIN((size_t)(b->pe - b->p), (digits + padding));
|
||
|
||
while (padding--) {
|
||
dns_b_putc(b, '0');
|
||
}
|
||
|
||
digits = 0;
|
||
tp = b->p;
|
||
r = u;
|
||
do {
|
||
if (overflow < ++digits)
|
||
dns_b_putc(b, '0' + (r % 10));
|
||
r /= 10;
|
||
} while (r);
|
||
|
||
te = b->p;
|
||
while (tp < te) {
|
||
tc = *--te;
|
||
*te = *tp;
|
||
*tp++ = tc;
|
||
}
|
||
|
||
return b->error;
|
||
}
|
||
|
||
static void
|
||
dns_b_popc(struct dns_buf *b)
|
||
{
|
||
if (b->overflow && !--b->overflow)
|
||
b->error = 0;
|
||
if (b->p > b->base)
|
||
b->p--;
|
||
}
|
||
|
||
static inline const char *
|
||
dns_b_tolstring(struct dns_buf *b, size_t *n)
|
||
{
|
||
if (b->p < b->pe) {
|
||
*b->p = '\0';
|
||
*n = b->p - b->base;
|
||
|
||
return (const char *)b->base;
|
||
} else if (b->p > b->base) {
|
||
if (b->p[-1] != '\0') {
|
||
dns_b_setoverflow(b, 1, DNS_ENOBUFS);
|
||
b->p[-1] = '\0';
|
||
}
|
||
*n = &b->p[-1] - b->base;
|
||
|
||
return (const char *)b->base;
|
||
} else {
|
||
*n = 0;
|
||
|
||
return "";
|
||
}
|
||
}
|
||
|
||
static inline const char *
|
||
dns_b_tostring(struct dns_buf *b)
|
||
{
|
||
size_t n;
|
||
return dns_b_tolstring(b, &n);
|
||
}
|
||
|
||
static inline size_t
|
||
dns_b_strlen(struct dns_buf *b)
|
||
{
|
||
size_t n;
|
||
dns_b_tolstring(b, &n);
|
||
return n;
|
||
}
|
||
|
||
static inline size_t
|
||
dns_b_strllen(struct dns_buf *b)
|
||
{
|
||
size_t n = dns_b_strlen(b);
|
||
return n + b->overflow;
|
||
}
|
||
|
||
DNS_NOTUSED static const struct dns_buf *
|
||
dns_b_from(const struct dns_buf *b, const void *src, size_t n)
|
||
{
|
||
*(struct dns_buf *)b = (struct dns_buf)DNS_B_FROM(src, n);
|
||
|
||
return b;
|
||
}
|
||
|
||
static inline int
|
||
dns_b_getc(const struct dns_buf *_b, const int eof)
|
||
{
|
||
struct dns_buf *b = (struct dns_buf *)_b;
|
||
|
||
if (!(b->p < b->pe))
|
||
return dns_b_setoverflow(b, 1, DNS_EILLEGAL), eof;
|
||
|
||
return *b->p++;
|
||
}
|
||
|
||
static inline intmax_t
|
||
dns_b_get16(const struct dns_buf *b, const intmax_t eof)
|
||
{
|
||
intmax_t n;
|
||
|
||
n = (dns_b_getc(b, 0) << 8);
|
||
n |= (dns_b_getc(b, 0) << 0);
|
||
|
||
return (!b->overflow)? n : eof;
|
||
}
|
||
|
||
DNS_NOTUSED static inline intmax_t
|
||
dns_b_get32(const struct dns_buf *b, const intmax_t eof)
|
||
{
|
||
intmax_t n;
|
||
|
||
n = (dns_b_get16(b, 0) << 16);
|
||
n |= (dns_b_get16(b, 0) << 0);
|
||
|
||
return (!b->overflow)? n : eof;
|
||
}
|
||
|
||
static inline dns_error_t
|
||
dns_b_move(struct dns_buf *dst, const struct dns_buf *_src, size_t n)
|
||
{
|
||
struct dns_buf *src = (struct dns_buf *)_src;
|
||
size_t src_n = DNS_PP_MIN((size_t)(src->pe - src->p), n);
|
||
size_t src_r = n - src_n;
|
||
|
||
dns_b_put(dst, src->p, src_n);
|
||
src->p += src_n;
|
||
|
||
if (src_r)
|
||
return dns_b_setoverflow(src, src_r, DNS_EILLEGAL);
|
||
|
||
return dst->error;
|
||
}
|
||
|
||
|
||
/*
|
||
* P A C K E T R O U T I N E S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
unsigned dns_p_count(struct dns_packet *P, enum dns_section section) {
|
||
unsigned count;
|
||
|
||
switch (section) {
|
||
case DNS_S_QD:
|
||
return ntohs(dns_header(P)->qdcount);
|
||
case DNS_S_AN:
|
||
return ntohs(dns_header(P)->ancount);
|
||
case DNS_S_NS:
|
||
return ntohs(dns_header(P)->nscount);
|
||
case DNS_S_AR:
|
||
return ntohs(dns_header(P)->arcount);
|
||
default:
|
||
count = 0;
|
||
|
||
if (section & DNS_S_QD)
|
||
count += ntohs(dns_header(P)->qdcount);
|
||
if (section & DNS_S_AN)
|
||
count += ntohs(dns_header(P)->ancount);
|
||
if (section & DNS_S_NS)
|
||
count += ntohs(dns_header(P)->nscount);
|
||
if (section & DNS_S_AR)
|
||
count += ntohs(dns_header(P)->arcount);
|
||
|
||
return count;
|
||
}
|
||
} /* dns_p_count() */
|
||
|
||
|
||
struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) {
|
||
if (!P)
|
||
return 0;
|
||
|
||
assert(size >= offsetof(struct dns_packet, data) + 12);
|
||
|
||
memset(P, 0, sizeof *P);
|
||
P->size = size - offsetof(struct dns_packet, data);
|
||
P->end = 12;
|
||
|
||
memset(P->data, '\0', 12);
|
||
|
||
return P;
|
||
} /* dns_p_init() */
|
||
|
||
|
||
static struct dns_packet *dns_p_reset(struct dns_packet *P) {
|
||
return dns_p_init(P, offsetof(struct dns_packet, data) + P->size);
|
||
} /* dns_p_reset() */
|
||
|
||
|
||
static unsigned short dns_p_qend(struct dns_packet *P) {
|
||
unsigned short qend = 12;
|
||
unsigned i, count = dns_p_count(P, DNS_S_QD);
|
||
|
||
for (i = 0; i < count && qend < P->end; i++) {
|
||
if (P->end == (qend = dns_d_skip(qend, P)))
|
||
goto invalid;
|
||
|
||
if (P->end - qend < 4)
|
||
goto invalid;
|
||
|
||
qend += 4;
|
||
}
|
||
|
||
return DNS_PP_MIN(qend, P->end);
|
||
invalid:
|
||
return P->end;
|
||
} /* dns_p_qend() */
|
||
|
||
|
||
struct dns_packet *dns_p_make(size_t len, int *error) {
|
||
struct dns_packet *P;
|
||
size_t size = dns_p_calcsize(len);
|
||
|
||
if (!(P = dns_p_init(malloc(size), size)))
|
||
*error = dns_syerr();
|
||
|
||
return P;
|
||
} /* dns_p_make() */
|
||
|
||
|
||
static void dns_p_free(struct dns_packet *P) {
|
||
free(P);
|
||
} /* dns_p_free() */
|
||
|
||
|
||
/* convience routine to free any existing packet before storing new packet */
|
||
static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) {
|
||
dns_p_free(*dst);
|
||
|
||
*dst = src;
|
||
|
||
return src;
|
||
} /* dns_p_setptr() */
|
||
|
||
|
||
static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) {
|
||
dns_p_setptr(dst, *src);
|
||
|
||
*src = NULL;
|
||
|
||
return *dst;
|
||
} /* dns_p_movptr() */
|
||
|
||
|
||
int dns_p_grow(struct dns_packet **P) {
|
||
struct dns_packet *tmp;
|
||
size_t size;
|
||
int error;
|
||
|
||
if (!*P) {
|
||
if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error)))
|
||
return error;
|
||
|
||
return 0;
|
||
}
|
||
|
||
size = dns_p_sizeof(*P);
|
||
size |= size >> 1;
|
||
size |= size >> 2;
|
||
size |= size >> 4;
|
||
size |= size >> 8;
|
||
size++;
|
||
|
||
if (size > 65536)
|
||
return DNS_ENOBUFS;
|
||
|
||
if (!(tmp = realloc(*P, dns_p_calcsize(size))))
|
||
return dns_syerr();
|
||
|
||
tmp->size = size;
|
||
*P = tmp;
|
||
|
||
return 0;
|
||
} /* dns_p_grow() */
|
||
|
||
|
||
struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) {
|
||
if (!P)
|
||
return 0;
|
||
|
||
P->end = DNS_PP_MIN(P->size, P0->end);
|
||
|
||
memcpy(P->data, P0->data, P->end);
|
||
|
||
return P;
|
||
} /* dns_p_copy() */
|
||
|
||
|
||
struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) {
|
||
size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0));
|
||
struct dns_packet *M;
|
||
enum dns_section section;
|
||
struct dns_rr rr, mr;
|
||
int error, copy;
|
||
|
||
if (!A && B) {
|
||
A = B;
|
||
Amask = Bmask;
|
||
B = 0;
|
||
}
|
||
|
||
merge:
|
||
if (!(M = dns_p_make(bufsiz, &error)))
|
||
goto error;
|
||
|
||
for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) {
|
||
if (A && (section & Amask)) {
|
||
dns_rr_foreach(&rr, A, .section = section) {
|
||
if ((error = dns_rr_copy(M, &rr, A)))
|
||
goto error;
|
||
}
|
||
}
|
||
|
||
if (B && (section & Bmask)) {
|
||
dns_rr_foreach(&rr, B, .section = section) {
|
||
copy = 1;
|
||
|
||
dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) {
|
||
if (!(copy = dns_rr_cmp(&rr, B, &mr, M)))
|
||
break;
|
||
}
|
||
|
||
if (copy && (error = dns_rr_copy(M, &rr, B)))
|
||
goto error;
|
||
}
|
||
}
|
||
}
|
||
|
||
return M;
|
||
error:
|
||
dns_p_setptr(&M, NULL);
|
||
|
||
if (error == DNS_ENOBUFS && bufsiz < 65535) {
|
||
bufsiz = DNS_PP_MIN(65535, bufsiz * 2);
|
||
|
||
goto merge;
|
||
}
|
||
|
||
*error_ = error;
|
||
|
||
return 0;
|
||
} /* dns_p_merge() */
|
||
|
||
|
||
static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t);
|
||
|
||
void dns_p_dictadd(struct dns_packet *P, unsigned short dn) {
|
||
unsigned short lp, lptr, i;
|
||
|
||
lp = dn;
|
||
|
||
while (lp < P->end) {
|
||
if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) {
|
||
lptr = ((0x3f & P->data[lp + 0]) << 8)
|
||
| ((0xff & P->data[lp + 1]) << 0);
|
||
|
||
for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
|
||
if (P->dict[i] == lptr) {
|
||
P->dict[i] = dn;
|
||
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
lp = dns_l_skip(lp, P->data, P->end);
|
||
}
|
||
|
||
for (i = 0; i < lengthof(P->dict); i++) {
|
||
if (!P->dict[i]) {
|
||
P->dict[i] = dn;
|
||
|
||
break;
|
||
}
|
||
}
|
||
} /* dns_p_dictadd() */
|
||
|
||
|
||
int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) {
|
||
size_t end = P->end;
|
||
int error;
|
||
|
||
if ((error = dns_d_push(P, dn, dnlen)))
|
||
goto error;
|
||
|
||
if (P->size - P->end < 4)
|
||
goto nobufs;
|
||
|
||
P->data[P->end++] = 0xff & (type >> 8);
|
||
P->data[P->end++] = 0xff & (type >> 0);
|
||
|
||
P->data[P->end++] = 0xff & (class >> 8);
|
||
P->data[P->end++] = 0xff & (class >> 0);
|
||
|
||
if (section == DNS_S_QD)
|
||
goto update;
|
||
|
||
if (P->size - P->end < 6)
|
||
goto nobufs;
|
||
|
||
if (type != DNS_T_OPT)
|
||
ttl = DNS_PP_MIN(ttl, 0x7fffffffU);
|
||
P->data[P->end++] = ttl >> 24;
|
||
P->data[P->end++] = ttl >> 16;
|
||
P->data[P->end++] = ttl >> 8;
|
||
P->data[P->end++] = ttl >> 0;
|
||
|
||
if ((error = dns_any_push(P, (union dns_any *)any, type)))
|
||
goto error;
|
||
|
||
update:
|
||
switch (section) {
|
||
case DNS_S_QD:
|
||
if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR))
|
||
goto order;
|
||
|
||
if (!P->memo.qd.base && (error = dns_p_study(P)))
|
||
goto error;
|
||
|
||
dns_header(P)->qdcount = htons(ntohs(dns_header(P)->qdcount) + 1);
|
||
|
||
P->memo.qd.end = P->end;
|
||
P->memo.an.base = P->end;
|
||
P->memo.an.end = P->end;
|
||
P->memo.ns.base = P->end;
|
||
P->memo.ns.end = P->end;
|
||
P->memo.ar.base = P->end;
|
||
P->memo.ar.end = P->end;
|
||
|
||
break;
|
||
case DNS_S_AN:
|
||
if (dns_p_count(P, DNS_S_NS|DNS_S_AR))
|
||
goto order;
|
||
|
||
if (!P->memo.an.base && (error = dns_p_study(P)))
|
||
goto error;
|
||
|
||
dns_header(P)->ancount = htons(ntohs(dns_header(P)->ancount) + 1);
|
||
|
||
P->memo.an.end = P->end;
|
||
P->memo.ns.base = P->end;
|
||
P->memo.ns.end = P->end;
|
||
P->memo.ar.base = P->end;
|
||
P->memo.ar.end = P->end;
|
||
|
||
break;
|
||
case DNS_S_NS:
|
||
if (dns_p_count(P, DNS_S_AR))
|
||
goto order;
|
||
|
||
if (!P->memo.ns.base && (error = dns_p_study(P)))
|
||
goto error;
|
||
|
||
dns_header(P)->nscount = htons(ntohs(dns_header(P)->nscount) + 1);
|
||
|
||
P->memo.ns.end = P->end;
|
||
P->memo.ar.base = P->end;
|
||
P->memo.ar.end = P->end;
|
||
|
||
break;
|
||
case DNS_S_AR:
|
||
if (!P->memo.ar.base && (error = dns_p_study(P)))
|
||
goto error;
|
||
|
||
dns_header(P)->arcount = htons(ntohs(dns_header(P)->arcount) + 1);
|
||
|
||
P->memo.ar.end = P->end;
|
||
|
||
if (type == DNS_T_OPT && !P->memo.opt.p) {
|
||
P->memo.opt.p = end;
|
||
P->memo.opt.maxudp = class;
|
||
P->memo.opt.ttl = ttl;
|
||
}
|
||
|
||
break;
|
||
default:
|
||
error = DNS_ESECTION;
|
||
|
||
goto error;
|
||
} /* switch() */
|
||
|
||
return 0;
|
||
nobufs:
|
||
error = DNS_ENOBUFS;
|
||
|
||
goto error;
|
||
order:
|
||
error = DNS_EORDER;
|
||
|
||
goto error;
|
||
error:
|
||
P->end = end;
|
||
|
||
return error;
|
||
} /* dns_p_push() */
|
||
|
||
|
||
static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) {
|
||
enum dns_section section;
|
||
struct dns_rr rr;
|
||
int error;
|
||
union dns_any any;
|
||
char pretty[sizeof any * 2];
|
||
size_t len;
|
||
|
||
fputs(";; [HEADER]\n", fp);
|
||
fprintf(fp, ";; qid : %d\n", ntohs(dns_header(P)->qid));
|
||
fprintf(fp, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr);
|
||
fprintf(fp, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode);
|
||
fprintf(fp, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa);
|
||
fprintf(fp, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc);
|
||
fprintf(fp, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd);
|
||
fprintf(fp, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra);
|
||
fprintf(fp, ";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P));
|
||
|
||
section = 0;
|
||
|
||
while (dns_rr_grep(&rr, 1, I, P, &error)) {
|
||
if (section != rr.section)
|
||
fprintf(fp, "\n;; [%s:%d]\n", dns_strsection(rr.section), dns_p_count(P, rr.section));
|
||
|
||
if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error)))
|
||
fprintf(fp, "%s\n", pretty);
|
||
|
||
section = rr.section;
|
||
}
|
||
} /* dns_p_dump3() */
|
||
|
||
|
||
void dns_p_dump(struct dns_packet *P, FILE *fp) {
|
||
dns_p_dump3(P, dns_rr_i_new(P, .section = 0), fp);
|
||
} /* dns_p_dump() */
|
||
|
||
|
||
static void dns_s_unstudy(struct dns_s_memo *m)
|
||
{ m->base = 0; m->end = 0; }
|
||
|
||
static void dns_m_unstudy(struct dns_p_memo *m) {
|
||
dns_s_unstudy(&m->qd);
|
||
dns_s_unstudy(&m->an);
|
||
dns_s_unstudy(&m->ns);
|
||
dns_s_unstudy(&m->ar);
|
||
m->opt.p = 0;
|
||
m->opt.maxudp = 0;
|
||
m->opt.ttl = 0;
|
||
} /* dns_m_unstudy() */
|
||
|
||
static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned short base, struct dns_packet *P) {
|
||
unsigned short count, rp;
|
||
|
||
count = dns_p_count(P, section);
|
||
|
||
for (rp = base; count && rp < P->end; count--)
|
||
rp = dns_rr_skip(rp, P);
|
||
|
||
m->base = base;
|
||
m->end = rp;
|
||
|
||
return 0;
|
||
} /* dns_s_study() */
|
||
|
||
static int dns_m_study(struct dns_p_memo *m, struct dns_packet *P) {
|
||
struct dns_rr rr;
|
||
int error;
|
||
|
||
if ((error = dns_s_study(&m->qd, DNS_S_QD, 12, P)))
|
||
goto error;
|
||
if ((error = dns_s_study(&m->an, DNS_S_AN, m->qd.end, P)))
|
||
goto error;
|
||
if ((error = dns_s_study(&m->ns, DNS_S_NS, m->an.end, P)))
|
||
goto error;
|
||
if ((error = dns_s_study(&m->ar, DNS_S_AR, m->ns.end, P)))
|
||
goto error;
|
||
|
||
m->opt.p = 0;
|
||
m->opt.maxudp = 0;
|
||
m->opt.ttl = 0;
|
||
dns_rr_foreach(&rr, P, .type = DNS_T_OPT, .section = DNS_S_AR) {
|
||
m->opt.p = rr.dn.p;
|
||
m->opt.maxudp = rr.class_;
|
||
m->opt.ttl = rr.ttl;
|
||
break;
|
||
}
|
||
|
||
return 0;
|
||
error:
|
||
dns_m_unstudy(m);
|
||
|
||
return error;
|
||
} /* dns_m_study() */
|
||
|
||
int dns_p_study(struct dns_packet *P) {
|
||
return dns_m_study(&P->memo, P);
|
||
} /* dns_p_study() */
|
||
|
||
|
||
enum dns_rcode dns_p_rcode(struct dns_packet *P) {
|
||
return 0xfff & ((P->memo.opt.ttl >> 20) | dns_header(P)->rcode);
|
||
} /* dns_p_rcode() */
|
||
|
||
|
||
/*
|
||
* Q U E R Y P A C K E T R O U T I N E S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#define DNS_Q_RD 0x1 /* recursion desired */
|
||
#define DNS_Q_EDNS0 0x2 /* include OPT RR */
|
||
|
||
static dns_error_t
|
||
dns_q_make2(struct dns_packet **_Q, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass, int qflags)
|
||
{
|
||
struct dns_packet *Q = NULL;
|
||
int error;
|
||
|
||
if (dns_p_movptr(&Q, _Q)) {
|
||
dns_p_reset(Q);
|
||
} else if (!(Q = dns_p_make(DNS_P_QBUFSIZ, &error))) {
|
||
goto error;
|
||
}
|
||
|
||
if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, qtype, qclass, 0, 0)))
|
||
goto error;
|
||
|
||
dns_header(Q)->rd = !!(qflags & DNS_Q_RD);
|
||
|
||
if (qflags & DNS_Q_EDNS0) {
|
||
struct dns_opt opt = DNS_OPT_INIT(&opt);
|
||
|
||
opt.version = 0; /* RFC 6891 version */
|
||
opt.maxudp = 4096;
|
||
|
||
if ((error = dns_p_push(Q, DNS_S_AR, ".", 1, DNS_T_OPT, dns_opt_class(&opt), dns_opt_ttl(&opt), &opt)))
|
||
goto error;
|
||
}
|
||
|
||
*_Q = Q;
|
||
|
||
return 0;
|
||
error:
|
||
dns_p_free(Q);
|
||
|
||
return error;
|
||
}
|
||
|
||
static dns_error_t
|
||
dns_q_make(struct dns_packet **Q, const char *qname, enum dns_type qtype, enum dns_class qclass, int qflags)
|
||
{
|
||
return dns_q_make2(Q, qname, strlen(qname), qtype, qclass, qflags);
|
||
}
|
||
|
||
static dns_error_t
|
||
dns_q_remake(struct dns_packet **Q, int qflags)
|
||
{
|
||
char qname[DNS_D_MAXNAME + 1];
|
||
size_t qlen;
|
||
struct dns_rr rr;
|
||
int error;
|
||
|
||
assert(Q && *Q);
|
||
if ((error = dns_rr_parse(&rr, 12, *Q)))
|
||
return error;
|
||
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, *Q, &error)))
|
||
return error;
|
||
if (qlen >= sizeof qname)
|
||
return DNS_EILLEGAL;
|
||
return dns_q_make2(Q, qname, qlen, rr.type, rr.class_, qflags);
|
||
}
|
||
|
||
/*
|
||
* D O M A I N N A M E R O U T I N E S
|
||
*
|
||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||
|
||
#ifndef DNS_D_MAXPTRS
|
||
#define DNS_D_MAXPTRS 127 /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */
|
||
#endif
|
||
|
||
static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) {
|
||
unsigned short len;
|
||
unsigned nptrs = 0;
|
||
|
||
retry:
|
||
if (src >= end)
|
||
goto invalid;
|
||
|
||
switch (0x03 & (data[src] >> 6)) {
|
||
case 0x00:
|
||
len = (0x3f & (data[src++]));
|
||
|
||
if (end - src < len)
|
||
goto invalid;
|
||
|
||
if (lim > 0) {
|
||
memcpy(dst, &data[src], DNS_PP_MIN(lim, len));
|
||
|
||
dst[DNS_PP_MIN(lim - 1, len)] = '\0';
|
||
}
|
||
|
||
*nxt = src + len;
|
||
|
||
return len;
|
||
case 0x01:
|
||
goto invalid;
|
||
case 0x02:
|
||
goto invalid;
|
||
case 0x03:
|
||
if (++nptrs > DNS_D_MAXPTRS)
|
||
goto invalid;
|
||
|
||
if (end - src < 2)
|
||
goto invalid;
|
||
|
||
src = ((0x3f & data[src + 0]) << 8)
|
||
| ((0xff & data[src + 1]) << 0);
|
||
|
||
goto retry;
|
||
} /* switch() */
|
||
|
||
/* NOT REACHED */
|
||
invalid:
|
||
*nxt = end;
|
||
|
||
return 0;
|
||
} /* dns_l_expand() */
|
||
|
||
|
||
static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) {
|
||
unsigned short len;
|
||
|
||
if (src >= end)
|
||
goto invalid;
|
||
|
||
switch (0x03 & (data[src] >> 6)) {
|
||
case 0x00:
|
||
len = (0x3f & (data[src++]));
|
||
|
||
if (end - src < len)
|
||
goto invalid;
|
||
|
||
return (len)? src + len : end;
|
||
case 0x01:
|
||
goto invalid;
|
||
case 0x02:
|
||
goto invalid;
|
||
case 0x03:
|
||
return end;
|
||
} /* switch() */
|
||
|
||
/* NOT REACHED */
|
||
invalid:
|
||
return end;
|
||
} /* dns_l_skip() */
|
||
|
||
|
||
static _Bool dns_d_isanchored(const void *_src, size_t len) {
|
||
const unsigned char *src = _src;
|
||
return len > 0 && src[len - 1] == '.';
|
||
} /* dns_d_isanchored() */
|
||
|
||
|
||
static size_t dns_d_ndots(const void *_src, size_t len) {
|
||
const unsigned char *p = _src, *pe = p + len;
|
||
size_t ndots = 0;
|
||
|
||
while ((p = memchr(p, '.', pe - p))) {
|
||
ndots++;
|
||
p++;
|
||
}
|
||
|
||
return ndots;
|
||
} /* dns_d_ndots() */
|
||
|
||
|
||
static size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) {
|
||
unsigned char *dst = dst_;
|
||
const unsigned char *src = src_;
|
||
size_t dp = 0, sp = 0;
|
||
int lc;
|
||
|
||
/* trim any leading dot(s) */
|
||
while (sp < len && src[sp] == '.')
|
||
sp++;
|
||
|
||
for (lc = 0; sp < len; lc = src[sp++]) {
|
||
/* trim extra dot(s) */
|
||
if (src[sp] == '.' && lc == '.')
|
||
continue;
|
||
|
||
if (dp < lim)
|
||
dst[dp] = src[sp];
|
||
|
||
dp++;
|
||
}
|
||
|
||
if ((flags & DNS_D_ANCHOR) && lc != '.') {
|
||
if (dp < lim)
|
||
dst[dp] = '.';
|
||
|
||
dp++;
|
||
}
|
||
|
||
if (lim > 0)
|
||
dst[DNS_PP_MIN(dp, lim - 1)] = '\0';
|
||
|
||
return dp;
|
||
} /* dns_d_trim() */
|
||
|
||
|
||
char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) {
|
||
if (flags & DNS_D_TRIM) {
|
||
dns_d_trim(dst, lim, src, len, flags);
|
||
} if (flags & DNS_D_ANCHOR) {
|
||
dns_d_anchor(dst, lim, src, len);
|
||
} else {
|
||
memmove(dst, src, DNS_PP_MIN(lim, len));
|
||
|
||
if (lim > 0)
|
||
((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0';
|
||
}
|
||
|
||
return dst;
|
||
} /* dns_d_init() */
|
||
|
||
|
||
size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) {
|
||
if (len == 0)
|
||
return 0;
|
||
|
||
memmove(dst, src, DNS_PP_MIN(lim, len));
|
||
|
||
if (((const char *)src)[len - 1] != '.') {
|
||
if (len < lim)
|
||
((char *)dst)[len] = '.';
|
||
len++;
|
||
}
|
||
|
||
if (lim > 0)
|
||
((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
|
||
|
||
return len;
|
||
} /* dns_d_anchor() */
|
||
|
||
|
||
size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) {
|
||
const char *dot;
|
||
|
||
/* XXX: Skip any leading dot. Handles cleaving root ".". */
|
||
if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1)))
|
||
return 0;
|
||
|
||
len -= dot - (const char *)src;
|
||
|
||
/* XXX: Unless root, skip the label's trailing dot. */
|
||
if (len > 1) {
|
||
src = ++dot;
|
||
len--;
|
||
} else
|
||
src = dot;
|
||
|
||
memmove(dst, src, DNS_PP_MIN(lim, len));
|
||
|
||
if (lim > 0)
|
||
((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
|
||
|
||
return len;
|
||
} /* dns_d_cleave() */
|
||
|
||
|
||
size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) {
|
||
struct { unsigned char *b; size_t p, x; } dst, src;
|
||
unsigned char ch = '.';
|
||
|
||
dst.b = dst_;
|
||
dst.p = 0;
|
||
dst.x = 1;
|
||
|
||
src.b = (unsigned char *)src_;
|
||
src.p = 0;
|
||
src.x = 0;
|
||
|
||
while (src.x < len) {
|
||
ch = src.b[src.x];
|
||
|
||
if (ch == '.') {
|
||
if (dst.p < lim)
|
||
dst.b[dst.p] = (0x3f & (src.x - src.p));
|
||
|
||
dst.p = dst.x++;
|
||
src.p = ++src.x;
|
||
} else {
|
||
if (dst.x < lim)
|
||
dst.b[dst.x] = ch;
|
||
|
||
dst.x++;
|
||
src.x++;
|
||
}
|
||
} /* while() */
|
||
|
||
if (src.x > src.p) {
|
||
if (dst.p < lim)
|
||
dst.b[dst.p] = (0x3f & (src.x - src.p));
|
||
|
||
dst.p = dst.x;
|
||
}
|
||
|
||
if (dst.p > 1) {
|
||
if (dst.p < lim)
|
||
dst.b[dst.p] = 0x00;
|
||
|
||
dst.p++;
|
||
}
|
||
|
||
#if 1
|
||
if (dst.p < lim) {
|
||
struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b;
|
||
unsigned i;
|
||
|
||
a.p = 0;
|
||
|
||
while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) {
|
||
for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
|
||
b.p = P->dict[i];
|
||
|
||
while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) {
|
||
a.y = a.x;
|
||
b.y = b.x;
|
||
|
||
while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) {
|
||
a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim);
|
||
b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end);
|
||
}
|
||
|
||
if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) {
|
||
dst.b[a.p++] = 0xc0
|
||
| (0x3f & (b.p >> 8));
|
||
dst.b[a.p++] = (0xff & (b.p >> 0));
|
||
|
||
/* silence static analyzers */
|
||
dns_assume(a.p > 0);
|
||
|
||
return a.p;
|
||
}
|
||
|
||
b.p = b.x;
|
||
} /* while() */
|
||
} /* for() */
|
||
|
||
a.p = a.x;
|
||
} /* while() */
|
||
} /* if () */
|
||
#endif
|
||
|
||
if (!dst.p)
|
||
*error = DNS_EILLEGAL;
|
||
|
||
return dst.p;
|
||
} /* dns_d_comp() */
|
||
|
||
|
||
unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) {
|
||
unsigned short len;
|
||
|
||
while (src < P->end) {
|
||
switch (0x03 & (P->data[src] >> 6)) {
|
||
case 0x00: /* FOLLOWS */
|
||
len = (0x3f & P->data[src++]);
|
||
< |