diff options
Diffstat (limited to 'deps/udns/udns_resolver.c')
-rw-r--r-- | deps/udns/udns_resolver.c | 1294 |
1 files changed, 0 insertions, 1294 deletions
diff --git a/deps/udns/udns_resolver.c b/deps/udns/udns_resolver.c deleted file mode 100644 index d083397d4d..0000000000 --- a/deps/udns/udns_resolver.c +++ /dev/null @@ -1,1294 +0,0 @@ -/* $Id: udns_resolver.c,v 1.98 2007/01/10 13:32:33 mjt Exp $ - resolver stuff (main module) - - Copyright (C) 2005 Michael Tokarev <mjt@corpit.ru> - This file is part of UDNS library, an async DNS stub resolver. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library, in file named COPYING.LGPL; if not, - write to the Free Software Foundation, Inc., 59 Temple Place, - Suite 330, Boston, MA 02111-1307 USA - - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif -#ifdef WINDOWS -# include <winsock2.h> /* includes <windows.h> */ -# include <ws2tcpip.h> /* needed for struct in6_addr */ -#else -# include <sys/types.h> -# include <sys/socket.h> -# include <netinet/in.h> -# include <unistd.h> -# include <fcntl.h> -# include <sys/time.h> -# ifdef HAVE_POLL -# include <sys/poll.h> -# else -# ifdef HAVE_SYS_SELECT_H -# include <sys/select.h> -# endif -# endif -# ifdef HAVE_TIMES -# include <sys/times.h> -# endif -# define closesocket(sock) close(sock) -#endif /* !WINDOWS */ - -#include <stdlib.h> -#include <string.h> -#include <time.h> -#include <errno.h> -#include <assert.h> -#include <stddef.h> -#include "udns.h" - -#ifndef EAFNOSUPPORT -# define EAFNOSUPPORT EINVAL -#endif -#ifndef MSG_DONTWAIT -# define MSG_DONTWAIT 0 -#endif - -struct dns_qlink { - struct dns_query *next, *prev; -}; - -struct dns_query { - struct dns_qlink dnsq_link; /* list entry (should be first) */ - unsigned dnsq_origdnl0; /* original query DN len w/o last 0 */ - unsigned dnsq_flags; /* control flags for this query */ - unsigned dnsq_servi; /* index of next server to try */ - unsigned dnsq_servwait; /* bitmask: servers left to wait */ - unsigned dnsq_servskip; /* bitmask: servers to skip */ - unsigned dnsq_servnEDNS0; /* bitmask: servers refusing EDNS0 */ - unsigned dnsq_try; /* number of tries made so far */ - dnscc_t *dnsq_nxtsrch; /* next search pointer @dnsc_srchbuf */ - time_t dnsq_deadline; /* when current try will expire */ - dns_parse_fn *dnsq_parse; /* parse: raw => application */ - dns_query_fn *dnsq_cbck; /* the callback to call when done */ - void *dnsq_cbdata; /* user data for the callback */ -#ifndef NDEBUG - struct dns_ctx *dnsq_ctx; /* the resolver context */ -#endif - /* char fields at the end to avoid padding */ - dnsc_t dnsq_id[2]; /* query ID */ - dnsc_t dnsq_typcls[4]; /* requested RR type+class */ - dnsc_t dnsq_dn[DNS_MAXDN+DNS_DNPAD]; /* the query DN +alignment */ -}; - -/* working with dns_query lists */ - -static __inline void qlist_init(struct dns_qlink *list) { - list->next = list->prev = (struct dns_query *)list; -} - -static __inline int qlist_isempty(const struct dns_qlink *list) { - return list->next == (const struct dns_query *)list ? 1 : 0; -} - -static __inline struct dns_query *qlist_first(struct dns_qlink *list) { - return list->next == (struct dns_query *)list ? 0 : list->next; -} - -static __inline void qlist_remove(struct dns_query *q) { - q->dnsq_link.next->dnsq_link.prev = q->dnsq_link.prev; - q->dnsq_link.prev->dnsq_link.next = q->dnsq_link.next; -} - -/* insert q between prev and next */ -static __inline void -qlist_insert(struct dns_query *q, - struct dns_query *prev, struct dns_query *next) { - q->dnsq_link.next = next; - q->dnsq_link.prev = prev; - prev->dnsq_link.next = next->dnsq_link.prev = q; -} - -static __inline void -qlist_insert_after(struct dns_query *q, struct dns_query *prev) { - qlist_insert(q, prev, prev->dnsq_link.next); -} - -static __inline void -qlist_insert_before(struct dns_query *q, struct dns_query *next) { - qlist_insert(q, next->dnsq_link.prev, next); -} - -static __inline void -qlist_add_tail(struct dns_query *q, struct dns_qlink *top) { - qlist_insert_before(q, (struct dns_query *)top); -} - -static __inline void -qlist_add_head(struct dns_query *q, struct dns_qlink *top) { - qlist_insert_after(q, (struct dns_query *)top); -} - -#define QLIST_FIRST(list, direction) ((list)->direction) -#define QLIST_ISLAST(list, q) ((q) == (struct dns_query*)(list)) -#define QLIST_NEXT(q, direction) ((q)->dnsq_link.direction) - -#define QLIST_FOR_EACH(list, q, direction) \ - for(q = QLIST_FIRST(list, direction); \ - !QLIST_ISLAST(list, q); q = QLIST_NEXT(q, direction)) - -union sockaddr_ns { - struct sockaddr sa; - struct sockaddr_in sin; -#ifdef HAVE_IPv6 - struct sockaddr_in6 sin6; -#endif -}; - -#define sin_eq(a,b) \ - ((a).sin_port == (b).sin_port && \ - (a).sin_addr.s_addr == (b).sin_addr.s_addr) -#define sin6_eq(a,b) \ - ((a).sin6_port == (b).sin6_port && \ - memcmp(&(a).sin6_addr, &(b).sin6_addr, sizeof(struct in6_addr)) == 0) - -struct dns_ctx { /* resolver context */ - /* settings */ - unsigned dnsc_flags; /* various flags */ - unsigned dnsc_timeout; /* timeout (base value) for queries */ - unsigned dnsc_ntries; /* number of retries */ - unsigned dnsc_ndots; /* ndots to assume absolute name */ - unsigned dnsc_port; /* default port (DNS_PORT) */ - unsigned dnsc_udpbuf; /* size of UDP buffer */ - /* array of nameserver addresses */ - union sockaddr_ns dnsc_serv[DNS_MAXSERV]; - unsigned dnsc_nserv; /* number of nameservers */ - unsigned dnsc_salen; /* length of socket addresses */ - dnsc_t dnsc_srchbuf[1024]; /* buffer for searchlist */ - dnsc_t *dnsc_srchend; /* current end of srchbuf */ - - dns_utm_fn *dnsc_utmfn; /* register/cancel timer events */ - void *dnsc_utmctx; /* user timer context for utmfn() */ - time_t dnsc_utmexp; /* when user timer expires */ - - dns_dbgfn *dnsc_udbgfn; /* debugging function */ - - /* dynamic data */ - unsigned dnsc_nextid; /* next queue ID to use */ - int dnsc_udpsock; /* UDP socket */ - struct dns_qlink dnsc_qactive; /* active list sorted by deadline */ - int dnsc_nactive; /* number entries in dnsc_qactive */ - dnsc_t *dnsc_pbuf; /* packet buffer (udpbuf size) */ - int dnsc_qstatus; /* last query status value */ -}; - -static const struct { - const char *name; - enum dns_opt opt; - unsigned offset; - unsigned min, max; -} dns_opts[] = { -#define opt(name,opt,field,min,max) \ - {name,opt,offsetof(struct dns_ctx,field),min,max} - opt("retrans", DNS_OPT_TIMEOUT, dnsc_timeout, 1,300), - opt("timeout", DNS_OPT_TIMEOUT, dnsc_timeout, 1,300), - opt("retry", DNS_OPT_NTRIES, dnsc_ntries, 1,50), - opt("attempts", DNS_OPT_NTRIES, dnsc_ntries, 1,50), - opt("ndots", DNS_OPT_NDOTS, dnsc_ndots, 0,1000), - opt("port", DNS_OPT_PORT, dnsc_port, 1,0xffff), - opt("udpbuf", DNS_OPT_UDPSIZE, dnsc_udpbuf, DNS_MAXPACKET,65536), -#undef opt -}; -#define dns_ctxopt(ctx,idx) (*((unsigned*)(((char*)ctx)+dns_opts[idx].offset))) - -#define ISSPACE(x) (x == ' ' || x == '\t' || x == '\r' || x == '\n') - -struct dns_ctx dns_defctx; - -#define SETCTX(ctx) if (!ctx) ctx = &dns_defctx -#define SETCTXINITED(ctx) SETCTX(ctx); assert(CTXINITED(ctx)) -#define CTXINITED(ctx) (ctx->dnsc_flags & DNS_INITED) -#define SETCTXFRESH(ctx) SETCTXINITED(ctx); assert(!CTXOPEN(ctx)) -#define SETCTXINACTIVE(ctx) \ - SETCTXINITED(ctx); assert(!ctx->dnsc_nactive) -#define SETCTXOPEN(ctx) SETCTXINITED(ctx); assert(CTXOPEN(ctx)) -#define CTXOPEN(ctx) (ctx->dnsc_udpsock >= 0) - -#if defined(NDEBUG) || !defined(DEBUG) -#define dns_assert_ctx(ctx) -#else -static void dns_assert_ctx(const struct dns_ctx *ctx) { - int nactive = 0; - const struct dns_query *q; - QLIST_FOR_EACH(&ctx->dnsc_qactive, q, next) { - assert(q->dnsq_ctx == ctx); - assert(q->dnsq_link.next->dnsq_link.prev == q); - assert(q->dnsq_link.prev->dnsq_link.next == q); - ++nactive; - } - assert(nactive == ctx->dnsc_nactive); -} -#endif - -enum { - DNS_INTERNAL = 0xffff, /* internal flags mask */ - DNS_INITED = 0x0001, /* the context is initialized */ - DNS_ASIS_DONE = 0x0002, /* search: skip the last as-is query */ - DNS_SEEN_NODATA = 0x0004, /* search: NODATA has been received */ -}; - -int dns_add_serv(struct dns_ctx *ctx, const char *serv) { - union sockaddr_ns *sns; - SETCTXFRESH(ctx); - if (!serv) - return (ctx->dnsc_nserv = 0); - if (ctx->dnsc_nserv >= DNS_MAXSERV) - return errno = ENFILE, -1; - sns = &ctx->dnsc_serv[ctx->dnsc_nserv]; - memset(sns, 0, sizeof(*sns)); - if (dns_pton(AF_INET, serv, &sns->sin.sin_addr) > 0) { - sns->sin.sin_family = AF_INET; - return ++ctx->dnsc_nserv; - } -#ifdef HAVE_IPv6 - if (dns_pton(AF_INET6, serv, &sns->sin6.sin6_addr) > 0) { - sns->sin6.sin6_family = AF_INET6; - return ++ctx->dnsc_nserv; - } -#endif - errno = EINVAL; - return -1; -} - -int dns_add_serv_s(struct dns_ctx *ctx, const struct sockaddr *sa) { - SETCTXFRESH(ctx); - if (!sa) - return (ctx->dnsc_nserv = 0); - if (ctx->dnsc_nserv >= DNS_MAXSERV) - return errno = ENFILE, -1; -#ifdef HAVE_IPv6 - else if (sa->sa_family == AF_INET6) - ctx->dnsc_serv[ctx->dnsc_nserv].sin6 = *(struct sockaddr_in6*)sa; -#endif - else if (sa->sa_family == AF_INET) - ctx->dnsc_serv[ctx->dnsc_nserv].sin = *(struct sockaddr_in*)sa; - else - return errno = EAFNOSUPPORT, -1; - return ++ctx->dnsc_nserv; -} - -int dns_set_opts(struct dns_ctx *ctx, const char *opts) { - unsigned i, v; - SETCTXINACTIVE(ctx); - for(;;) { - while(ISSPACE(*opts)) ++opts; - if (!*opts) break; - for(i = 0; i < sizeof(dns_opts)/sizeof(dns_opts[0]); ++i) { - v = strlen(dns_opts[i].name); - if (strncmp(dns_opts[i].name, opts, v) != 0 || - (opts[v] != ':' && opts[v] != '=')) - continue; - opts += v + 1; - v = 0; - if (*opts < '0' || *opts > '9') break; - do v = v * 10 + (*opts++ - '0'); - while (*opts >= '0' && *opts <= '9'); - if (v < dns_opts[i].min) v = dns_opts[i].min; - if (v > dns_opts[i].max) v = dns_opts[i].max; - dns_ctxopt(ctx, i) = v; - break; - } - while(*opts && !ISSPACE(*opts)) ++opts; - } - return 0; -} - -int dns_set_opt(struct dns_ctx *ctx, enum dns_opt opt, int val) { - int prev; - unsigned i; - SETCTXINACTIVE(ctx); - for(i = 0; i < sizeof(dns_opts)/sizeof(dns_opts[0]); ++i) { - if (dns_opts[i].opt != opt) continue; - prev = dns_ctxopt(ctx, i); - if (val >= 0) { - unsigned v = val; - if (v < dns_opts[i].min || v > dns_opts[i].max) { - errno = EINVAL; - return -1; - } - dns_ctxopt(ctx, i) = v; - } - return prev; - } - if (opt == DNS_OPT_FLAGS) { - prev = ctx->dnsc_flags & ~DNS_INTERNAL; - if (val >= 0) - ctx->dnsc_flags = - (ctx->dnsc_flags & DNS_INTERNAL) | (val & ~DNS_INTERNAL); - return prev; - } - errno = ENOSYS; - return -1; -} - -int dns_add_srch(struct dns_ctx *ctx, const char *srch) { - int dnl; - SETCTXINACTIVE(ctx); - if (!srch) { - memset(ctx->dnsc_srchbuf, 0, sizeof(ctx->dnsc_srchbuf)); - ctx->dnsc_srchend = ctx->dnsc_srchbuf; - return 0; - } - dnl = - sizeof(ctx->dnsc_srchbuf) - (ctx->dnsc_srchend - ctx->dnsc_srchbuf) - 1; - dnl = dns_sptodn(srch, ctx->dnsc_srchend, dnl); - if (dnl > 0) - ctx->dnsc_srchend += dnl; - ctx->dnsc_srchend[0] = '\0'; /* we ensure the list is always ends at . */ - if (dnl > 0) - return 0; - errno = EINVAL; - return -1; -} - -static void dns_drop_utm(struct dns_ctx *ctx) { - if (ctx->dnsc_utmfn) - ctx->dnsc_utmfn(NULL, -1, ctx->dnsc_utmctx); - ctx->dnsc_utmctx = NULL; - ctx->dnsc_utmexp = -1; -} - -static void -_dns_request_utm(struct dns_ctx *ctx, time_t now) { - struct dns_query *q; - time_t deadline; - int timeout; - q = qlist_first(&ctx->dnsc_qactive); - if (!q) - deadline = -1, timeout = -1; - else if (!now || q->dnsq_deadline <= now) - deadline = 0, timeout = 0; - else - deadline = q->dnsq_deadline, timeout = (int)(deadline - now); - if (ctx->dnsc_utmexp == deadline) - return; - ctx->dnsc_utmfn(ctx, timeout, ctx->dnsc_utmctx); - ctx->dnsc_utmexp = deadline; -} - -static __inline void -dns_request_utm(struct dns_ctx *ctx, time_t now) { - if (ctx->dnsc_utmfn) - _dns_request_utm(ctx, now); -} - -void dns_set_dbgfn(struct dns_ctx *ctx, dns_dbgfn *dbgfn) { - SETCTXINITED(ctx); - ctx->dnsc_udbgfn = dbgfn; -} - -void -dns_set_tmcbck(struct dns_ctx *ctx, dns_utm_fn *fn, void *data) { - SETCTXINITED(ctx); - dns_drop_utm(ctx); - ctx->dnsc_utmfn = fn; - ctx->dnsc_utmctx = data; - if (CTXOPEN(ctx)) - dns_request_utm(ctx, 0); -} - -unsigned dns_random16(void) { -#ifdef WINDOWS - FILETIME ft; - GetSystemTimeAsFileTime(&ft); -#define x (ft.dwLowDateTime) -#else - struct timeval tv; - gettimeofday(&tv, NULL); -#define x (tv.tv_usec) -#endif - return ((unsigned)x ^ ((unsigned)x >> 16)) & 0xffff; -#undef x -} - -void dns_close(struct dns_ctx *ctx) { - struct dns_query *q; - SETCTX(ctx); - if (CTXINITED(ctx)) { - if (ctx->dnsc_udpsock >= 0) - closesocket(ctx->dnsc_udpsock); - ctx->dnsc_udpsock = -1; - if (ctx->dnsc_pbuf) - free(ctx->dnsc_pbuf); - ctx->dnsc_pbuf = NULL; - while((q = qlist_first(&ctx->dnsc_qactive)) != NULL) { - qlist_remove(q); - free(q); - } - ctx->dnsc_nactive = 0; - dns_drop_utm(ctx); - } -} - -void dns_reset(struct dns_ctx *ctx) { - SETCTX(ctx); - dns_close(ctx); - memset(ctx, 0, sizeof(*ctx)); - ctx->dnsc_timeout = 4; - ctx->dnsc_ntries = 3; - ctx->dnsc_ndots = 1; - ctx->dnsc_udpbuf = DNS_EDNS0PACKET; - ctx->dnsc_port = DNS_PORT; - ctx->dnsc_udpsock = -1; - ctx->dnsc_srchend = ctx->dnsc_srchbuf; - qlist_init(&ctx->dnsc_qactive); - ctx->dnsc_nextid = dns_random16(); - ctx->dnsc_flags = DNS_INITED; -} - -struct dns_ctx *dns_new(const struct dns_ctx *copy) { - struct dns_ctx *ctx; - SETCTXINITED(copy); - dns_assert_ctx(copy); - ctx = malloc(sizeof(*ctx)); - if (!ctx) - return NULL; - *ctx = *copy; - ctx->dnsc_udpsock = -1; - qlist_init(&ctx->dnsc_qactive); - ctx->dnsc_nactive = 0; - ctx->dnsc_pbuf = NULL; - ctx->dnsc_qstatus = 0; - ctx->dnsc_utmfn = NULL; - ctx->dnsc_utmctx = NULL; - ctx->dnsc_nextid = dns_random16(); - return ctx; -} - -void dns_free(struct dns_ctx *ctx) { - assert(ctx != NULL && ctx != &dns_defctx); - dns_reset(ctx); - free(ctx); -} - -int dns_open(struct dns_ctx *ctx) { - int sock; - unsigned i; - int port; - union sockaddr_ns *sns; -#ifdef HAVE_IPv6 - unsigned have_inet6 = 0; -#endif - - SETCTXINITED(ctx); - assert(!CTXOPEN(ctx)); - - port = htons((unsigned short)ctx->dnsc_port); - /* ensure we have at least one server */ - if (!ctx->dnsc_nserv) { - sns = ctx->dnsc_serv; - sns->sin.sin_family = AF_INET; - sns->sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - ctx->dnsc_nserv = 1; - } - - for (i = 0; i < ctx->dnsc_nserv; ++i) { - sns = &ctx->dnsc_serv[i]; - /* set port for each sockaddr */ -#ifdef HAVE_IPv6 - if (sns->sa.sa_family == AF_INET6) { - if (!sns->sin6.sin6_port) sns->sin6.sin6_port = (unsigned short)port; - ++have_inet6; - } - else -#endif - { - assert(sns->sa.sa_family == AF_INET); - if (!sns->sin.sin_port) sns->sin.sin_port = (unsigned short)port; - } - } - -#ifdef HAVE_IPv6 - if (have_inet6 && have_inet6 < ctx->dnsc_nserv) { - /* convert all IPv4 addresses to IPv6 V4MAPPED */ - struct sockaddr_in6 sin6; - memset(&sin6, 0, sizeof(sin6)); - sin6.sin6_family = AF_INET6; - /* V4MAPPED: ::ffff:1.2.3.4 */ - sin6.sin6_addr.s6_addr[10] = 0xff; - sin6.sin6_addr.s6_addr[11] = 0xff; - for(i = 0; i < ctx->dnsc_nserv; ++i) { - sns = &ctx->dnsc_serv[i]; - if (sns->sa.sa_family == AF_INET) { - sin6.sin6_port = sns->sin.sin_port; - ((struct in_addr*)&sin6.sin6_addr)[3] = sns->sin.sin_addr; - sns->sin6 = sin6; - } - } - } - - ctx->dnsc_salen = have_inet6 ? - sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); - - if (have_inet6) - sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); - else - sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); -#else /* !HAVE_IPv6 */ - sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); - ctx->dnsc_salen = sizeof(struct sockaddr_in); -#endif /* HAVE_IPv6 */ - - if (sock < 0) { - ctx->dnsc_qstatus = DNS_E_TEMPFAIL; - return -1; - } -#ifdef WINDOWS - { unsigned long on = 1; - if (ioctlsocket(sock, FIONBIO, &on) == SOCKET_ERROR) { - closesocket(sock); - ctx->dnsc_qstatus = DNS_E_TEMPFAIL; - return -1; - } - } -#else /* !WINDOWS */ - if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK) < 0 || - fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) { - closesocket(sock); - ctx->dnsc_qstatus = DNS_E_TEMPFAIL; - return -1; - } -#endif /* WINDOWS */ - /* allocate the packet buffer */ - if ((ctx->dnsc_pbuf = malloc(ctx->dnsc_udpbuf)) == NULL) { - closesocket(sock); - ctx->dnsc_qstatus = DNS_E_NOMEM; - errno = ENOMEM; - return -1; - } - - ctx->dnsc_udpsock = sock; - dns_request_utm(ctx, 0); - return sock; -} - -int dns_sock(const struct dns_ctx *ctx) { - SETCTXINITED(ctx); - return ctx->dnsc_udpsock; -} - -int dns_active(const struct dns_ctx *ctx) { - SETCTXINITED(ctx); - dns_assert_ctx(ctx); - return ctx->dnsc_nactive; -} - -int dns_status(const struct dns_ctx *ctx) { - SETCTX(ctx); - return ctx->dnsc_qstatus; -} -void dns_setstatus(struct dns_ctx *ctx, int status) { - SETCTX(ctx); - ctx->dnsc_qstatus = status; -} - -/* End the query: disconnect it from the active list, free it, - * and return the result to the caller. - */ -static void -dns_end_query(struct dns_ctx *ctx, struct dns_query *q, - int status, void *result) { - dns_query_fn *cbck = q->dnsq_cbck; - void *cbdata = q->dnsq_cbdata; - ctx->dnsc_qstatus = status; - assert((status < 0 && result == 0) || (status >= 0 && result != 0)); - assert(cbck != 0); /*XXX callback may be NULL */ - assert(ctx->dnsc_nactive > 0); - --ctx->dnsc_nactive; - qlist_remove(q); - /* force the query to be unconnected */ - /*memset(q, 0, sizeof(*q));*/ -#ifndef NDEBUG - q->dnsq_ctx = NULL; -#endif - free(q); - cbck(ctx, result, cbdata); -} - -#define DNS_DBG(ctx, code, sa, slen, pkt, plen) \ - do { \ - if (ctx->dnsc_udbgfn) \ - ctx->dnsc_udbgfn(code, (sa), slen, pkt, plen, 0, 0); \ - } while(0) -#define DNS_DBGQ(ctx, q, code, sa, slen, pkt, plen) \ - do { \ - if (ctx->dnsc_udbgfn) \ - ctx->dnsc_udbgfn(code, (sa), slen, pkt, plen, q, q->dnsq_cbdata); \ - } while(0) - -static void dns_newid(struct dns_ctx *ctx, struct dns_query *q) { - /* this is how we choose an identifier for a new query (qID). - * For now, it's just sequential number, incremented for every query, and - * thus obviously trivial to guess. - * There are two choices: - * a) use sequential numbers. It is plain insecure. In DNS, there are two - * places where random numbers are (or can) be used to increase security: - * random qID and random source port number. Without this randomness - * (udns uses fixed port for all queries), or when the randomness is weak, - * it's trivial to spoof query replies. With randomness however, it - * becomes a bit more difficult task. Too bad we only have 16 bits for - * our security, as qID is only two bytes. It isn't a security per se, - * to rely on those 16 bits - an attacker can just flood us with fake - * replies with all possible qIDs (only 65536 of them), and in this case, - * even if we'll use true random qIDs, we'll be in trouble (not protected - * against spoofing). Yes, this is only possible on a high-speed network - * (probably on the LAN only, since usually a border router for a LAN - * protects internal machines from packets with spoofed local addresses - * from outside, and usually a nameserver resides on LAN), but it's - * still very well possible to send us fake replies. - * In other words: there's nothing a DNS (stub) resolver can do against - * spoofing attacks, unless DNSSEC is in use, which helps here alot. - * Too bad that DNSSEC isn't widespread, so relying on it isn't an - * option in almost all cases... - * b) use random qID, based on some random-number generation mechanism. - * This way, we increase our protection a bit (see above - it's very weak - * still), but we also increase risk of qID reuse and matching late replies - * that comes to queries we've sent before against new queries. There are - * some more corner cases around that, as well - for example, normally, - * udns tries to find the query for a given reply by qID, *and* by - * verifying that the query DN and other parameters are also the same - * (so if the new query is against another domain name, old reply will - * be ignored automatically). But certain types of replies which we now - * handle - for example, FORMERR reply from servers which refuses to - * process EDNS0-enabled packets - comes without all the query parameters - * but the qID - so we're forced to use qID only when determining which - * query the given reply corresponds to. This makes us even more - * vulnerable to spoofing attacks, because an attacker don't even need to - * know which queries we perform to spoof the replies - he only needs to - * flood us with fake FORMERR "replies". - * - * That all to say: using sequential (or any other trivially guessable) - * numbers for qIDs is insecure, but the whole thing is inherently insecure - * as well, and this "extra weakness" that comes from weak qID choosing - * algorithm adds almost nothing to the underlying problem. - * - * It CAN NOT be made secure. Period. That's it. - * Unless we choose to implement DNSSEC, which is a whole different story. - * Forcing TCP mode makes it better, but who uses TCP for DNS anyway? - * (and it's hardly possible because of huge impact on the recursive - * nameservers). - * - * Note that ALL stub resolvers (again, unless they implement and enforce - * DNSSEC) suffers from this same problem. - * - * So, instead of trying to be more secure (which actually is not - false - * sense of security is - I think - is worse than no security), I'm trying - * to be more robust here (by preventing qID reuse, which helps in normal - * conditions). And use sequential qID generation scheme. - */ - dns_put16(q->dnsq_id, ctx->dnsc_nextid++); - /* reset all parameters relevant for previous query lifetime */ - q->dnsq_try = 0; - q->dnsq_servi = 0; - /*XXX probably should keep dnsq_servnEDNS0 bits? - * See also comments in dns_ioevent() about FORMERR case */ - q->dnsq_servwait = q->dnsq_servskip = q->dnsq_servnEDNS0 = 0; -} - -/* Find next search suffix and fills in q->dnsq_dn. - * Return 0 if no more to try. */ -static int dns_next_srch(struct dns_ctx *ctx, struct dns_query *q) { - unsigned dnl; - - for(;;) { - if (q->dnsq_nxtsrch > ctx->dnsc_srchend) - return 0; - dnl = dns_dnlen(q->dnsq_nxtsrch); - if (dnl + q->dnsq_origdnl0 <= DNS_MAXDN && - (*q->dnsq_nxtsrch || !(q->dnsq_flags & DNS_ASIS_DONE))) - break; - q->dnsq_nxtsrch += dnl; - } - memcpy(q->dnsq_dn + q->dnsq_origdnl0, q->dnsq_nxtsrch, dnl); - if (!*q->dnsq_nxtsrch) - q->dnsq_flags |= DNS_ASIS_DONE; - q->dnsq_nxtsrch += dnl; - dns_newid(ctx, q); /* new ID for new qDN */ - return 1; -} - -/* find the server to try for current iteration. - * Note that current dnsq_servi may point to a server we should skip -- - * in that case advance to the next server. - * Return true if found, false if all tried. - */ -static int dns_find_serv(const struct dns_ctx *ctx, struct dns_query *q) { - while(q->dnsq_servi < ctx->dnsc_nserv) { - if (!(q->dnsq_servskip & (1 << q->dnsq_servi))) - return 1; - ++q->dnsq_servi; - } - return 0; -} - -/* format and send the query to a given server. - * In case of network problem (sendto() fails), return -1, - * else return 0. - */ -static int -dns_send_this(struct dns_ctx *ctx, struct dns_query *q, - unsigned servi, time_t now) { - unsigned qlen; - unsigned tries; - - { /* format the query buffer */ - dnsc_t *p = ctx->dnsc_pbuf; - memset(p, 0, DNS_HSIZE); - if (!(q->dnsq_flags & DNS_NORD)) p[DNS_H_F1] |= DNS_HF1_RD; - if (q->dnsq_flags & DNS_AAONLY) p[DNS_H_F1] |= DNS_HF1_AA; - p[DNS_H_QDCNT2] = 1; - memcpy(p + DNS_H_QID, q->dnsq_id, 2); - p = dns_payload(p); - /* copy query dn */ - p += dns_dntodn(q->dnsq_dn, p, DNS_MAXDN); - /* query type and class */ - memcpy(p, q->dnsq_typcls, 4); p += 4; - /* add EDNS0 size record */ - if (ctx->dnsc_udpbuf > DNS_MAXPACKET && - !(q->dnsq_servnEDNS0 & (1 << servi))) { - *p++ = 0; /* empty (root) DN */ - p = dns_put16(p, DNS_T_OPT); - p = dns_put16(p, ctx->dnsc_udpbuf); - /* EDNS0 RCODE & VERSION; rest of the TTL field; RDLEN */ - memset(p, 0, 2+2+2); p += 2+2+2; - ctx->dnsc_pbuf[DNS_H_ARCNT2] = 1; - } - qlen = p - ctx->dnsc_pbuf; - assert(qlen <= ctx->dnsc_udpbuf); - } - - /* send the query */ - tries = 10; - while (sendto(ctx->dnsc_udpsock, (void*)ctx->dnsc_pbuf, qlen, 0, - &ctx->dnsc_serv[servi].sa, ctx->dnsc_salen) < 0) { - /*XXX just ignore the sendto() error for now and try again. - * In the future, it may be possible to retrieve the error code - * and find which operation/query failed. - *XXX try the next server too? (if ENETUNREACH is returned immediately) - */ - if (--tries) continue; - /* if we can't send the query, fail it. */ - dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0); - return -1; - } - DNS_DBGQ(ctx, q, 1, - &ctx->dnsc_serv[servi].sa, sizeof(union sockaddr_ns), - ctx->dnsc_pbuf, qlen); - q->dnsq_servwait |= 1 << servi; /* expect reply from this ns */ - - q->dnsq_deadline = now + - (dns_find_serv(ctx, q) ? 1 : ctx->dnsc_timeout << q->dnsq_try); - - /* move the query to the proper place, according to the new deadline */ - qlist_remove(q); - { /* insert from the tail */ - struct dns_query *p; - QLIST_FOR_EACH(&ctx->dnsc_qactive, p, prev) - if (p->dnsq_deadline <= q->dnsq_deadline) - break; - qlist_insert_after(q, p); - } - - return 0; -} - -/* send the query out using next available server - * and add it to the active list, or, if no servers available, - * end it. - */ -static void -dns_send(struct dns_ctx *ctx, struct dns_query *q, time_t now) { - - /* if we can't send the query, return TEMPFAIL even when searching: - * we can't be sure whenever the name we tried to search exists or not, - * so don't continue searching, or we may find the wrong name. */ - - if (!dns_find_serv(ctx, q)) { - /* no more servers in this iteration. Try the next cycle */ - q->dnsq_servi = 0; /* reset */ - q->dnsq_try++; /* next try */ - if (q->dnsq_try >= ctx->dnsc_ntries || - !dns_find_serv(ctx, q)) { - /* no more servers and tries, fail the query */ - /* return TEMPFAIL even when searching: no more tries for this - * searchlist, and no single definitive reply (handled in dns_ioevent() - * in NOERROR or NXDOMAIN cases) => all nameservers failed to process - * current search list element, so we don't know whenever the name exists. - */ - dns_end_query(ctx, q, DNS_E_TEMPFAIL, 0); - return; - } - } - - dns_send_this(ctx, q, q->dnsq_servi++, now); -} - -static void dns_dummy_cb(struct dns_ctx *ctx, void *result, void *data) { - if (result) free(result); - data = ctx = 0; /* used */ -} - -/* The (only, main, real) query submission routine. - * Allocate new query structure, initialize it, check validity of - * parameters, and add it to the head of the active list, without - * trying to send it (to be picked up on next event). - * Error return (without calling the callback routine) - - * no memory or wrong parameters. - *XXX The `no memory' case probably should go to the callback anyway... - */ -struct dns_query * -dns_submit_dn(struct dns_ctx *ctx, - dnscc_t *dn, int qcls, int qtyp, int flags, - dns_parse_fn *parse, dns_query_fn *cbck, void *data) { - struct dns_query *q; - SETCTXOPEN(ctx); - dns_assert_ctx(ctx); - - q = calloc(sizeof(*q), 1); - if (!q) { - ctx->dnsc_qstatus = DNS_E_NOMEM; - return NULL; - } - -#ifndef NDEBUG - q->dnsq_ctx = ctx; -#endif - q->dnsq_parse = parse; - q->dnsq_cbck = cbck ? cbck : dns_dummy_cb; - q->dnsq_cbdata = data; - - q->dnsq_origdnl0 = dns_dntodn(dn, q->dnsq_dn, sizeof(q->dnsq_dn)); - assert(q->dnsq_origdnl0 > 0); - --q->dnsq_origdnl0; /* w/o the trailing 0 */ - dns_put16(q->dnsq_typcls+0, qtyp); - dns_put16(q->dnsq_typcls+2, qcls); - q->dnsq_flags = (flags | ctx->dnsc_flags) & ~DNS_INTERNAL; - - if (flags & DNS_NOSRCH || - dns_dnlabels(q->dnsq_dn) > ctx->dnsc_ndots) { - q->dnsq_nxtsrch = flags & DNS_NOSRCH ? - ctx->dnsc_srchend /* end of the search list if no search requested */ : - ctx->dnsc_srchbuf /* beginning of the list, but try as-is first */; - q->dnsq_flags |= DNS_ASIS_DONE; - dns_newid(ctx, q); - } - else { - q->dnsq_nxtsrch = ctx->dnsc_srchbuf; - dns_next_srch(ctx, q); - } - - qlist_add_head(q, &ctx->dnsc_qactive); - ++ctx->dnsc_nactive; - dns_request_utm(ctx, 0); - - return q; -} - -struct dns_query * -dns_submit_p(struct dns_ctx *ctx, - const char *name, int qcls, int qtyp, int flags, - dns_parse_fn *parse, dns_query_fn *cbck, void *data) { - int isabs; - SETCTXOPEN(ctx); - if (dns_ptodn(name, 0, ctx->dnsc_pbuf, DNS_MAXDN, &isabs) <= 0) { - ctx->dnsc_qstatus = DNS_E_BADQUERY; - return NULL; - } - if (isabs) - flags |= DNS_NOSRCH; - return - dns_submit_dn(ctx, ctx->dnsc_pbuf, qcls, qtyp, flags, parse, cbck, data); -} - -/* process readable fd condition. - * To be usable in edge-triggered environment, the routine - * should consume all input so it should loop over. - * Note it isn't really necessary to loop here, because - * an application may perform the loop just fine by it's own, - * but in this case we should return some sensitive result, - * to indicate when to stop calling and error conditions. - * Note also we may encounter all sorts of recvfrom() - * errors which aren't fatal, and at the same time we may - * loop forever if an error IS fatal. - */ -void dns_ioevent(struct dns_ctx *ctx, time_t now) { - int r; - unsigned servi; - struct dns_query *q; - dnsc_t *pbuf; - dnscc_t *pend, *pcur; - void *result; - union sockaddr_ns sns; - socklen_t slen; - - SETCTX(ctx); - if (!CTXOPEN(ctx)) - return; - dns_assert_ctx(ctx); - pbuf = ctx->dnsc_pbuf; - - if (!now) now = time(NULL); - -again: /* receive the reply */ - - slen = sizeof(sns); - r = recvfrom(ctx->dnsc_udpsock, (void*)pbuf, ctx->dnsc_udpbuf, - MSG_DONTWAIT, &sns.sa, &slen); - if (r < 0) { - /*XXX just ignore recvfrom() errors for now. - * in the future it may be possible to determine which - * query failed and requeue it. - * Note there may be various error conditions, triggered - * by both local problems and remote problems. It isn't - * quite trivial to determine whenever an error is local - * or remote. On local errors, we should stop, while - * remote errors should be ignored (for now anyway). - */ -#ifdef WINDOWS - if (WSAGetLastError() == WSAEWOULDBLOCK) -#else - if (errno == EAGAIN) -#endif - { - dns_request_utm(ctx, now); - return; - } - goto again; - } - - pend = pbuf + r; - pcur = dns_payload(pbuf); - - /* check reply header */ - if (pcur > pend || dns_numqd(pbuf) > 1 || dns_opcode(pbuf) != 0) { - DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r); - goto again; - } - - /* find the matching query, by qID */ - for (q = QLIST_FIRST(&ctx->dnsc_qactive, next);; q = QLIST_NEXT(q, next)) { - if (QLIST_ISLAST(&ctx->dnsc_qactive, q)) { - /* no more requests: old reply? */ - DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r); - goto again; - } - if (pbuf[DNS_H_QID1] == q->dnsq_id[0] && - pbuf[DNS_H_QID2] == q->dnsq_id[1]) - break; - } - - /* if we have numqd, compare with our query qDN */ - if (dns_numqd(pbuf)) { - /* decode the qDN */ - dnsc_t dn[DNS_MAXDN]; - if (dns_getdn(pbuf, &pcur, pend, dn, sizeof(dn)) < 0 || - pcur + 4 > pend) { - DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r); - goto again; - } - if (!dns_dnequal(dn, q->dnsq_dn) || - memcmp(pcur, q->dnsq_typcls, 4) != 0) { - /* not this query */ - DNS_DBG(ctx, -5/*no matching query*/, &sns.sa, slen, pbuf, r); - goto again; - } - /* here, query match, and pcur points past qDN in query section in pbuf */ - } - /* if no numqd, we only allow FORMERR rcode */ - else if (dns_rcode(pbuf) != DNS_R_FORMERR) { - /* treat it as bad reply if !FORMERR */ - DNS_DBG(ctx, -1/*bad reply*/, &sns.sa, slen, pbuf, r); - goto again; - } - else { - /* else it's FORMERR, handled below */ - } - - /* find server */ -#ifdef HAVE_IPv6 - if (sns.sa.sa_family == AF_INET6 && slen >= sizeof(sns.sin6)) { - for(servi = 0; servi < ctx->dnsc_nserv; ++servi) - if (sin6_eq(ctx->dnsc_serv[servi].sin6, sns.sin6)) - break; - } - else -#endif - if (sns.sa.sa_family == AF_INET && slen >= sizeof(sns.sin)) { - for(servi = 0; servi < ctx->dnsc_nserv; ++servi) - if (sin_eq(ctx->dnsc_serv[servi].sin, sns.sin)) - break; - } - else - servi = ctx->dnsc_nserv; - - /* check if we expect reply from this server. - * Note we can receive reply from first try if we're already at next */ - if (!(q->dnsq_servwait & (1 << servi))) { /* if ever asked this NS */ - DNS_DBG(ctx, -2/*wrong server*/, &sns.sa, slen, pbuf, r); - goto again; - } - - /* we got (some) reply for our query */ - - DNS_DBGQ(ctx, q, 0, &sns.sa, slen, pbuf, r); - q->dnsq_servwait &= ~(1 << servi); /* don't expect reply from this serv */ - - /* process the RCODE */ - switch(dns_rcode(pbuf)) { - - case DNS_R_NOERROR: - if (dns_tc(pbuf)) { - /* possible truncation. We can't deal with it. */ - /*XXX for now, treat TC bit the same as SERVFAIL. - * It is possible to: - * a) try to decode the reply - may be ANSWER section is ok; - * b) check if server understands EDNS0, and if it is, and - * answer still don't fit, end query. - */ - break; - } - if (!dns_numan(pbuf)) { /* no data of requested type */ - if (dns_next_srch(ctx, q)) { - /* if we're searching, try next searchlist element, - * but remember NODATA reply. */ - q->dnsq_flags |= DNS_SEEN_NODATA; - dns_send(ctx, q, now); - } - else - /* else - nothing to search any more - finish the query. - * It will be NODATA since we've seen a NODATA reply. */ - dns_end_query(ctx, q, DNS_E_NODATA, 0); - } - /* we've got a positive reply here */ - else if (q->dnsq_parse) { - /* if we have parsing routine, call it and return whatever it returned */ - /* don't try to re-search if NODATA here. For example, - * if we asked for A but only received CNAME. Unless we'll - * someday do recursive queries. And that's problematic too, since - * we may be dealing with specific AA-only nameservers for a given - * domain, but CNAME points elsewhere... - */ - r = q->dnsq_parse(q->dnsq_dn, pbuf, pcur, pend, &result); - dns_end_query(ctx, q, r, r < 0 ? NULL : result); - } - /* else just malloc+copy the raw DNS reply */ - else if ((result = malloc(r)) == NULL) - dns_end_query(ctx, q, DNS_E_NOMEM, NULL); - else { - memcpy(result, pbuf, r); - dns_end_query(ctx, q, r, result); - } - goto again; - - case DNS_R_NXDOMAIN: /* Non-existing domain. */ - if (dns_next_srch(ctx, q)) - /* more search entries exists, try them. */ - dns_send(ctx, q, now); - else - /* nothing to search anymore. End the query, returning either NODATA - * if we've seen it before, or NXDOMAIN if not. */ - dns_end_query(ctx, q, - q->dnsq_flags & DNS_SEEN_NODATA ? DNS_E_NODATA : DNS_E_NXDOMAIN, 0); - goto again; - - case DNS_R_FORMERR: - case DNS_R_NOTIMPL: - /* for FORMERR and NOTIMPL rcodes, if we tried EDNS0-enabled query, - * try w/o EDNS0. */ - if (ctx->dnsc_udpbuf > DNS_MAXPACKET && - !(q->dnsq_servnEDNS0 & (1 << servi))) { - /* we always trying EDNS0 first if enabled, and retry a given query - * if not available. Maybe it's better to remember inavailability of - * EDNS0 in ctx as a per-NS flag, and never try again for this NS. - * For long-running applications.. maybe they will change the nameserver - * while we're running? :) Also, since FORMERR is the only rcode we - * allow to be header-only, and in this case the only check we do to - * find a query it belongs to is qID (not qDN+qCLS+qTYP), it's much - * easier to spoof and to force us to perform non-EDNS0 queries only... - */ - q->dnsq_servnEDNS0 |= 1 << servi; - dns_send_this(ctx, q, servi, now); - goto again; - } - /* else we handle it the same as SERVFAIL etc */ - - case DNS_R_SERVFAIL: - case DNS_R_REFUSED: - /* for these rcodes, advance this request - * to the next server and reschedule */ - default: /* unknown rcode? hmmm... */ - break; - } - - /* here, we received unexpected reply */ - q->dnsq_servskip |= (1 << servi); /* don't retry this server */ - - /* we don't expect replies from this server anymore. - * But there may be other servers. Some may be still processing our - * query, and some may be left to try. - * We just ignore this reply and wait a bit more if some NSes haven't - * replied yet (dnsq_servwait != 0), and let the situation to be handled - * on next event processing. Timeout for this query is set correctly, - * if not taking into account the one-second difference - we can try - * next server in the same iteration sooner. - */ - - /* try next server */ - if (!q->dnsq_servwait) { - /* next retry: maybe some other servers will reply next time. - * dns_send() will end the query for us if no more servers to try. - * Note we can't continue with the next searchlist element here: - * we don't know if the current qdn exists or not, there's no definitive - * answer yet (which is seen in cases above). - *XXX standard resolver also tries as-is query in case all nameservers - * failed to process our query and if not tried before. We don't do it. - */ - dns_send(ctx, q, now); - } - else { - /* else don't do anything - not all servers replied yet */ - } - goto again; - -} - -/* handle all timeouts */ -int dns_timeouts(struct dns_ctx *ctx, int maxwait, time_t now) { - /* this is a hot routine */ - struct dns_query *q; - - SETCTX(ctx); - dns_assert_ctx(ctx); - - /* Pick up first entry from query list. - * If its deadline has passed, (re)send it - * (dns_send() will move it next in the list). - * If not, this is the query which determines the closest deadline. - */ - - q = qlist_first(&ctx->dnsc_qactive); - if (!q) - return maxwait; - if (!now) - now = time(NULL); - do { - if (q->dnsq_deadline > now) { /* first non-expired query */ - int w = (int)(q->dnsq_deadline - now); - if (maxwait < 0 || maxwait > w) - maxwait = w; - break; - } - else { - /* process expired deadline */ - dns_send(ctx, q, now); - } - } while((q = qlist_first(&ctx->dnsc_qactive)) != NULL); - - dns_request_utm(ctx, now); /* update timer with new deadline */ - return maxwait; -} - -struct dns_resolve_data { - int dnsrd_done; - void *dnsrd_result; -}; - -static void dns_resolve_cb(struct dns_ctx *ctx, void *result, void *data) { - struct dns_resolve_data *d = data; - d->dnsrd_result = result; - d->dnsrd_done = 1; - ctx = ctx; -} - -void *dns_resolve(struct dns_ctx *ctx, struct dns_query *q) { - time_t now; - struct dns_resolve_data d; - int n; - SETCTXOPEN(ctx); - - if (!q) - return NULL; - - assert(ctx == q->dnsq_ctx); - dns_assert_ctx(ctx); - /* do not allow re-resolving syncronous queries */ - assert(q->dnsq_cbck != dns_resolve_cb && "can't resolve syncronous query"); - if (q->dnsq_cbck == dns_resolve_cb) { - ctx->dnsc_qstatus = DNS_E_BADQUERY; - return NULL; - } - q->dnsq_cbck = dns_resolve_cb; - q->dnsq_cbdata = &d; - d.dnsrd_done = 0; - - now = time(NULL); - while(!d.dnsrd_done && (n = dns_timeouts(ctx, -1, now)) >= 0) { -#ifdef HAVE_POLL - struct pollfd pfd; - pfd.fd = ctx->dnsc_udpsock; - pfd.events = POLLIN; - n = poll(&pfd, 1, n * 1000); -#else - fd_set rfd; - struct timeval tv; - FD_ZERO(&rfd); - FD_SET(ctx->dnsc_udpsock, &rfd); - tv.tv_sec = n; tv.tv_usec = 0; - n = select(ctx->dnsc_udpsock + 1, &rfd, NULL, NULL, &tv); -#endif - now = time(NULL); - if (n > 0) - dns_ioevent(ctx, now); - } - - return d.dnsrd_result; -} - -void *dns_resolve_dn(struct dns_ctx *ctx, - dnscc_t *dn, int qcls, int qtyp, int flags, - dns_parse_fn *parse) { - return - dns_resolve(ctx, - dns_submit_dn(ctx, dn, qcls, qtyp, flags, parse, NULL, NULL)); -} - -void *dns_resolve_p(struct dns_ctx *ctx, - const char *name, int qcls, int qtyp, int flags, - dns_parse_fn *parse) { - return - dns_resolve(ctx, - dns_submit_p(ctx, name, qcls, qtyp, flags, parse, NULL, NULL)); -} - -int dns_cancel(struct dns_ctx *ctx, struct dns_query *q) { - SETCTX(ctx); - dns_assert_ctx(ctx); - assert(q->dnsq_ctx == ctx); - /* do not allow cancelling syncronous queries */ - assert(q->dnsq_cbck != dns_resolve_cb && "can't cancel syncronous query"); - if (q->dnsq_cbck == dns_resolve_cb) - return (ctx->dnsc_qstatus = DNS_E_BADQUERY); - qlist_remove(q); - --ctx->dnsc_nactive; - dns_request_utm(ctx, 0); - return 0; -} - |