mirror of
https://github.com/strophe/libstrophe.git
synced 2026-07-18 20:06:21 +00:00
It has been pointed out that the wording of the license of this library is not entirely clear. The term "dual licensing" usually refers to a licence choice of two licenses "LICENSE1 _or_ LICENSE2. Instead the license of this library claimed "LICENSE1 _and_ LICENSE2". After an internal discussion with @metajack and @pasis it was made clear that the initial idea was to dual license the library in the usual way. This was also made clear by jack on the ML in the past [0]. As of jack, these licensing terms originated from jquery, which also used the 'and' version in the past and has since been corrected [1]. This patch changes the license terms to 'MIT or GPLv3' and also adds SPDX headers [2]. [0] https://groups.google.com/g/libstrophe/c/JkFgr601JQc [1] https://stackoverflow.com/q/2758409 [2] https://spdx.org Signed-off-by: Steffen Jaeckel <jaeckel-floss@eyet-services.de>
1005 lines
31 KiB
C
1005 lines
31 KiB
C
/* SPDX-License-Identifier: MIT OR GPL-3.0-only */
|
|
/* resolver.c
|
|
* strophe XMPP client library -- DNS resolver
|
|
*
|
|
* Copyright (C) 2015 Dmitry Podgorny <pasis.ua@gmail.com>
|
|
*
|
|
* This software is provided AS-IS with no warranty, either express
|
|
* or implied.
|
|
*
|
|
* This program is dual licensed under the MIT or GPLv3 licenses.
|
|
*/
|
|
|
|
/** @file
|
|
* DNS resolver.
|
|
*/
|
|
|
|
#if !defined(_WIN32) && !defined(HAVE_CARES)
|
|
#include <netinet/in.h>
|
|
#include <arpa/nameser.h>
|
|
#include <resolv.h>
|
|
#endif /* _WIN32 && HAVE_CARES */
|
|
|
|
#ifdef HAVE_CARES
|
|
#include <ares.h>
|
|
/* for select(2) */
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#else /* _WIN32 */
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#endif /* !_WIN32 */
|
|
#endif /* HAVE_CARES */
|
|
|
|
#include <string.h> /* strncpy */
|
|
|
|
#include "ostypes.h"
|
|
#include "snprintf.h"
|
|
#include "util.h" /* xmpp_min */
|
|
#include "resolver.h"
|
|
|
|
#define MESSAGE_HEADER_LEN 12
|
|
#define MESSAGE_RESPONSE 1
|
|
#define MESSAGE_T_SRV 33
|
|
#define MESSAGE_C_IN 1
|
|
|
|
/*******************************************************************************
|
|
* Forward declarations.
|
|
******************************************************************************/
|
|
|
|
#ifdef HAVE_CARES
|
|
static int resolver_ares_srv_lookup_buf(xmpp_ctx_t *ctx,
|
|
const unsigned char *buf,
|
|
size_t len,
|
|
resolver_srv_rr_t **srv_rr_list);
|
|
static int resolver_ares_srv_lookup(xmpp_ctx_t *ctx,
|
|
const char *fulldomain,
|
|
resolver_srv_rr_t **srv_rr_list);
|
|
#endif /* HAVE_CARES */
|
|
|
|
#ifndef HAVE_CARES
|
|
static int resolver_raw_srv_lookup_buf(xmpp_ctx_t *ctx,
|
|
const unsigned char *buf,
|
|
size_t len,
|
|
resolver_srv_rr_t **srv_rr_list);
|
|
#endif /* !HAVE_CARES */
|
|
|
|
#if defined(_WIN32) && !defined(HAVE_CARES)
|
|
static int resolver_win32_srv_lookup(xmpp_ctx_t *ctx,
|
|
const char *fulldomain,
|
|
resolver_srv_rr_t **srv_rr_list);
|
|
static int resolver_win32_srv_query(const char *fulldomain,
|
|
unsigned char *buf,
|
|
size_t len);
|
|
#endif /* _WIN32 && !HAVE_CARES */
|
|
|
|
/*******************************************************************************
|
|
* Implementation.
|
|
******************************************************************************/
|
|
|
|
void resolver_initialize(void)
|
|
{
|
|
#ifdef HAVE_CARES
|
|
ares_library_init(ARES_LIB_INIT_ALL);
|
|
#endif
|
|
}
|
|
|
|
void resolver_shutdown(void)
|
|
{
|
|
#ifdef HAVE_CARES
|
|
ares_library_cleanup();
|
|
#endif
|
|
}
|
|
|
|
resolver_srv_rr_t *resolver_srv_rr_new(xmpp_ctx_t *ctx,
|
|
const char *host,
|
|
unsigned short port,
|
|
unsigned short prio,
|
|
unsigned short weight)
|
|
{
|
|
resolver_srv_rr_t *rr = strophe_alloc(ctx, sizeof(*rr));
|
|
if (rr) {
|
|
memset(rr, 0, sizeof(*rr));
|
|
rr->port = port;
|
|
rr->priority = prio;
|
|
rr->weight = weight;
|
|
if (host) {
|
|
snprintf(rr->target, sizeof(rr->target), "%s", host);
|
|
}
|
|
}
|
|
return rr;
|
|
}
|
|
|
|
static void resolver_srv_list_sort(resolver_srv_rr_t **srv_rr_list)
|
|
{
|
|
resolver_srv_rr_t *rr_head;
|
|
resolver_srv_rr_t *rr_current;
|
|
resolver_srv_rr_t *rr_next;
|
|
resolver_srv_rr_t *rr_prev;
|
|
int swap;
|
|
|
|
rr_head = *srv_rr_list;
|
|
|
|
if ((rr_head == NULL) || (rr_head->next == NULL)) {
|
|
/* Empty or single record list */
|
|
return;
|
|
}
|
|
|
|
do {
|
|
rr_prev = NULL;
|
|
rr_current = rr_head;
|
|
rr_next = rr_head->next;
|
|
swap = 0;
|
|
while (rr_next != NULL) {
|
|
/*
|
|
* RFC2052: A client MUST attempt to contact the target host
|
|
* with the lowest-numbered priority it can reach.
|
|
* RFC2052: When selecting a target host among the
|
|
* those that have the same priority, the chance of trying
|
|
* this one first SHOULD be proportional to its weight.
|
|
*/
|
|
if ((rr_current->priority > rr_next->priority) ||
|
|
(rr_current->priority == rr_next->priority &&
|
|
rr_current->weight < rr_next->weight)) {
|
|
/* Swap node */
|
|
swap = 1;
|
|
if (rr_prev != NULL) {
|
|
rr_prev->next = rr_next;
|
|
} else {
|
|
/* Swap head node */
|
|
rr_head = rr_next;
|
|
}
|
|
rr_current->next = rr_next->next;
|
|
rr_next->next = rr_current;
|
|
|
|
rr_prev = rr_next;
|
|
rr_next = rr_current->next;
|
|
} else {
|
|
/* Next node */
|
|
rr_prev = rr_current;
|
|
rr_current = rr_next;
|
|
rr_next = rr_next->next;
|
|
}
|
|
}
|
|
} while (swap != 0);
|
|
|
|
*srv_rr_list = rr_head;
|
|
}
|
|
|
|
int resolver_srv_lookup_buf(xmpp_ctx_t *ctx,
|
|
const unsigned char *buf,
|
|
size_t len,
|
|
resolver_srv_rr_t **srv_rr_list)
|
|
{
|
|
int set;
|
|
|
|
#ifdef HAVE_CARES
|
|
set = resolver_ares_srv_lookup_buf(ctx, buf, len, srv_rr_list);
|
|
#else
|
|
set = resolver_raw_srv_lookup_buf(ctx, buf, len, srv_rr_list);
|
|
if (set != XMPP_DOMAIN_FOUND && *srv_rr_list != NULL) {
|
|
resolver_srv_free(ctx, *srv_rr_list);
|
|
*srv_rr_list = NULL;
|
|
}
|
|
#endif
|
|
resolver_srv_list_sort(srv_rr_list);
|
|
|
|
return set;
|
|
}
|
|
|
|
int resolver_srv_lookup(xmpp_ctx_t *ctx,
|
|
const char *service,
|
|
const char *proto,
|
|
const char *domain,
|
|
resolver_srv_rr_t **srv_rr_list)
|
|
{
|
|
#define RESOLVER_BUF_MAX 65536
|
|
unsigned char *buf;
|
|
char fulldomain[2048];
|
|
int len;
|
|
int set = XMPP_DOMAIN_NOT_FOUND;
|
|
|
|
(void)buf;
|
|
(void)len;
|
|
|
|
strophe_snprintf(fulldomain, sizeof(fulldomain), "_%s._%s.%s", service,
|
|
proto, domain);
|
|
|
|
*srv_rr_list = NULL;
|
|
|
|
#ifdef HAVE_CARES
|
|
|
|
set = resolver_ares_srv_lookup(ctx, fulldomain, srv_rr_list);
|
|
|
|
#else /* HAVE_CARES */
|
|
|
|
#ifdef _WIN32
|
|
set = resolver_win32_srv_lookup(ctx, fulldomain, srv_rr_list);
|
|
if (set == XMPP_DOMAIN_FOUND)
|
|
return set;
|
|
#endif /* _WIN32 */
|
|
|
|
buf = strophe_alloc(ctx, RESOLVER_BUF_MAX);
|
|
if (buf == NULL)
|
|
return XMPP_DOMAIN_NOT_FOUND;
|
|
|
|
#ifdef _WIN32
|
|
len = resolver_win32_srv_query(fulldomain, buf, RESOLVER_BUF_MAX);
|
|
#else /* _WIN32 */
|
|
len = res_query(fulldomain, MESSAGE_C_IN, MESSAGE_T_SRV, buf,
|
|
RESOLVER_BUF_MAX);
|
|
#endif /* _WIN32 */
|
|
|
|
if (len > 0)
|
|
set = resolver_srv_lookup_buf(ctx, buf, (size_t)len, srv_rr_list);
|
|
|
|
strophe_free(ctx, buf);
|
|
|
|
#endif /* HAVE_CARES */
|
|
|
|
return set;
|
|
}
|
|
|
|
void resolver_srv_free(xmpp_ctx_t *ctx, resolver_srv_rr_t *srv_rr_list)
|
|
{
|
|
resolver_srv_rr_t *rr;
|
|
|
|
while (srv_rr_list != NULL) {
|
|
rr = srv_rr_list->next;
|
|
strophe_free(ctx, srv_rr_list);
|
|
srv_rr_list = rr;
|
|
}
|
|
}
|
|
|
|
#ifndef HAVE_CARES
|
|
/*******************************************************************************
|
|
* Resolver raw implementation.
|
|
*
|
|
* This code is common for both unix and win32.
|
|
******************************************************************************/
|
|
|
|
struct message_header {
|
|
uint16_t id;
|
|
uint8_t octet2;
|
|
uint8_t octet3;
|
|
uint16_t qdcount;
|
|
uint16_t ancount;
|
|
uint16_t nscount;
|
|
uint16_t arcount;
|
|
};
|
|
|
|
/* the same as ntohs(), but receives pointer to the value */
|
|
static uint16_t xmpp_ntohs_ptr(const void *ptr)
|
|
{
|
|
const uint8_t *p = (const uint8_t *)ptr;
|
|
|
|
return (uint16_t)((p[0] << 8U) + p[1]);
|
|
}
|
|
|
|
static uint8_t message_header_qr(const struct message_header *header)
|
|
{
|
|
return (header->octet2 >> 7) & 1;
|
|
}
|
|
|
|
static uint8_t message_header_rcode(const struct message_header *header)
|
|
{
|
|
return header->octet3 & 0x0f;
|
|
}
|
|
|
|
/*
|
|
* Append a label or a dot to the target name with buffer overflow checks.
|
|
* Returns length of the non-truncated resulting string, may be bigger than
|
|
* name_max.
|
|
*/
|
|
static size_t message_name_append_safe(char *name,
|
|
size_t name_len,
|
|
size_t name_max,
|
|
const char *tail,
|
|
size_t tail_len)
|
|
{
|
|
size_t copy_len;
|
|
|
|
copy_len = name_max > name_len ? name_max - name_len : 0;
|
|
copy_len = xmpp_min(tail_len, copy_len);
|
|
if (copy_len > 0)
|
|
memcpy(&name[name_len], tail, copy_len);
|
|
|
|
return name_len + tail_len;
|
|
}
|
|
|
|
/* Returns length of the compressed name. This is NOT the same as strlen(). */
|
|
static unsigned message_name_get(const unsigned char *buf,
|
|
size_t buf_len,
|
|
unsigned buf_offset,
|
|
char *name,
|
|
size_t name_max)
|
|
{
|
|
size_t name_len = 0;
|
|
unsigned i = buf_offset;
|
|
unsigned pointer;
|
|
unsigned rc;
|
|
unsigned char label_len;
|
|
|
|
while (1) {
|
|
if (i >= buf_len)
|
|
return 0;
|
|
label_len = buf[i++];
|
|
if (label_len == 0)
|
|
break;
|
|
|
|
/* Label */
|
|
if ((label_len & 0xc0) == 0) {
|
|
if (i + label_len - 1 >= buf_len)
|
|
return 0;
|
|
if (name != NULL) {
|
|
name_len = message_name_append_safe(name, name_len, name_max,
|
|
(char *)&buf[i], label_len);
|
|
name_len =
|
|
message_name_append_safe(name, name_len, name_max, ".", 1);
|
|
}
|
|
i += label_len;
|
|
|
|
/* Pointer */
|
|
} else if ((label_len & 0xc0) == 0xc0) {
|
|
if (i >= buf_len)
|
|
return 0;
|
|
pointer = (label_len & 0x3f) << 8 | buf[i++];
|
|
/* Prevent infinite looping */
|
|
if (pointer >= buf_offset)
|
|
return 0;
|
|
if (name != NULL && name_len >= name_max && name_max > 0) {
|
|
/* We have filled the name buffer. Don't pass it recursively. */
|
|
name[name_max - 1] = '\0';
|
|
name = NULL;
|
|
name_max = 0;
|
|
}
|
|
rc = message_name_get(
|
|
buf, buf_len, pointer, name != NULL ? &name[name_len] : NULL,
|
|
name_max > name_len ? name_max - name_len : 0);
|
|
if (rc == 0)
|
|
return 0;
|
|
/* Pointer is always the last. */
|
|
break;
|
|
|
|
/* The 10 and 01 combinations are reserved for future use. */
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
if (label_len == 0) {
|
|
if (name_len == 0)
|
|
name_len = 1;
|
|
/*
|
|
* At this point name_len is length of the resulting name,
|
|
* including '\0'. This value can be exported to allocate buffer
|
|
* of precise size.
|
|
*/
|
|
if (name != NULL && name_max > 0) {
|
|
/*
|
|
* Overwrite leading '.' with a '\0'. If the resulting name is
|
|
* bigger than name_max it is truncated.
|
|
*/
|
|
name[xmpp_min(name_len, name_max) - 1] = '\0';
|
|
}
|
|
}
|
|
|
|
return i - buf_offset;
|
|
}
|
|
|
|
static unsigned
|
|
message_name_len(const unsigned char *buf, size_t buf_len, unsigned buf_offset)
|
|
{
|
|
return message_name_get(buf, buf_len, buf_offset, NULL, SIZE_MAX);
|
|
}
|
|
|
|
#define BUF_OVERFLOW_CHECK(ptr, len) \
|
|
do { \
|
|
if ((ptr) >= (len)) { \
|
|
if (*srv_rr_list != NULL) \
|
|
resolver_srv_free(ctx, *srv_rr_list); \
|
|
*srv_rr_list = NULL; \
|
|
return XMPP_DOMAIN_NOT_FOUND; \
|
|
} \
|
|
} while (0)
|
|
|
|
static int resolver_raw_srv_lookup_buf(xmpp_ctx_t *ctx,
|
|
const unsigned char *buf,
|
|
size_t len,
|
|
resolver_srv_rr_t **srv_rr_list)
|
|
{
|
|
unsigned i;
|
|
unsigned j;
|
|
unsigned name_len;
|
|
unsigned rdlength;
|
|
uint16_t type;
|
|
uint16_t class;
|
|
struct message_header header;
|
|
resolver_srv_rr_t *rr;
|
|
|
|
*srv_rr_list = NULL;
|
|
|
|
if (len < MESSAGE_HEADER_LEN)
|
|
return XMPP_DOMAIN_NOT_FOUND;
|
|
|
|
header.id = xmpp_ntohs_ptr(&buf[0]);
|
|
header.octet2 = buf[2];
|
|
header.octet3 = buf[3];
|
|
header.qdcount = xmpp_ntohs_ptr(&buf[4]);
|
|
header.ancount = xmpp_ntohs_ptr(&buf[6]);
|
|
header.nscount = xmpp_ntohs_ptr(&buf[8]);
|
|
header.arcount = xmpp_ntohs_ptr(&buf[10]);
|
|
if (message_header_qr(&header) != MESSAGE_RESPONSE ||
|
|
message_header_rcode(&header) != 0) {
|
|
return XMPP_DOMAIN_NOT_FOUND;
|
|
}
|
|
j = MESSAGE_HEADER_LEN;
|
|
|
|
/* skip question section */
|
|
for (i = 0; i < header.qdcount; ++i) {
|
|
BUF_OVERFLOW_CHECK(j, len);
|
|
name_len = message_name_len(buf, len, j);
|
|
/* error in name format */
|
|
if (name_len == 0)
|
|
return XMPP_DOMAIN_NOT_FOUND;
|
|
j += name_len + 4;
|
|
}
|
|
|
|
for (i = 0; i < header.ancount; ++i) {
|
|
BUF_OVERFLOW_CHECK(j, len);
|
|
name_len = message_name_len(buf, len, j);
|
|
/* error in name format */
|
|
if (name_len == 0)
|
|
return XMPP_DOMAIN_NOT_FOUND;
|
|
j += name_len;
|
|
BUF_OVERFLOW_CHECK(j + 16, len);
|
|
type = xmpp_ntohs_ptr(&buf[j]);
|
|
class = xmpp_ntohs_ptr(&buf[j + 2]);
|
|
rdlength = xmpp_ntohs_ptr(&buf[j + 8]);
|
|
j += 10;
|
|
if (type == MESSAGE_T_SRV && class == MESSAGE_C_IN) {
|
|
rr = resolver_srv_rr_new(ctx, NULL, 0, 0, 0);
|
|
if (rr) {
|
|
rr->next = *srv_rr_list;
|
|
rr->priority = xmpp_ntohs_ptr(&buf[j]);
|
|
rr->weight = xmpp_ntohs_ptr(&buf[j + 2]);
|
|
rr->port = xmpp_ntohs_ptr(&buf[j + 4]);
|
|
name_len = message_name_get(buf, len, j + 6, rr->target,
|
|
sizeof(rr->target));
|
|
if (name_len > 0)
|
|
*srv_rr_list = rr;
|
|
else
|
|
strophe_free(ctx, rr); /* skip broken record */
|
|
}
|
|
}
|
|
j += rdlength;
|
|
}
|
|
|
|
return *srv_rr_list != NULL ? XMPP_DOMAIN_FOUND : XMPP_DOMAIN_NOT_FOUND;
|
|
}
|
|
|
|
#endif /* !HAVE_CARES */
|
|
|
|
#ifdef HAVE_CARES
|
|
/*******************************************************************************
|
|
* Resolver implementation using c-ares library.
|
|
******************************************************************************/
|
|
|
|
struct resolver_ares_ctx {
|
|
xmpp_ctx_t *ctx;
|
|
int result;
|
|
resolver_srv_rr_t *srv_rr_list;
|
|
};
|
|
|
|
static int resolver_ares_srv_lookup_buf(xmpp_ctx_t *ctx,
|
|
const unsigned char *buf,
|
|
size_t len,
|
|
resolver_srv_rr_t **srv_rr_list)
|
|
{
|
|
struct ares_srv_reply *srv;
|
|
struct ares_srv_reply *item;
|
|
resolver_srv_rr_t *rr;
|
|
int rc;
|
|
|
|
*srv_rr_list = NULL;
|
|
|
|
rc = ares_parse_srv_reply(buf, len, &srv);
|
|
if (rc != ARES_SUCCESS)
|
|
return XMPP_DOMAIN_NOT_FOUND;
|
|
|
|
item = srv;
|
|
while (item != NULL) {
|
|
rr = strophe_alloc(ctx, sizeof(*rr));
|
|
if (rr == NULL)
|
|
break;
|
|
rr->next = *srv_rr_list;
|
|
rr->priority = item->priority;
|
|
rr->weight = item->weight;
|
|
rr->port = item->port;
|
|
strncpy(rr->target, item->host, sizeof(rr->target) - 1);
|
|
rr->target[sizeof(rr->target) - 1] = '\0';
|
|
*srv_rr_list = rr;
|
|
item = item->next;
|
|
}
|
|
ares_free_data(srv);
|
|
|
|
return *srv_rr_list == NULL ? XMPP_DOMAIN_NOT_FOUND : XMPP_DOMAIN_FOUND;
|
|
}
|
|
|
|
static void ares_srv_lookup_callback(
|
|
void *arg, int status, int timeouts, unsigned char *buf, int len)
|
|
{
|
|
struct resolver_ares_ctx *actx = arg;
|
|
|
|
(void)timeouts;
|
|
|
|
if (status != ARES_SUCCESS)
|
|
actx->result = XMPP_DOMAIN_NOT_FOUND;
|
|
else
|
|
actx->result = resolver_ares_srv_lookup_buf(actx->ctx, buf, len,
|
|
&actx->srv_rr_list);
|
|
}
|
|
|
|
static int resolver_ares_srv_lookup(xmpp_ctx_t *ctx,
|
|
const char *fulldomain,
|
|
resolver_srv_rr_t **srv_rr_list)
|
|
{
|
|
struct resolver_ares_ctx actx;
|
|
ares_channel chan;
|
|
struct timeval tv;
|
|
struct timeval *tvp;
|
|
fd_set rfds;
|
|
fd_set wfds;
|
|
int nfds;
|
|
int rc;
|
|
|
|
actx.ctx = ctx;
|
|
actx.result = XMPP_DOMAIN_NOT_FOUND;
|
|
actx.srv_rr_list = NULL;
|
|
|
|
rc = ares_init(&chan);
|
|
if (rc == ARES_SUCCESS) {
|
|
ares_query(chan, fulldomain, MESSAGE_C_IN, MESSAGE_T_SRV,
|
|
ares_srv_lookup_callback, &actx);
|
|
while (1) {
|
|
FD_ZERO(&rfds);
|
|
FD_ZERO(&wfds);
|
|
nfds = ares_fds(chan, &rfds, &wfds);
|
|
if (nfds == 0)
|
|
break;
|
|
tvp = ares_timeout(chan, NULL, &tv);
|
|
select(nfds, &rfds, &wfds, NULL, tvp);
|
|
ares_process(chan, &rfds, &wfds);
|
|
}
|
|
ares_destroy(chan);
|
|
}
|
|
|
|
*srv_rr_list = actx.srv_rr_list;
|
|
return actx.result;
|
|
}
|
|
|
|
#endif /* HAVE_CARES */
|
|
|
|
#if defined(_WIN32) && !defined(HAVE_CARES)
|
|
/*******************************************************************************
|
|
* Next part was copied from sock.c and contains old win32 code.
|
|
*
|
|
* The idea is to get raw response from a name server and pass it to
|
|
* resolver_srv_lookup_buf(). In fact, resolver_win32_srv_query() replaces
|
|
* the call of res_query().
|
|
* Dnsapi code is moved to a separated function resolver_srv_win32_lookup() and
|
|
* changed to meet new API.
|
|
*
|
|
* XXX If the code is compiled it should work like before.
|
|
******************************************************************************/
|
|
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <windns.h>
|
|
#include <iphlpapi.h>
|
|
|
|
struct dnsquery_header {
|
|
unsigned short id;
|
|
unsigned char qr;
|
|
unsigned char opcode;
|
|
unsigned char aa;
|
|
unsigned char tc;
|
|
unsigned char rd;
|
|
unsigned char ra;
|
|
unsigned char z;
|
|
unsigned char rcode;
|
|
unsigned short qdcount;
|
|
unsigned short ancount;
|
|
unsigned short nscount;
|
|
unsigned short arcount;
|
|
};
|
|
|
|
struct dnsquery_question {
|
|
char qname[1024];
|
|
unsigned short qtype;
|
|
unsigned short qclass;
|
|
};
|
|
|
|
static void netbuf_add_16bitnum(unsigned char *buf,
|
|
int buflen,
|
|
int *offset,
|
|
unsigned short num)
|
|
{
|
|
unsigned char *start = buf + *offset;
|
|
unsigned char *p = start;
|
|
|
|
/* assuming big endian */
|
|
*p++ = (num >> 8) & 0xff;
|
|
*p++ = (num)&0xff;
|
|
|
|
*offset += 2;
|
|
}
|
|
|
|
static void
|
|
netbuf_add_domain_name(unsigned char *buf, int buflen, int *offset, char *name)
|
|
{
|
|
unsigned char *start = buf + *offset;
|
|
unsigned char *p = start;
|
|
unsigned char *wordstart, *wordend;
|
|
|
|
wordstart = (unsigned char *)name;
|
|
|
|
while (*wordstart) {
|
|
int len;
|
|
wordend = wordstart;
|
|
while (*wordend && *wordend != '.') {
|
|
wordend++;
|
|
}
|
|
|
|
len = (int)(wordend - wordstart);
|
|
|
|
if (len > 0x3F) {
|
|
len = 0x3F;
|
|
}
|
|
|
|
*p++ = len;
|
|
|
|
while (wordstart != wordend) {
|
|
*p++ = *wordstart++;
|
|
}
|
|
|
|
if (*wordstart == '.') {
|
|
wordstart++;
|
|
}
|
|
}
|
|
|
|
*p++ = '\0';
|
|
|
|
*offset += (int)(p - start);
|
|
}
|
|
|
|
static void netbuf_add_dnsquery_header(unsigned char *buf,
|
|
int buflen,
|
|
int *offset,
|
|
struct dnsquery_header *header)
|
|
{
|
|
unsigned char *p;
|
|
|
|
netbuf_add_16bitnum(buf, buflen, offset, header->id);
|
|
|
|
p = buf + *offset;
|
|
*p++ = ((header->qr & 0x01) << 7) | ((header->opcode & 0x0F) << 3) |
|
|
((header->aa & 0x01) << 2) | ((header->tc & 0x01) << 1) |
|
|
((header->rd & 0x01));
|
|
*p++ = ((header->ra & 0x01) << 7) | ((header->z & 0x07) << 4) |
|
|
((header->rcode & 0x0F));
|
|
*offset += 2;
|
|
|
|
netbuf_add_16bitnum(buf, buflen, offset, header->qdcount);
|
|
netbuf_add_16bitnum(buf, buflen, offset, header->ancount);
|
|
netbuf_add_16bitnum(buf, buflen, offset, header->nscount);
|
|
netbuf_add_16bitnum(buf, buflen, offset, header->arcount);
|
|
}
|
|
|
|
static void netbuf_add_dnsquery_question(unsigned char *buf,
|
|
int buflen,
|
|
int *offset,
|
|
struct dnsquery_question *question)
|
|
{
|
|
netbuf_add_domain_name(buf, buflen, offset, question->qname);
|
|
netbuf_add_16bitnum(buf, buflen, offset, question->qtype);
|
|
netbuf_add_16bitnum(buf, buflen, offset, question->qclass);
|
|
}
|
|
|
|
static int resolver_win32_srv_lookup(xmpp_ctx_t *ctx,
|
|
const char *fulldomain,
|
|
resolver_srv_rr_t **srv_rr_list)
|
|
{
|
|
resolver_srv_rr_t *rr;
|
|
HINSTANCE hdnsapi = NULL;
|
|
|
|
DNS_STATUS(WINAPI * pDnsQuery_A)
|
|
(PCSTR, WORD, DWORD, PIP4_ARRAY, DNS_RECORDA **, PVOID *);
|
|
void(WINAPI * pDnsRecordListFree)(DNS_RECORDA *, DNS_FREE_TYPE);
|
|
|
|
if (hdnsapi = LoadLibrary("dnsapi.dll")) {
|
|
pDnsQuery_A = (void *)GetProcAddress(hdnsapi, "DnsQuery_A");
|
|
pDnsRecordListFree =
|
|
(void *)GetProcAddress(hdnsapi, "DnsRecordListFree");
|
|
|
|
if (pDnsQuery_A && pDnsRecordListFree) {
|
|
DNS_RECORDA *dnsrecords = NULL;
|
|
DNS_STATUS error;
|
|
|
|
error = pDnsQuery_A(fulldomain, DNS_TYPE_SRV, DNS_QUERY_STANDARD,
|
|
NULL, &dnsrecords, NULL);
|
|
|
|
if (error == 0) {
|
|
DNS_RECORDA *current = dnsrecords;
|
|
|
|
while (current) {
|
|
if (current->wType == DNS_TYPE_SRV) {
|
|
rr = strophe_alloc(ctx, sizeof(*rr));
|
|
if (rr == NULL)
|
|
break;
|
|
rr->next = *srv_rr_list;
|
|
rr->port = current->Data.Srv.wPort;
|
|
rr->priority = current->Data.Srv.wPriority;
|
|
rr->weight = current->Data.Srv.wWeight;
|
|
strophe_snprintf(rr->target, sizeof(rr->target), "%s",
|
|
current->Data.Srv.pNameTarget);
|
|
*srv_rr_list = rr;
|
|
}
|
|
current = current->pNext;
|
|
}
|
|
}
|
|
|
|
pDnsRecordListFree(dnsrecords, DnsFreeRecordList);
|
|
}
|
|
|
|
FreeLibrary(hdnsapi);
|
|
}
|
|
resolver_srv_list_sort(srv_rr_list);
|
|
|
|
return *srv_rr_list != NULL ? XMPP_DOMAIN_FOUND : XMPP_DOMAIN_NOT_FOUND;
|
|
}
|
|
|
|
static int
|
|
resolver_win32_srv_query(const char *fulldomain, unsigned char *buf, size_t len)
|
|
{
|
|
int set = 0;
|
|
int insize = 0;
|
|
|
|
/* if dnsapi didn't work/isn't there, try querying the dns server manually
|
|
*/
|
|
if (!set) {
|
|
struct dnsquery_header header;
|
|
struct dnsquery_question question;
|
|
int offset = 0;
|
|
int addrlen;
|
|
sock_t sock;
|
|
struct sockaddr_in dnsaddr;
|
|
char dnsserverips[16][256];
|
|
int numdnsservers = 0;
|
|
int j;
|
|
|
|
/* Try getting the DNS server ips from GetNetworkParams() in iphlpapi
|
|
* first */
|
|
if (!numdnsservers) {
|
|
HINSTANCE hiphlpapi = NULL;
|
|
DWORD(WINAPI * pGetNetworkParams)(PFIXED_INFO, PULONG);
|
|
|
|
if (hiphlpapi = LoadLibrary("Iphlpapi.dll")) {
|
|
pGetNetworkParams =
|
|
(void *)GetProcAddress(hiphlpapi, "GetNetworkParams");
|
|
|
|
if (pGetNetworkParams) {
|
|
FIXED_INFO *fi;
|
|
ULONG len;
|
|
DWORD error;
|
|
char buffer[65535];
|
|
|
|
len = 65535;
|
|
fi = (FIXED_INFO *)buffer;
|
|
|
|
if ((error = pGetNetworkParams(fi, &len)) ==
|
|
ERROR_SUCCESS) {
|
|
IP_ADDR_STRING *pias = &(fi->DnsServerList);
|
|
|
|
while (pias && numdnsservers < 16) {
|
|
strcpy(dnsserverips[numdnsservers++],
|
|
pias->IpAddress.String);
|
|
pias = pias->Next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
FreeLibrary(hiphlpapi);
|
|
}
|
|
|
|
/* Next, try getting the DNS server ips from the registry */
|
|
if (!numdnsservers) {
|
|
HKEY search;
|
|
LONG error;
|
|
|
|
error = RegOpenKeyEx(
|
|
HKEY_LOCAL_MACHINE,
|
|
"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", 0,
|
|
KEY_READ, &search);
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
error = RegOpenKeyEx(
|
|
HKEY_LOCAL_MACHINE,
|
|
"SYSTEM\\CurrentControlSet\\Services\\VxD\\MSTCP", 0,
|
|
KEY_READ, &search);
|
|
}
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
char name[512];
|
|
DWORD len = 512;
|
|
|
|
error = RegQueryValueEx(search, "NameServer", NULL, NULL,
|
|
(LPBYTE)name, &len);
|
|
|
|
if (error != ERROR_SUCCESS) {
|
|
error = RegQueryValueEx(search, "DhcpNameServer", NULL,
|
|
NULL, (LPBYTE)name, &len);
|
|
}
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
char *parse = "0123456789.", *start, *end;
|
|
start = name;
|
|
end = name;
|
|
name[len] = '\0';
|
|
|
|
while (*start && numdnsservers < 16) {
|
|
while (strchr(parse, *end)) {
|
|
end++;
|
|
}
|
|
|
|
strncpy(dnsserverips[numdnsservers++], start,
|
|
end - start);
|
|
|
|
while (*end && !strchr(parse, *end)) {
|
|
end++;
|
|
}
|
|
|
|
start = end;
|
|
}
|
|
}
|
|
}
|
|
|
|
RegCloseKey(search);
|
|
}
|
|
|
|
if (!numdnsservers) {
|
|
HKEY searchlist;
|
|
LONG error;
|
|
|
|
error = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
|
"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\P"
|
|
"arameters\\Interfaces",
|
|
0, KEY_READ, &searchlist);
|
|
|
|
if (error == ERROR_SUCCESS) {
|
|
unsigned int i;
|
|
DWORD numinterfaces = 0;
|
|
|
|
RegQueryInfoKey(searchlist, NULL, NULL, NULL, &numinterfaces,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
|
|
for (i = 0; i < numinterfaces; i++) {
|
|
char name[512];
|
|
DWORD len = 512;
|
|
HKEY searchentry;
|
|
|
|
RegEnumKeyEx(searchlist, i, (LPTSTR)name, &len, NULL, NULL,
|
|
NULL, NULL);
|
|
|
|
if (RegOpenKeyEx(searchlist, name, 0, KEY_READ,
|
|
&searchentry) == ERROR_SUCCESS) {
|
|
if (RegQueryValueEx(searchentry, "DhcpNameServer", NULL,
|
|
NULL, (LPBYTE)name,
|
|
&len) == ERROR_SUCCESS) {
|
|
char *parse = "0123456789.", *start, *end;
|
|
start = name;
|
|
end = name;
|
|
name[len] = '\0';
|
|
|
|
while (*start && numdnsservers < 16) {
|
|
while (strchr(parse, *end)) {
|
|
end++;
|
|
}
|
|
|
|
strncpy(dnsserverips[numdnsservers++], start,
|
|
end - start);
|
|
|
|
while (*end && !strchr(parse, *end)) {
|
|
end++;
|
|
}
|
|
|
|
start = end;
|
|
}
|
|
} else if (RegQueryValueEx(searchentry, "NameServer",
|
|
NULL, NULL, (LPBYTE)name,
|
|
&len) == ERROR_SUCCESS) {
|
|
char *parse = "0123456789.", *start, *end;
|
|
start = name;
|
|
end = name;
|
|
name[len] = '\0';
|
|
|
|
while (*start && numdnsservers < 16) {
|
|
while (strchr(parse, *end)) {
|
|
end++;
|
|
}
|
|
|
|
strncpy(dnsserverips[numdnsservers++], start,
|
|
end - start);
|
|
|
|
while (*end && !strchr(parse, *end)) {
|
|
end++;
|
|
}
|
|
|
|
start = end;
|
|
}
|
|
}
|
|
RegCloseKey(searchentry);
|
|
}
|
|
}
|
|
RegCloseKey(searchlist);
|
|
}
|
|
}
|
|
|
|
/* If we have a DNS server, use it */
|
|
if (numdnsservers) {
|
|
ULONG nonblocking = 1;
|
|
int i;
|
|
|
|
memset(&header, 0, sizeof(header));
|
|
header.id = 12345; /* FIXME: Get a better id here */
|
|
header.rd = 1;
|
|
header.qdcount = 1;
|
|
|
|
netbuf_add_dnsquery_header(buf, (int)len, &offset, &header);
|
|
|
|
memset(&question, 0, sizeof(question));
|
|
strncpy(question.qname, fulldomain, 1024);
|
|
question.qtype = MESSAGE_T_SRV; /* SRV */
|
|
question.qclass = MESSAGE_C_IN; /* INTERNET! */
|
|
|
|
netbuf_add_dnsquery_question(buf, (int)len, &offset, &question);
|
|
|
|
insize = 0;
|
|
for (i = 0; i < numdnsservers && insize <= 0; i++) {
|
|
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
|
ioctlsocket(sock, FIONBIO, &nonblocking);
|
|
|
|
memset(&dnsaddr, 0, sizeof(dnsaddr));
|
|
|
|
dnsaddr.sin_family = AF_INET;
|
|
dnsaddr.sin_port = htons(53);
|
|
dnsaddr.sin_addr.s_addr = inet_addr(dnsserverips[i]);
|
|
|
|
addrlen = sizeof(dnsaddr);
|
|
sendto(sock, (char *)buf, offset, 0,
|
|
(struct sockaddr *)&dnsaddr, addrlen);
|
|
for (j = 0; j < 50; j++) {
|
|
insize = recvfrom(sock, (char *)buf, (int)len, 0,
|
|
(struct sockaddr *)&dnsaddr, &addrlen);
|
|
if (insize == SOCKET_ERROR) {
|
|
if (sock_error() == WSAEWOULDBLOCK) {
|
|
Sleep(100);
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
closesocket(sock);
|
|
}
|
|
set = insize > 0;
|
|
}
|
|
}
|
|
|
|
return set ? insize : -1;
|
|
}
|
|
|
|
#endif /* _WIN32 && !HAVE_CARES */
|