/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2006 Nokia Corporation.
 * Copyright (C) 2006 Dimitri E. Prado.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

/**@CFILE sres.c
 * @brief Sofia DNS Resolver implementation.
 * 
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 * @author Teemu Jalava <Teemu.Jalava@nokia.com>
 * @author Mikko Haataja
 * @author Kai Vehmanen <kai.vehmanen@nokia.com> 
 *         (work on the win32 nameserver discovery) 
 * @author Dimitri E. Prado 
 *         (initial version of win32 nameserver discovery)
 *
 * @todo The resolver should allow handling arbitrary records, too.
 */

#include "config.h"

#if HAVE_STDINT_H
#include <stdint.h>
#elif HAVE_INTTYPES_H
#include <inttypes.h>
#else
#if defined(HAVE_WIN32)
typedef _int8 int8_t;
typedef unsigned _int8 uint8_t;
typedef unsigned _int16 uint16_t;
typedef unsigned _int32 uint32_t;
#endif
#endif

#if HAVE_NETINET_IN_H
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#endif

#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#if HAVE_WINSOCK2_H
#include <winsock2.h>
#include <ws2tcpip.h>
#ifndef IPPROTO_IPV6		/* socklen_t is used with @RFC2133 API */
typedef int socklen_t;
#endif
#endif

#if HAVE_IPHLPAPI_H
#include <iphlpapi.h>
#endif

#include <time.h>

#include "sofia-resolv/sres.h"
#include "sofia-resolv/sres_cache.h"
#include "sofia-resolv/sres_record.h"
#include "sofia-resolv/sres_async.h"

#include <sofia-sip/su_alloc.h>
#include <sofia-sip/su_strlst.h>
#include <sofia-sip/su_errno.h>

#include "sofia-sip/htable.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <stdlib.h>
#include <stdarg.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#include <limits.h>

#include <assert.h>

#if HAVE_WINSOCK2_H
/* Posix send() */
su_inline 
ssize_t sres_send(sres_socket_t s, void *b, size_t length, int flags)
{
  if (length > INT_MAX)
    length = INT_MAX;
  return (ssize_t)send(s, b, (int)length, flags);
}

/* Posix recvfrom() */
su_inline 
ssize_t sres_recvfrom(sres_socket_t s, void *buffer, size_t length, int flags,
		      struct sockaddr *from, socklen_t *fromlen)
{
  int retval, ilen;

  if (fromlen)
    ilen = *fromlen;

  if (length > INT_MAX)
    length = INT_MAX;

  retval = recvfrom(s, buffer, (int)length, flags, 
		    (void *)from, fromlen ? &ilen : NULL);

  if (fromlen)
    *fromlen = ilen;

  return (ssize_t)retval;
}

su_inline
int sres_close(sres_socket_t s)
{
  return closesocket(s);
}

#if !defined(IPPROTO_IPV6)
#if HAVE_SIN6
#include <tpipv6.h>
#else
#if !defined(__MINGW32__)
struct sockaddr_storage {
    short ss_family;
    char ss_pad[126];
};
#endif
#endif
#endif
#else

#define sres_send(s,b,len,flags) send((s),(b),(len),(flags))
#define sres_recvfrom(s,b,len,flags,a,alen) \
  recvfrom((s),(b),(len),(flags),(a),(alen))
#define sres_close(s) close((s))
#define SOCKET_ERROR   (-1)
#define INVALID_SOCKET ((sres_socket_t)-1)
#endif

#define SRES_TIME_MAX ((time_t)LONG_MAX)

#if !HAVE_INET_PTON
int inet_pton(int af, char const *src, void *dst);
#endif
#if !HAVE_INET_NTOP
const char *inet_ntop(int af, void const *src, char *dst, size_t size);
#endif

#if defined(va_copy)
#elif defined(__va_copy)
#define va_copy(dst, src) __va_copy((dst), (src))
#else
#define va_copy(dst, src) (memcpy(&(dst), &(src), sizeof (va_list)))
#endif

/**
 * How often to recheck nameserver information (seconds).
 */
#ifndef HAVE_WIN32
#define SRES_UPDATE_INTERVAL_SECS        5
#else
#define SRES_UPDATE_INTERVAL_SECS        180
#endif
void sres_cache_clean(sres_cache_t *cache, time_t now);

typedef struct sres_message    sres_message_t;
typedef struct sres_config     sres_config_t;
typedef struct sres_server     sres_server_t;
typedef struct sres_nameserver sres_nameserver_t;

/** Default path to resolv.conf */
static char const sres_conf_file_path[] = "/etc/resolv.conf";

/** EDNS0 support. @internal */
enum edns { 
  edns_not_tried = -1,
  edns_not_supported = 0,
  edns0_configured = 1,
  edns0_supported = 2,
};

struct sres_server {
  sres_socket_t           dns_socket;

  char                    dns_name[48];     /**< Server name */
  struct sockaddr_storage dns_addr[1];  /**< Server node address */
  ssize_t                 dns_addrlen;  /**< Size of address */

  enum edns               dns_edns;	/**< Server supports edns. */

  /** ICMP/temporary error received, zero when successful. */
  time_t                  dns_icmp;
  /** Persistent error, zero when successful or timeout. 
   *
   * Never selected if dns_error is SRES_TIME_MAX.
   */
  time_t                  dns_error;
};

HTABLE_DECLARE_WITH(sres_qtable, qt, sres_query_t, unsigned, size_t);

struct sres_resolver_s {
  su_home_t           res_home[1];

  void               *res_userdata;
  sres_cache_t       *res_cache;

  time_t              res_now;
  sres_qtable_t       res_queries[1];   /**< Table of active queries */

  char const         *res_cnffile;      /**< Configuration file name */
  char const        **res_options;      /**< Option strings */

  sres_config_t const *res_config;
  time_t              res_checked;

  unsigned long       res_updated;
  sres_update_f      *res_updcb;
  sres_async_t       *res_async;
  sres_schedule_f    *res_schedulecb;
  short               res_update_all;

  uint16_t            res_id;
  short               res_i_server;  /**< Current server to try 
					(when doing round-robin) */
  short               res_n_servers; /**< Number of servers */
  sres_server_t     **res_servers;
};

/* Parsed configuration. @internal */
struct sres_config {
  su_home_t c_home[1];

  time_t c_modified;
  char const *c_filename;

  /* domain and search */
  char const *c_search[SRES_MAX_SEARCH + 1];

  /* nameserver */
  struct sres_nameserver {
    struct sockaddr_storage ns_addr[1];
    ssize_t ns_addrlen;
  } *c_nameservers[SRES_MAX_NAMESERVERS + 1];

  /* sortlist */
  struct sres_sortlist {
    struct sockaddr_storage addr[1];
    ssize_t addrlen;
    char const *name;
  } *c_sortlist[SRES_MAX_SORTLIST + 1];

  uint16_t    c_port;	     /**< Server port to use */

  /* options */
  struct sres_options {
    uint16_t timeout;
    uint16_t attempts;
    uint16_t ndots;
    enum edns edns;
    unsigned debug:1;
    unsigned rotate:1;
    unsigned check_names:1;
    unsigned inet6:1;
    unsigned ip6int:1;
    unsigned ip6bytestring:1;
  } c_opt;
};

struct sres_query_s {
  unsigned        q_hash;
  sres_resolver_t*q_res;
  sres_answer_f  *q_callback;
  sres_context_t *q_context;
  char           *q_name;
  time_t          q_timestamp;
  uint16_t        q_type;
  uint16_t        q_class;
  uint16_t        q_id;			/**< If nonzero, not answered */
  uint16_t        q_retry_count;
  uint8_t         q_n_servers;
  uint8_t         q_i_server;
  int8_t          q_edns;
  uint8_t         q_n_subs;
  sres_query_t   *q_subqueries[1 + SRES_MAX_SEARCH];
  sres_record_t **q_subanswers[1 + SRES_MAX_SEARCH];
};


struct sres_message {
  uint16_t m_offset;
  uint16_t m_size;
  char const *m_error;
  union {
    struct {
      /* Header defined in RFC 1035 section 4.1.1 (page 26) */
      uint16_t mh_id;		/* Query ID */
      uint16_t mh_flags;	/* Flags */
      uint16_t mh_qdcount;	/* Question record count */
      uint16_t mh_ancount;	/* Answer record count */
      uint16_t mh_nscount;	/* Authority records count */
      uint16_t mh_arcount;	/* Additional records count */
    } mp_header;
    uint8_t mp_data[1500 - 40];	/**< IPv6 datagram */
  } m_packet;
#define m_id      m_packet.mp_header.mh_id
#define m_flags   m_packet.mp_header.mh_flags
#define m_qdcount m_packet.mp_header.mh_qdcount
#define m_ancount m_packet.mp_header.mh_ancount
#define m_nscount m_packet.mp_header.mh_nscount
#define m_arcount m_packet.mp_header.mh_arcount 
#define m_data    m_packet.mp_data
};

#define sr_refcount sr_record->r_refcount
#define sr_name     sr_record->r_name
#define sr_status   sr_record->r_status
#define sr_size     sr_record->r_size
#define sr_type     sr_record->r_type
#define sr_class    sr_record->r_class
#define sr_ttl      sr_record->r_ttl
#define sr_rdlen    sr_record->r_rdlen
#define sr_parsed   sr_record->r_parsed
#define sr_rdata    sr_generic->g_data

enum {
  SRES_HDR_QR = (1 << 15),
  SRES_HDR_QUERY = (0 << 11),
  SRES_HDR_IQUERY = (1 << 11),
  SRES_HDR_STATUS = (2 << 11),
  SRES_HDR_OPCODE = (15 << 11),	/* mask */
  SRES_HDR_AA = (1 << 10),
  SRES_HDR_TC = (1 << 9),
  SRES_HDR_RD = (1 << 8),
  SRES_HDR_RA = (1 << 7),
  SRES_HDR_RCODE = (15 << 0)	/* mask of return code */
};

HTABLE_PROTOS_WITH(sres_qtable, qt, sres_query_t, unsigned, size_t);

#define CHOME(cache) ((su_home_t *)(cache))

/** Get address from sockaddr storage. */
#if HAVE_SIN6
#define SS_ADDR(ss) \
  ((ss)->ss_family == AF_INET ? \
   (void *)&((struct sockaddr_in *)ss)->sin_addr : \
  ((ss)->ss_family == AF_INET6 ? \
   (void *)&((struct sockaddr_in6 *)ss)->sin6_addr : \
   (void *)&((struct sockaddr *)ss)->sa_data))
#else
#define SS_ADDR(ss) \
  ((ss)->ss_family == AF_INET ? \
   (void *)&((struct sockaddr_in *)ss)->sin_addr : \
   (void *)&((struct sockaddr *)ss)->sa_data)
#endif

static int sres_config_changed_servers(sres_config_t const *new_c, 
				       sres_config_t const *old_c);
static sres_server_t **sres_servers_new(sres_resolver_t *res,
					sres_config_t const *c);

/** Generate new 16-bit identifier for DNS query. */
static uint16_t
sres_new_id(sres_resolver_t *res)
{
  return res->res_id ? res->res_id++ : (res->res_id = 2, 1);
}

/** Return true if we have a search list or a local domain name. */
static int 
sres_has_search_domain(sres_resolver_t *res) 
{
  return res->res_config->c_search[0] != NULL;
}

static void sres_resolver_destructor(void *);

sres_resolver_t *
sres_resolver_new_with_cache_va(char const *conf_file_path,
				sres_cache_t *cache,
				char const *options, 
				va_list va);
static
sres_resolver_t *
sres_resolver_new_internal(sres_cache_t *cache,
			   sres_config_t const *config,
			   char const *conf_file_path,
			   char const **options);

static void sres_servers_close(sres_resolver_t *res,
			       sres_server_t **servers);

static int sres_servers_count(sres_server_t * const *servers);

static sres_socket_t sres_server_socket(sres_resolver_t *res,
					sres_server_t *dns);

static sres_query_t * sres_query_alloc(sres_resolver_t *res,
				       sres_answer_f *callback,
				       sres_context_t *context,
				       uint16_t type,
				       char const * domain);

static void sres_free_query(sres_resolver_t *res, sres_query_t *q);

static 
int sres_sockaddr2string(sres_resolver_t *, 
			 char name[], size_t namelen, 
			 struct sockaddr const *);

static 
sres_config_t *sres_parse_resolv_conf(sres_resolver_t *res,
				      char const **options);

static
sres_server_t *sres_next_server(sres_resolver_t *res, 
				uint8_t *in_out_i,
				int always);

static
int sres_send_dns_query(sres_resolver_t *res, sres_query_t *q);

static 
void sres_answer_subquery(sres_context_t *context, 
			  sres_query_t *query,
			  sres_record_t **answers);

static
sres_record_t **
sres_combine_results(sres_resolver_t *res,
		     sres_record_t **search_results[SRES_MAX_SEARCH + 1]);

static
void sres_query_report_error(sres_query_t *q,
			     sres_record_t **answers);

static void
sres_resend_dns_query(sres_resolver_t *res, sres_query_t *q, int timeout);

static 
sres_server_t *sres_server_by_socket(sres_resolver_t const *ts,
				     sres_socket_t socket);

static
int sres_resolver_report_error(sres_resolver_t *res, 
			       sres_socket_t socket,
			       int errcode,
			       struct sockaddr_storage *remote,
			       socklen_t remotelen, 
			       char const *info);

static
void sres_log_response(sres_resolver_t const *res, 
		       sres_message_t const *m,
		       struct sockaddr_storage const *from,
		       sres_query_t const *query,
		       sres_record_t * const *reply);

static int sres_decode_msg(sres_resolver_t *res, 
			   sres_message_t *m,
			   sres_query_t **,
			   sres_record_t ***aanswers);

static char const *sres_toplevel(char buf[], size_t bsize, char const *domain);

static sres_record_t *sres_create_record(sres_resolver_t *, sres_message_t *m);

static sres_record_t *sres_init_rr_soa(sres_cache_t *cache,
				       sres_soa_record_t *,
				       sres_message_t *m);
static sres_record_t *sres_init_rr_a(sres_cache_t *cache,
				     sres_a_record_t *,
				     sres_message_t *m);
static sres_record_t *sres_init_rr_a6(sres_cache_t *cache,
				      sres_a6_record_t *,
				      sres_message_t *m);
static sres_record_t *sres_init_rr_aaaa(sres_cache_t *cache,
					sres_aaaa_record_t *,
					sres_message_t *m);
static sres_record_t *sres_init_rr_cname(sres_cache_t *cache,
					 sres_cname_record_t *,
					 sres_message_t *m);
static sres_record_t *sres_init_rr_ptr(sres_cache_t *cache,
				       sres_ptr_record_t *,
				       sres_message_t *m);
static sres_record_t *sres_init_rr_srv(sres_cache_t *cache,
				       sres_srv_record_t *,
				       sres_message_t *m);
static sres_record_t *sres_init_rr_naptr(sres_cache_t *cache,
					 sres_naptr_record_t *,
					 sres_message_t *m);
static sres_record_t *sres_init_rr_unknown(sres_cache_t *cache,
					   sres_common_t *r,
					   sres_message_t *m);

static sres_record_t *sres_create_error_rr(sres_cache_t *cache,
                                           sres_query_t const *q,
                                           uint16_t errcode);

static void m_put_uint16(sres_message_t *m, uint16_t h);
static void m_put_uint32(sres_message_t *m, uint32_t w);

static uint16_t m_put_domain(sres_message_t *m, 
                             char const *domain, 
                             uint16_t top, 
                             char const *topdomain);

static uint32_t m_get_uint32(sres_message_t *m);
static uint16_t m_get_uint16(sres_message_t *m);
static uint8_t m_get_uint8(sres_message_t *m);

static int m_get_string(char *d, int n, sres_message_t *m, uint16_t offset);
static int m_get_domain(char *d, int n, sres_message_t *m, uint16_t offset);

/* ---------------------------------------------------------------------- */

#define SU_LOG sresolv_log

#include <sofia-sip/su_debug.h>

#ifdef HAVE_WIN32
#include <winreg.h>
#endif

/**@ingroup sresolv_env
 *
 * Environment variable determining the debug log level for @b sresolv
 * module.
 *
 * The SRESOLV_DEBUG environment variable is used to determine the debug
 * logging level for @b sresolv module. The default level is 3.
 * 
 * @sa <su_debug.h>, sresolv_log, SOFIA_DEBUG
 */
#ifdef DOXYGEN
extern char const SRESOLV_DEBUG[]; /* dummy declaration for Doxygen */
#endif

/**Debug log for @b sresolv module. 
 * 
 * The sresolv_log is the log object used by @b sresolv module. The level of
 * #sresolv_log is set using #SRESOLV_DEBUG environment variable.
 */
su_log_t sresolv_log[] = { SU_LOG_INIT("sresolv", "SRESOLV_DEBUG", 3) };

/** Internal errors */
enum {
  SRES_EDNS0_ERR = 255		/**< Server did not support EDNS. */
};

/* ---------------------------------------------------------------------- */

/**Create a resolver.
 *
 * Allocate and initialize a new sres resolver object. The resolver object
 * contains the parsed resolv.conf file, a cache object containing past
 * answers from DNS, and a list of active queries. The default resolv.conf
 * file can be overriden by giving the name of the configuration file as @a
 * conf_file_path.
 *
 * @param conf_file_path name of the resolv.conf configuration file 
 *
 * @return A pointer to a newly created sres resolver object, or NULL upon
 * an error.
 */
sres_resolver_t *
sres_resolver_new(char const *conf_file_path)
{
  return sres_resolver_new_internal(NULL, NULL, conf_file_path, NULL);
}

/** Copy a resolver.
 *
 * Make a copy of resolver sharing the configuration and cache with old
 * resolver.
 */
sres_resolver_t *sres_resolver_copy(sres_resolver_t *res)
{
  char const *cnffile;
  sres_config_t *config;
  sres_cache_t *cache;
  char const **options;

  if (!res)
    return NULL;

  cnffile = res->res_cnffile;
  config = su_home_ref(res->res_config->c_home);
  cache = res->res_cache;
  options = res->res_options;

  return sres_resolver_new_internal(cache, config, cnffile, options);
}

/**New resolver object.
 *
 * Allocate and initialize a new sres resolver object. The resolver object
 * contains the parsed resolv.conf file, a cache object containing past
 * answers from DNS, and a list of active queries. The default resolv.conf
 * file can be overriden by giving the name of the configuration file as @a
 * conf_file_path.
 *
 * It is also possible to override the values in the resolv.conf and
 * RES_OPTIONS by giving the directives in the NULL-terminated list.
 *
 * @param conf_file_path name of the resolv.conf configuration file 
 * @param cache          optional pointer to a resolver cache (may be NULL)
 * @param option, ...    list of resolv.conf options directives 
 *                       (overriding options in conf_file)
 *
 * @par Environment Variables
 * - #LOCALDOMAIN overrides @c domain or @c search directives
 * - #RES_OPTIONS overrides values of @a options in resolv.conf
 * - #SRES_OPTIONS overrides values of @a options in resolv.conf, #RES_OPTIONS,
 *   and @a options, ... list given as argument for this function
 *
 * @return A pointer to a newly created sres resolver object, or NULL upon
 * an error.
 */
sres_resolver_t *
sres_resolver_new_with_cache(char const *conf_file_path,
			     sres_cache_t *cache,
			     char const *option, ...)
{
  sres_resolver_t *retval;
  va_list va;
  va_start(va, option);
  retval = sres_resolver_new_with_cache_va(conf_file_path, cache, option, va);
  va_end(va);
  return retval;
}

/**Create a resolver.
 *
 * Allocate and initialize a new sres resolver object. 
 *
 * This is a stdarg version of sres_resolver_new_with_cache().
 */
sres_resolver_t *
sres_resolver_new_with_cache_va(char const *conf_file_path,
				sres_cache_t *cache,
				char const *option,
				va_list va)
{
  va_list va0;
  size_t i;
  char const *o, *oarray[16], **olist = oarray;
  sres_resolver_t *res;

  va_copy(va0, va);
  
  for (i = 0, o = option; o; o = va_arg(va0, char const *)) {
    if (i < 16)
      olist[i] = o;
    i++;
  }

  if (i >= 16) {
    olist = malloc((i + 1) * sizeof *olist);
    if (!olist)
      return NULL;
    for (i = 0, o = option; o; o = va_arg(va, char const *)) {
      olist[i++] = o;
      i++;
    }
  }
  olist[i] = NULL;
  res = sres_resolver_new_internal(cache, NULL, conf_file_path, olist);
  if (olist != oarray)
    free(olist);

  return res;
}

sres_resolver_t *
sres_resolver_new_internal(sres_cache_t *cache,
			   sres_config_t const *config,
			   char const *conf_file_path,
			   char const **options)
{
  sres_resolver_t *res;
  size_t i, n, len;
  char **array, *o, *end;
 
  for (n = 0, len = 0; options && options[n]; n++)
    len += strlen(options[n]) + 1;

  res = su_home_new(sizeof(*res) + (n + 1) * (sizeof *options) + len);

  if (res == NULL)
    return NULL;

  array = (void *)(res + 1);
  o = (void *)(array + n + 1);
  end = o + len;

  for (i = 0; options && options[i]; i++)
    o = memccpy(array[i] = o, options[i], '\0', len - (end - o));
  assert(o == end);

  su_home_destructor(res->res_home, sres_resolver_destructor);

  while (res->res_id == 0) {
#if HAVE_DEV_URANDOM
    int fd;
    if ((fd = open("/dev/urandom", O_RDONLY, 0)) != -1) {
      size_t len = read(fd, &res->res_id, (sizeof res->res_id)); (void)len;
      close(fd);
    }
    else
#endif
    res->res_id = time(NULL);
  }

  time(&res->res_now);

  if (cache)
    res->res_cache = sres_cache_ref(cache);
  else
    res->res_cache = sres_cache_new(0);

  res->res_config = config;

  if (conf_file_path && conf_file_path != sres_conf_file_path)
    res->res_cnffile = su_strdup(res->res_home, conf_file_path);
  else
    res->res_cnffile = conf_file_path = sres_conf_file_path;

  if (!res->res_cache || !res->res_cnffile) {
    perror("sres: malloc");
  }
  else if (sres_qtable_resize(res->res_home, res->res_queries, 0) < 0) {
    perror("sres: res_qtable_resize");
  }
  else if (sres_resolver_update(res, config == NULL) < 0) {
    perror("sres: sres_resolver_update");
  }
  else {
    return res;
  }

  sres_resolver_unref(res);

  return NULL;
}

/** Increase reference count on a resolver object. */
sres_resolver_t *
sres_resolver_ref(sres_resolver_t *res)
{
  return su_home_ref(res->res_home);
}
		     
/** Decrease the reference count on a resolver object.  */
void
sres_resolver_unref(sres_resolver_t *res)
{
  su_home_unref(res->res_home);
}

/** Set userdata pointer.
 *
 * @return New userdata pointer.
 * 
 * @ERRORS
 * @ERROR EFAULT @a res points outside the address space
 */
void *
sres_resolver_set_userdata(sres_resolver_t *res, 
			   void *userdata)
{
  void *old;

  if (!res)
    return su_seterrno(EFAULT), (void *)NULL;

  old = res->res_userdata, res->res_userdata = userdata;

  return old;
}

/**Get userdata pointer.
 *
 * @return Userdata pointer.
 * 
 * @ERRORS
 * @ERROR EFAULT @a res points outside the address space
 */
void *
sres_resolver_get_userdata(sres_resolver_t const *res)
{
  if (res == NULL)
    return su_seterrno(EFAULT), (void *)NULL;
  else
    return res->res_userdata;
}

/** Set async object.
 *
 * @return Set async object.
 * 
 * @ERRORS
 * @ERROR EFAULT @a res points outside the address space
 * @ERROR EALREADY different async callback already set
 */
sres_async_t *
sres_resolver_set_async(sres_resolver_t *res,
			sres_update_f *callback,
			sres_async_t *async, 
			int update_all)
{
  if (!res)
    return su_seterrno(EFAULT), (void *)NULL;

  if (res->res_updcb && res->res_updcb != callback)
    return su_seterrno(EALREADY), (void *)NULL;
    
  res->res_async = async;
  res->res_updcb = callback;
  res->res_update_all = callback && update_all != 0;

  return async;
}

/** Get async object */
sres_async_t *
sres_resolver_get_async(sres_resolver_t const *res,
			sres_update_f *callback)
{
  if (res == NULL)
    return su_seterrno(EFAULT), (void *)NULL;
  else if (callback == NULL)
    return res->res_async ? (sres_async_t *)-1 : 0;
  else if (res->res_updcb != callback)
    return NULL;
  else
    return res->res_async;
}

/** Register resolver timer callback. */
int sres_resolver_set_timer_cb(sres_resolver_t *res,
			       sres_schedule_f *callback,
			       sres_async_t *async)
{
  if (res == NULL)
    return su_seterrno(EFAULT);
  if (res->res_async != async)
    return su_seterrno(EALREADY);

  res->res_schedulecb = callback;
  return 0;
}

/**Send a DNS query.
 *
 * Sends a DNS query with specified @a type and @a domain to the DNS server.
 * When an answer is received, the @a callback function is called with
 * @a context and returned records as arguments.
 *
 * The sres resolver takes care of retransmitting the query if a root object
 * is associate with the resolver or if sres_resolver_timer() is called in
 * regular intervals. It generates an error record with nonzero status if no
 * response is received.
 *
 * @param res pointer to resolver
 * @param callback function called when query is answered or times out
 * @param context pointer given as an extra argument to @a callback function
 * @param type record type to query (see #sres_qtypes)
 * @param domain name to query
 *
 * Query types also indicate the record type of the result.
 * Any record can be queried with #sres_qtype_any.
 * Well-known query types understood and decoded by @b sres include
 * #sres_type_a, 
 * #sres_type_aaaa,
 * #sres_type_cname,
 * #sres_type_ptr 
 * #sres_type_soa, 
 * #sres_type_aaaa, 
 * #sres_type_srv, and
 * #sres_type_naptr.
 *
 * Deprecated query type #sres_type_a6 is also decoded.
 *
 * @note The domain name is @b not concatenated with the domains from seach
 * path or with the local domain. Use sres_search() in order to try domains
 * in search path.
 *
 * @sa sres_search(), sres_blocking_query(), sres_cached_answers(),
 * sres_query_sockaddr()
 *
 * @ERRORS
 * @ERROR EFAULT @a res or @a domain point outside the address space
 * @ERROR ENAMETOOLONG @a domain is longer than SRES_MAXDNAME
 * @ERROR ENETDOWN no DNS servers configured
 * @ERROR ENOMEM memory exhausted
 */
sres_query_t *
sres_query(sres_resolver_t *res,
	   sres_answer_f *callback,
	   sres_context_t *context,
	   uint16_t type,
	   char const *domain)
{
  sres_query_t *query = NULL;
  size_t dlen;
  
  char b[8];
  SU_DEBUG_9(("sres_query(%p, %p, %s, \"%s\") called\n",
			  (void *)res, (void *)context, sres_record_type(type, b), domain));

  if (res == NULL || domain == NULL)
    return su_seterrno(EFAULT), (void *)NULL;

  dlen = strlen(domain);
  if (dlen > SRES_MAXDNAME ||
      (dlen == SRES_MAXDNAME && domain[dlen - 1] != '.')) {
    su_seterrno(ENAMETOOLONG);
    return NULL;
  }

  /* Reread resolv.conf if needed */
  sres_resolver_update(res, 0);

  if (res->res_n_servers == 0)
    return (void)su_seterrno(ENETDOWN), (sres_query_t *)NULL;

  query = sres_query_alloc(res, callback, context, type, domain);

  if (query && sres_send_dns_query(res, query) != 0)
    sres_free_query(res, query), query = NULL;

  return query;
}

/**Search DNS.
 *
 * Sends DNS queries with specified @a type and @a name to the DNS server. 
 * If the @a name does not contain enought dots, the search domains are
 * appended to the name and resulting domain name are also queried. When
 * answer to all the search domains is received, the @a callback function
 * is called with @a context and combined records from answers as arguments.
 * 
 * The sres resolver takes care of retransmitting the queries if a root
 * object is associate with the resolver or if sres_resolver_timer() is
 * called in regular intervals. It generates an error record with nonzero
 * status if no response is received.
 *
 * @param res pointer to resolver object
 * @param callback pointer to completion function
 * @param context argument given to the completion function
 * @param type record type to search (or sres_qtype_any for any record)
 * @param name host or domain name to search from DNS
 *
 * @ERRORS
 * @ERROR EFAULT @a res or @a domain point outside the address space
 * @ERROR ENAMETOOLONG @a domain is longer than SRES_MAXDNAME
 * @ERROR ENETDOWN no DNS servers configured
 * @ERROR ENOMEM memory exhausted
 *
 * @sa sres_query(), sres_blocking_search(), sres_search_cached_answers().
 */
sres_query_t *
sres_search(sres_resolver_t *res,
	    sres_answer_f *callback,
	    sres_context_t *context,
	    uint16_t type,
	    char const *name)
{
  char const *domain = name;
  sres_query_t *query = NULL;
  size_t dlen;
  unsigned dots; char const *dot;
  char b[8];

  SU_DEBUG_9(("sres_search(%p, %p, %s, \"%s\") called\n",
			  (void *)res, (void *)context, sres_record_type(type, b), domain));

  if (res == NULL || domain == NULL)
    return su_seterrno(EFAULT), (void *)NULL;

  dlen = strlen(domain);
  if (dlen > SRES_MAXDNAME ||
      (dlen == SRES_MAXDNAME && domain[dlen - 1] != '.')) {
    su_seterrno(ENAMETOOLONG);
    return NULL;
  }

  sres_resolver_update(res, 0);

  if (res->res_n_servers == 0)
    return (void)su_seterrno(ENETDOWN), (sres_query_t *)NULL;

  if (sres_has_search_domain(res))
    for (dots = 0, dot = strchr(domain, '.');
	 dots < res->res_config->c_opt.ndots && dot; 
	 dots++, dot = strchr(dot + 1, '.'))
      ;
  else
    dots = 0;

  query = sres_query_alloc(res, callback, context, type, domain);

  if (query) {
    /* Create sub-query for each search domain */
    if (dots < res->res_config->c_opt.ndots) {
      sres_query_t *sub;
      int i, subs;
      size_t len;
      char const *const *domains = res->res_config->c_search;
      char search[SRES_MAXDNAME + 1];

      memcpy(search, domain, dlen);
      search[dlen++] = '.';
      search[dlen] = '\0';

      for (i = 0, subs = 0; i <= SRES_MAX_SEARCH; i++) {
	if (domains[i]) {
	  len = strlen(domains[i]);
	  
	  if (dlen + len + 1 > SRES_MAXDNAME)
	    continue;

	  memcpy(search + dlen, domains[i], len);
	  search[dlen + len] = '.';
	  search[dlen + len + 1] = '\0';
	  sub = sres_query_alloc(res, sres_answer_subquery, (void *)query,
				 type, search);

	  if (sres_send_dns_query(res, sub) == 0) {
	    query->q_subqueries[i] = sub;
	  }
	  else {
	    sres_free_query(res, sub), sub = NULL;
	  }
	  subs += sub != NULL;
	}
      }

      query->q_n_subs = subs;
    }

    if (sres_send_dns_query(res, query) != 0) {
      if (!query->q_n_subs)
	sres_free_query(res, query), query = NULL;
      else
	query->q_id = 0;
    }
  }

  return query;
}

/** Make a reverse DNS query.
 *
 * Send a query to DNS server with specified @a type and domain name formed
 * from the socket address @a addr. The sres resolver takes care of
 * retransmitting the query if a root object is associate with the resolver or
 * if sres_resolver_timer() is called in regular intervals. It generates an
 * error record with nonzero status if no response is received.
 *
 * @param res pointer to resolver
 * @param callback function called when query is answered or times out
 * @param context pointer given as an extra argument to @a callback function
 * @param type record type to query (or sres_qtype_any for any record)
 * @param addr socket address structure
 * 
 * The @a type should be #sres_type_ptr. The @a addr should contain either
 * IPv4 (AF_INET) or IPv6 (AF_INET6) address.
 *
 * If the #SRES_OPTIONS environment variable, #RES_OPTIONS environment
 * variable, or an "options" entry in resolv.conf file contains an option
 * "ip6-dotint", the IPv6 addresses are resolved using suffix ".ip6.int"
 * instead of the standard ".ip6.arpa" suffix.
 *
 * @ERRORS
 * @ERROR EAFNOSUPPORT address family specified in @a addr is not supported
 * @ERROR ENETDOWN no DNS servers configured
 * @ERROR EFAULT @a res or @a addr point outside the address space
 * @ERROR ENOMEM memory exhausted
 *
 * @sa sres_query(), sres_blocking_query_sockaddr(),
 * sres_cached_answers_sockaddr()
 *
 */
sres_query_t *
sres_query_sockaddr(sres_resolver_t *res,
		    sres_answer_f *callback,
		    sres_context_t *context,
		    uint16_t type,
		    struct sockaddr const *addr)
{
  char name[80]; 

  if (!res || !addr)
    return su_seterrno(EFAULT), (void *)NULL;

  if (!sres_sockaddr2string(res, name, sizeof(name), addr))
    return NULL;

  return sres_query(res, callback, context, type, name);
}


/** Make a DNS query.
 *
 * @deprecated Use sres_query() instead.
 */
sres_query_t *
sres_query_make(sres_resolver_t *res,
		sres_answer_f *callback,
		sres_context_t *context,
		int dummy,
		uint16_t type,
		char const *domain)
{
  return sres_query(res, callback, context, type, domain);
}

/** Make a reverse DNS query.
 *
 * @deprecated Use sres_query_sockaddr() instead.
 */
sres_query_t *
sres_query_make_sockaddr(sres_resolver_t *res,
			 sres_answer_f *callback,
			 sres_context_t *context,
			 int dummy,
			 uint16_t type,
			 struct sockaddr const *addr)
{
  char name[80]; 

  if (!res || !addr)
    return su_seterrno(EFAULT), (void *)NULL;

  if (!sres_sockaddr2string(res, name, sizeof(name), addr))
    return NULL;

  return sres_query_make(res, callback, context, dummy, type, name);
}


/** Bind a query with another callback and context pointer.
 *
 * @param query pointer to a query object to bind
 * @param callback pointer to new callback function (may be NULL)
 * @param context pointer to callback context (may be NULL)
*/
void sres_query_bind(sres_query_t *query,
                     sres_answer_f *callback,
                     sres_context_t *context)
{
  if (query) {
    query->q_callback = callback;
    query->q_context = context;
  }
}

/**Get a list of matching (type/domain) records from cache.
 *
 * @return
 * pointer to an array of pointers to cached records, or
 * NULL if no entry was found.
 *
 * @ERRORS
 * @ERROR ENAMETOOLONG @a domain is longer than SRES_MAXDNAME
 * @ERROR ENOENT no cached records were found
 * @ERROR EFAULT @a res or @a domain point outside the address space
 * @ERROR ENOMEM memory exhausted
 */
sres_record_t **
sres_cached_answers(sres_resolver_t *res,
		    uint16_t type,
		    char const *domain)
{
  sres_record_t **result;
  char rooted_domain[SRES_MAXDNAME];

  if (!res)
    return su_seterrno(EFAULT), (void *)NULL;

  domain = sres_toplevel(rooted_domain, sizeof rooted_domain, domain);

  if (!domain)
    return NULL;
  
  if (!sres_cache_get(res->res_cache, type, domain, &result))
    return su_seterrno(ENOENT), (void *)NULL;

  return result;
}

/**Search for a list of matching (type/name) records from cache.
 *
 * @return
 * pointer to an array of pointers to cached records, or
 * NULL if no entry was found.
 *
 * @ERRORS
 * @ERROR ENAMETOOLONG @a name or resulting domain is longer than SRES_MAXDNAME
 * @ERROR ENOENT no cached records were found
 * @ERROR EFAULT @a res or @a domain point outside the address space
 * @ERROR ENOMEM memory exhausted
 *
 * @sa sres_search(), sres_cached_answers()
 */
sres_record_t **
sres_search_cached_answers(sres_resolver_t *res,
			   uint16_t type,
			   char const *name)
{
  char const *domain = name;
  sres_record_t **search_results[SRES_MAX_SEARCH + 1] = { NULL };
  char rooted_domain[SRES_MAXDNAME];
  unsigned dots; char const *dot;
  size_t found = 0;
  int i;

  SU_DEBUG_9(("sres_search_cached_answers(%p, %s, \"%s\") called\n",
	      (void *)res, sres_record_type(type, rooted_domain), domain));

  if (!res || !name)
    return su_seterrno(EFAULT), (void *)NULL;

  if (sres_has_search_domain(res))
    for (dots = 0, dot = strchr(domain, '.');
	 dots < res->res_config->c_opt.ndots && dot; 
	 dots++, dot = strchr(dot + 1, '.'))
      ;
  else
    dots = 0;

  domain = sres_toplevel(rooted_domain, sizeof rooted_domain, domain);

  if (!domain)
    return NULL;

  if (sres_cache_get(res->res_cache, type, domain, &search_results[0]))
    found = 1;

  if (dots < res->res_config->c_opt.ndots) {
    char const *const *domains = res->res_config->c_search;
    size_t dlen = strlen(domain);

    for (i = 0; domains[i] && i < SRES_MAX_SEARCH; i++) {
      size_t len = strlen(domains[i]);
      if (dlen + len + 1 >= SRES_MAXDNAME)
	continue;
      if (domain != rooted_domain)
	domain = memcpy(rooted_domain, domain, dlen);
      memcpy(rooted_domain + dlen, domains[i], len);
      strcpy(rooted_domain + dlen + len, ".");
      if (sres_cache_get(res->res_cache, type, domain, search_results + i + 1))
	found++;
    }
  }

  if (found == 0)
    return su_seterrno(ENOENT), (void *)NULL;

  if (found == 1) {
    for (i = 0; i <= SRES_MAX_SEARCH; i++)
      if (search_results[i])
	return search_results[i];
  }

  return sres_combine_results(res, search_results);
}

/**Get a list of matching (type/domain) reverse records from cache.
 *
 * @param res pointer to resolver
 * @param type record type to query (or sres_qtype_any for any record)
 * @param addr socket address structure
 * 
 * The @a type should be #sres_type_ptr. The @a addr should contain either
 * IPv4 (AF_INET) or IPv6 (AF_INET6) address.
 *
 * If the #SRES_OPTIONS environment variable, #RES_OPTIONS environment
 * variable or an "options" entry in resolv.conf file contains an option
 * "ip6-dotint", the IPv6 addresses are resolved using suffix ".ip6.int"
 * instead of default ".ip6.arpa".
 *
 * @retval 
 * pointer to an array of pointers to cached records, or
 * NULL if no entry was found.
 *
 * @ERRORS
 * @ERROR EAFNOSUPPORT address family specified in @a addr is not supported
 * @ERROR ENOENT no cached records were found
 * @ERROR EFAULT @a res or @a addr point outside the address space
 * @ERROR ENOMEM memory exhausted
 */
sres_record_t **
sres_cached_answers_sockaddr(sres_resolver_t *res,
			     uint16_t type,
			     struct sockaddr const *addr)
{
  sres_record_t **result;
  char name[80];

  if (!res || !addr)
    return su_seterrno(EFAULT), (void *)NULL;

  if (!sres_sockaddr2string(res, name, sizeof name, addr))
    return NULL;

  if (!sres_cache_get(res->res_cache, type, name, &result))
    su_seterrno(ENOENT), (void *)NULL;

  return result;
}

/** Sort answers. */
int
sres_sort_answers(sres_resolver_t *res, sres_record_t **answers)
{
  int i, j;

  if (res == NULL || answers == NULL)
    return su_seterrno(EFAULT);

  if (answers[0] == NULL || answers[1] == NULL)
    return 0;

  /* Simple insertion sorting */
  /*
   * We do not use qsort because we want later extend this to sort 
   * local A records first etc.
   */
  for (i = 1; answers[i]; i++) {
    for (j = 0; j < i; j++) {
      if (sres_record_compare(answers[i], answers[j]) < 0)
	break;
    }
    if (j < i) {
      sres_record_t *r = answers[i];
      for (; j < i; i--) {
	answers[i] = answers[i - 1];
      }
      answers[j] = r;
    }
  }

  return 0;
}

/** Sort and filter query results */
int
sres_filter_answers(sres_resolver_t *res, 
		    sres_record_t **answers, 
		    uint16_t type)
{		    
  int i, n;

  for (n = 0, i = 0; answers && answers[i]; i++) {
    if (answers[i]->sr_record->r_status ||
	answers[i]->sr_record->r_class != sres_class_in ||
	(type != 0 && answers[i]->sr_record->r_type != type)) {
      sres_free_answer(res, answers[i]);
      continue;
    }
    answers[n++] = answers[i];
  }
  answers[n] = NULL;

  sres_sort_answers(res, answers);

  return n;
}


/** Free and zero one record. */
void sres_free_answer(sres_resolver_t *res, sres_record_t *answer)
{
  if (res && answer)
    sres_cache_free_one(res->res_cache, answer);
}

/** Free and zero an array of records.
 * 
 * The array of records can be returned by sres_cached_answers() or 
 * given by callback function.
 */
void 
sres_free_answers(sres_resolver_t *res,
		  sres_record_t **answers)
{
  if (res && answers)
    sres_cache_free_answers(res->res_cache, answers);
}

/** Convert type to its name. */
char const *sres_record_type(int type, char buffer[8])
{
  switch (type) {
  case sres_type_a: return "A";
  case sres_type_ns: return "NS";
  case sres_type_mf: return "MF";
  case sres_type_cname: return "CNAME";
  case sres_type_soa: return "SOA";
  case sres_type_mb: return "MB";
  case sres_type_mg: return "MG";
  case sres_type_mr: return "MR";
  case sres_type_null: return "NULL";
  case sres_type_wks: return "WKS";
  case sres_type_ptr: return "PTR";
  case sres_type_hinfo: return "HINFO";
  case sres_type_minfo: return "MINFO";
  case sres_type_mx: return "MX";
  case sres_type_txt: return "TXT";
  case sres_type_rp: return "RP";
  case sres_type_afsdb: return "AFSDB";
  case sres_type_x25: return "X25";
  case sres_type_isdn: return "ISDN";
  case sres_type_rt: return "RT";
  case sres_type_nsap: return "NSAP";
  case sres_type_nsap_ptr: return "NSAP_PTR";
  case sres_type_sig: return "SIG";
  case sres_type_key: return "KEY";
  case sres_type_px: return "PX";
  case sres_type_gpos: return "GPOS";
  case sres_type_aaaa: return "AAAA";
  case sres_type_loc: return "LOC";
  case sres_type_nxt: return "NXT";
  case sres_type_eid: return "EID";
  case sres_type_nimloc: return "NIMLOC";
  case sres_type_srv: return "SRV";
  case sres_type_atma: return "ATMA";
  case sres_type_naptr: return "NAPTR";
  case sres_type_kx: return "KX";
  case sres_type_cert: return "CERT";
  case sres_type_a6: return "A6";
  case sres_type_dname: return "DNAME";
  case sres_type_sink: return "SINK";
  case sres_type_opt: return "OPT";

  case sres_qtype_tsig: return "TSIG";
  case sres_qtype_ixfr: return "IXFR";
  case sres_qtype_axfr: return "AXFR";
  case sres_qtype_mailb: return "MAILB";
  case sres_qtype_maila: return "MAILA";
  case sres_qtype_any: return "ANY";
    
  default:
    sprintf(buffer, "%u?", type & 65535);
    return buffer;
  }
}

/** Convert class to its name. */
char const *sres_record_class(int rclass, char buffer[8])
{
  switch (rclass) {
  case 1: return "IN";
  case 2: return "2?";
  case 3: return "CHAOS";
  case 4: return "HS";
  case 254: return "NONE";
  case 255: return "ANY";

  default:
    sprintf(buffer, "%u?", rclass & 65535);
    return buffer;
  }
}

/** Compare two records. */
int 
sres_record_compare(sres_record_t const *aa, sres_record_t const *bb)
{
  int D;
  sres_common_t const *a = aa->sr_record, *b = bb->sr_record;

  D = a->r_status - b->r_status; if (D) return D;
  D = a->r_class - b->r_class; if (D) return D;
  D = a->r_type - b->r_type; if (D) return D;

  if (a->r_status)
    return 0;
  
  switch (a->r_type) {
  case sres_type_soa: 
    {
      sres_soa_record_t const *A = aa->sr_soa, *B = bb->sr_soa;
      D = A->soa_serial - B->soa_serial; if (D) return D;
      D = strcasecmp(A->soa_mname, B->soa_mname); if (D) return D;
      D = strcasecmp(A->soa_rname, B->soa_rname); if (D) return D;
      D = A->soa_refresh - B->soa_refresh; if (D) return D;
      D = A->soa_retry - B->soa_retry; if (D) return D;
      D = A->soa_expire - B->soa_expire; if (D) return D;
      D = A->soa_minimum - B->soa_minimum; if (D) return D;
      return 0;
    }
  case sres_type_a:
    {
      sres_a_record_t const *A = aa->sr_a, *B = bb->sr_a;
      return memcmp(&A->a_addr, &B->a_addr, sizeof A->a_addr);
    }
  case sres_type_a6:
    {
      sres_a6_record_t const *A = aa->sr_a6, *B = bb->sr_a6;
      D = A->a6_prelen - B->a6_prelen; if (D) return D;
      D = !A->a6_prename - !B->a6_prename; 
      if (D == 0 && A->a6_prename && B->a6_prename)
	D = strcasecmp(A->a6_prename, B->a6_prename); if (D) return D;
      return memcmp(&A->a6_suffix, &B->a6_suffix, sizeof A->a6_suffix);
    }
  case sres_type_aaaa:
    {
      sres_aaaa_record_t const *A = aa->sr_aaaa, *B = bb->sr_aaaa;
      return memcmp(&A->aaaa_addr, &B->aaaa_addr, sizeof A->aaaa_addr);      
    }
  case sres_type_cname:
    {
      sres_cname_record_t const *A = aa->sr_cname, *B = bb->sr_cname;
      return strcmp(A->cn_cname, B->cn_cname);
    }
  case sres_type_ptr:
    {
      sres_ptr_record_t const *A = aa->sr_ptr, *B = bb->sr_ptr;
      return strcmp(A->ptr_domain, B->ptr_domain);
    }
  case sres_type_srv:
    {
      sres_srv_record_t const *A = aa->sr_srv, *B = bb->sr_srv;
      D = A->srv_priority - B->srv_priority; if (D) return D;
      /* Record with larger weight first */
      D = B->srv_weight - A->srv_weight; if (D) return D;
      D = strcmp(A->srv_target, B->srv_target); if (D) return D;
      return A->srv_port - B->srv_port;
    }
  case sres_type_naptr:
    {
      sres_naptr_record_t const *A = aa->sr_naptr, *B = bb->sr_naptr;
      D = A->na_order - B->na_order; if (D) return D;
      D = A->na_prefer - B->na_prefer; if (D) return D;
      D = strcmp(A->na_flags, B->na_flags); if (D) return D;
      D = strcmp(A->na_services, B->na_services); if (D) return D;
      D = strcmp(A->na_regexp, B->na_regexp); if (D) return D;
      return strcmp(A->na_replace, B->na_replace); 
    }
  default:
    return 0;
  }
}

/* ---------------------------------------------------------------------- */
/* Private functions */

/** Destruct */
static 
void 
sres_resolver_destructor(void *arg)
{
  sres_resolver_t *res = arg;

  assert(res);
  sres_cache_unref(res->res_cache); 
  res->res_cache = NULL;

  sres_servers_close(res, res->res_servers);

  if (res->res_config)
    su_home_unref((su_home_t *)res->res_config->c_home);

  if (res->res_updcb)
    res->res_updcb(res->res_async, INVALID_SOCKET, INVALID_SOCKET);
}

/*
 * 3571 is a prime => 
 * we hash successive id values to different parts of hash tables
 */
#define Q_PRIME 3571
#define SRES_QUERY_HASH(q) ((q)->q_hash)

HTABLE_BODIES_WITH(sres_qtable, qt, sres_query_t, SRES_QUERY_HASH,
		   unsigned, size_t);

/** Allocate a query structure */
static
sres_query_t *
sres_query_alloc(sres_resolver_t *res,
		 sres_answer_f *callback,
		 sres_context_t *context,
		 uint16_t type,
		 char const *domain)
{
  sres_query_t *query;
  size_t dlen = strlen(domain);

  if (sres_qtable_is_full(res->res_queries))
    if (sres_qtable_resize(res->res_home, res->res_queries, 0) < 0)
      return NULL;

  query = su_alloc(res->res_home, sizeof(*query) + dlen + 1);

  if (query) {
    memset(query, 0, sizeof *query);
    query->q_res = res;
    query->q_callback = callback;
    query->q_context = context;
    query->q_type = type;
    query->q_class = sres_class_in;
    query->q_timestamp = res->res_now;
    query->q_name = strcpy((char *)(query + 1), domain);

    query->q_id = sres_new_id(res); assert(query->q_id);
    query->q_i_server = res->res_i_server;
    query->q_n_servers = res->res_n_servers;
    query->q_hash = query->q_id * Q_PRIME /* + query->q_i_server */;
    
    sres_qtable_append(res->res_queries, query);

    if (res->res_schedulecb && res->res_queries->qt_used == 1)
      res->res_schedulecb(res->res_async, 2 * SRES_RETRANSMIT_INTERVAL); 
  }

  return query;
}

su_inline
void 
sres_remove_query(sres_resolver_t *res, sres_query_t *q, int all)
{
  int i;

  if (q->q_hash) {
    sres_qtable_remove(res->res_queries, q), q->q_hash = 0;

    if (all)
      for (i = 0; i <= SRES_MAX_SEARCH; i++) {
	if (q->q_subqueries[i] && q->q_subqueries[i]->q_hash) {
	  sres_qtable_remove(res->res_queries, q->q_subqueries[i]);
	  q->q_subqueries[i]->q_hash = 0;
	}
      }
  }
}

/** Remove a query from hash table and free it. */
static
void sres_free_query(sres_resolver_t *res, sres_query_t *q)
{
  int i;

  if (q == NULL)
    return;

  if (q->q_hash)
    sres_qtable_remove(res->res_queries, q), q->q_hash = 0;

  for (i = 0; i <= SRES_MAX_SEARCH; i++) {
    sres_query_t *sq;

    sq = q->q_subqueries[i];
    q->q_subqueries[i] = NULL;
    if (sq)
      sres_free_query(res, sq);
    if (q->q_subanswers[i])
      sres_cache_free_answers(res->res_cache, q->q_subanswers[i]);
    q->q_subanswers[i] = NULL;
  }
 
  su_free(res->res_home, q);
}

static
sres_record_t **
sres_combine_results(sres_resolver_t *res,
		     sres_record_t **search_results[SRES_MAX_SEARCH + 1])
{
  sres_record_t **combined_result;
  int i, j, found;

  /* Combine the results into a single list. */
  for (i = 0, found = 0; i <= SRES_MAX_SEARCH; i++)
    if (search_results[i])
      for (j = 0; search_results[i][j]; j++)
	found++;

  combined_result = su_alloc((su_home_t *)res->res_cache, 
			     (found + 1) * (sizeof combined_result[0]));
  if (combined_result) {
    for (i = 0, found = 0; i <= SRES_MAX_SEARCH; i++)
      if (search_results[i])
	for (j = 0; search_results[i][j]; j++) {
	  combined_result[found++] = search_results[i][j];
	  search_results[i][j] = NULL;
	}

    combined_result[found] = NULL;
    sres_sort_answers(res, combined_result);
  }

  for (i = 0; i <= SRES_MAX_SEARCH; i++)
    if (search_results[i])
      sres_free_answers(res, search_results[i]), search_results[i] = NULL;

  return combined_result;
}

static
int
sres_sockaddr2string(sres_resolver_t *res,
		     char name[],
		     size_t namelen,
		     struct sockaddr const *addr)
{
  name[0] = '\0';

  if (addr->sa_family == AF_INET) {
    struct sockaddr_in const *sin = (struct sockaddr_in *)addr;
    uint8_t const *in_addr = (uint8_t*)&sin->sin_addr;
    return snprintf(name, namelen, "%u.%u.%u.%u.in-addr.arpa.",
		    in_addr[3], in_addr[2], in_addr[1], in_addr[0]);
  }
#if HAVE_SIN6
  else if (addr->sa_family == AF_INET6) {
    struct sockaddr_in6 const *sin6 = (struct sockaddr_in6 *)addr;
    size_t addrsize = sizeof(sin6->sin6_addr.s6_addr);
    char *postfix;
    size_t required;
    size_t i;

    if (res->res_config->c_opt.ip6int)
      postfix = "ip6.int.";
    else
      postfix = "ip6.arpa.";

    required = addrsize * 4 + strlen(postfix);

    if (namelen <= required)
      return (int)required;

    for (i = 0; i < addrsize; i++) {
      uint8_t byte = sin6->sin6_addr.s6_addr[addrsize - i - 1];
      uint8_t hex;

      hex = byte & 0xf;
      name[4 * i] = hex > 9 ? hex + 'a' - 10 : hex + '0';
      name[4 * i + 1] = '.';
      hex = (byte >> 4) & 0xf;
      name[4 * i + 2] = hex > 9 ? hex + 'a' - 10 : hex + '0';
      name[4 * i + 3] = '.';
    }
    
    strcpy(name + 4 * i, postfix);

    return (int)required;
  }
#endif /* HAVE_SIN6 */
  else {
    su_seterrno(EAFNOSUPPORT);
    SU_DEBUG_3(("%s: %s\n", "sres_sockaddr2string", 
                su_strerror(EAFNOSUPPORT)));
    return 0;
  }
}

/** Make a domain name a top level domain name.
 *
 * The function sres_toplevel() returns a copies string @a domain and 
 * terminates it with a dot if it is not already terminated. 
 */
static
char const *
sres_toplevel(char buf[], size_t blen, char const *domain)
{
  size_t len;
  int already;

  if (!domain)
    return su_seterrno(EFAULT), (void *)NULL;

  len = strlen(domain);

  if (len >= blen)
    return su_seterrno(ENAMETOOLONG), (void *)NULL;

  already = len > 0 && domain[len - 1] == '.';

  if (already)
    return domain;

  if (len + 1 >= blen)
    return su_seterrno(ENAMETOOLONG), (void *)NULL;

  strcpy(buf, domain);
  buf[len] = '.'; buf[len + 1] = '\0';

  return buf;
}

/* ---------------------------------------------------------------------- */

static int sres_update_config(sres_resolver_t *res, int always, time_t now);
static int sres_parse_config(sres_config_t *, FILE *);
static int sres_parse_options(sres_config_t *c, char const *value);
static int sres_parse_nameserver(sres_config_t *c, char const *server);
static time_t sres_config_timestamp(sres_config_t const *c);

/** Update configuration
 *
 * @retval 0 when successful
 * @retval -1 upon an error
 */
int sres_resolver_update(sres_resolver_t *res, int always)
{
  sres_server_t **servers, **old_servers;
  int updated;

  updated = sres_update_config(res, always, time(&res->res_now));
  if (updated < 0)
    return -1;

  if (!res->res_servers || always || updated) {
    servers = sres_servers_new(res, res->res_config);
    old_servers = res->res_servers;

    res->res_i_server = 0;
    res->res_n_servers = sres_servers_count(servers);
    res->res_servers = servers;

    sres_servers_close(res, old_servers);
    su_free(res->res_home, old_servers);

    if (!servers)
      return -1;
  }

  return 0;
}

/** Update config file.
 *
 * @retval 1 if DNS server list is different from old one.
 * @retval 0 when otherwise successful
 * @retval -1 upon an error
 */
static 
int sres_update_config(sres_resolver_t *res, int always, time_t now)
{
  sres_config_t *c = NULL;
  sres_config_t const *previous;
  int retval;

  previous = res->res_config;

  if (!always && previous && now < res->res_checked)
    return 0;
  /* Try avoid checking for changes too often. */
  res->res_checked = now + SRES_UPDATE_INTERVAL_SECS; 
  
  if (!always && previous && 
      sres_config_timestamp(previous) == previous->c_modified)
    return 0;

  c = sres_parse_resolv_conf(res, res->res_options);
  if (!c)
    return -1;

  res->res_config = c;

  retval = sres_config_changed_servers(c, previous);

  su_home_unref((su_home_t *)previous->c_home);

  return retval;
}

#if HAVE_WIN32

/** Number of octets to read from a registry key at a time */
#define QUERY_DATALEN         1024
#define MAX_DATALEN           65535

/**
 * Uses IP Helper IP to get DNS servers list.
 */
static int sres_parse_win32_ip(sres_config_t *c)
{
  int ret = -1;

#if HAVE_IPHLPAPI_H
  DWORD dw;
  su_home_t *home = c->c_home;
  ULONG size = sizeof(FIXED_INFO);

  do {
    FIXED_INFO *info = (FIXED_INFO *)su_alloc(home, size);
    dw = GetNetworkParams(info, &size);
    if (dw == ERROR_SUCCESS) {
      IP_ADDR_STRING* addr = &info->DnsServerList;
      for (; addr; addr = addr->Next) {
       SU_DEBUG_3(("Adding nameserver: %s\n", addr->IpAddress.String));
       sres_parse_nameserver(c, addr->IpAddress.String);
      }
      ret = 0;
    }
    su_free(home, info);
  } while (dw == ERROR_BUFFER_OVERFLOW);
#endif

  return ret;
}

/**
 * Parses name servers listed in registry key 'key+lpValueName'. The
 * key is expected to contain a whitespace separate list of
 * name server IP addresses.
 *
 * @return number of server addresses added
 */ 
static int sres_parse_win32_reg_parse_dnsserver(sres_config_t *c, HKEY key, LPCTSTR lpValueName)
{
  su_home_t *home = c->c_home;
  su_strlst_t *reg_dns_list;
  BYTE *name_servers = su_alloc(home, QUERY_DATALEN);
  DWORD name_servers_length = QUERY_DATALEN;
  int ret, servers_added = 0;

  /* get name servers and ... */
  while((ret = RegQueryValueEx(key, 
			       lpValueName, 
			       NULL, NULL, 
			       name_servers, 
			       &name_servers_length)) == ERROR_MORE_DATA) {
    name_servers_length += QUERY_DATALEN;

    /* sanity check, upper limit for memallocs */
    if (name_servers_length > MAX_DATALEN) break;

    name_servers = su_realloc(home, name_servers, name_servers_length);
  }

  /* if reading the key was succesful, continue */
  if (ret == ERROR_SUCCESS) {
    if (name_servers[0]){
      int i;

      /* add to list */
      reg_dns_list = su_strlst_split(home, (char *)name_servers, " ");
	    
      for(i = 0 ; i < su_strlst_len(reg_dns_list); i++) {
	const char *item = su_strlst_item(reg_dns_list, i);
	SU_DEBUG_3(("Adding nameserver: %s (key=%s)\n", item, (char*)lpValueName));
	sres_parse_nameserver(c, item);
	++servers_added;
      }
	    
      su_strlst_destroy(reg_dns_list);

    }
  }

  su_free(home, name_servers);

  return servers_added;
}

/**
 * Discover system nameservers from Windows registry.
 *
 * Refs:
 *  - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/regqueryvalueex.asp
 *  - http://support.microsoft.com/default.aspx?scid=kb;en-us;120642
 *  - http://support.microsoft.com/kb/314053/EN-US/
 *  - IP Helper API (possibly better way than current registry-based impl.)
 *    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/iphlp/iphlp/ip_helper_start_page.asp
 */
static int sres_parse_win32_reg(sres_config_t *c)
{
  int ret = -1;

#define MAX_KEY_LEN           255
#define MAX_VALUE_NAME_LEN    16383

  su_home_t *home = c->c_home;
  HKEY key_handle;  
#if 0
  HKEY interface_key_handle;  
  FILETIME ftime;
  int index, i;
#endif
  int found = 0;
  char *interface_guid = su_alloc(home, MAX_VALUE_NAME_LEN);

#if 0
#if __MINGW32__
  DWORD guid_size = QUERY_DATALEN;
#else
  int guid_size = MAX_VALUE_NAME_LEN;
#endif

  /* step: find interface specific nameservers 
   * - this is currently disabled 2006/Jun (the current check might insert
   *   multiple unnecessary nameservers to the search list) 
   */
  /* open the 'Interfaces' registry Key */
  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
		   "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces", 
		   0, KEY_READ, &key_handle)) {
    SU_DEBUG_2(("RegOpenKeyEx failed\n"));
  } else {
    index = 0;
    /* for each interface listed ... */
    while (RegEnumKeyEx(key_handle, index,
			interface_guid, &guid_size,
			NULL,NULL,0,&ftime) == ERROR_SUCCESS){
      if (RegOpenKeyEx(key_handle, interface_guid,
		       0, KEY_READ, 
		       &interface_key_handle) == ERROR_SUCCESS) {

	/* note: 'NameServer' is preferred over 'DhcpNameServer' */
	found += sres_parse_win32_reg_parse_dnsserver(c, interface_key_handle, "NameServer");
	if (found == 0) 
	  found += sres_parse_win32_reg_parse_dnsserver(c, interface_key_handle, "DhcpNameServer");

	RegCloseKey(interface_key_handle);
      } else{
	SU_DEBUG_2(("interface RegOpenKeyEx failed\n"));
      }
      index++;
      guid_size = 64;
    }
    RegCloseKey(key_handle);
  }
#endif /* #if 0: interface-specific nameservers */

  /* step: if no interface-specific nameservers are found, 
   *       check for system-wide nameservers */
  if (found == 0) {
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
		     "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters", 
		     0, KEY_READ, &key_handle)) {
      SU_DEBUG_2(("RegOpenKeyEx failed (2)\n"));
    } else {
      found += sres_parse_win32_reg_parse_dnsserver(c, key_handle, "NameServer");
      if (found == 0) 
	found += sres_parse_win32_reg_parse_dnsserver(c, key_handle, "DhcpNameServer");
      RegCloseKey(key_handle);
    }
  }

  SU_DEBUG_3(("Total of %d name servers found from win32 registry.\n", found));

  /* return success if servers found */
  if (found) ret = 0;

  su_free(home, interface_guid);

  return ret;
}

#endif /* HAVE_WIN32 */

/** Parse /etc/resolv.conf file.
 *
 * @retval #sres_config_t structure when successful 
 * @retval NULL upon an error
 *
 * @todo The resolv.conf directives @b sortlist and most of the options 
 *       are currently ignored.
 */
static 
sres_config_t *sres_parse_resolv_conf(sres_resolver_t *res,
				      char const **options)
{
  sres_config_t *c = su_home_new(sizeof *c);

  if (c) {
    FILE *f;
    int i;
    
    f = fopen(c->c_filename = res->res_cnffile, "r");

    sres_parse_config(c, f);

    if (f)
      fclose(f);

#if HAVE_WIN32    
    /* note: no 127.0.0.1 on win32 systems */
    /* on win32, query the registry for nameservers */
    if (sres_parse_win32_ip(c) == 0 || sres_parse_win32_reg(c) == 0)
      /* success */;
    else
      /* now what? */;
#else
    /* Use local nameserver by default */
    if (c->c_nameservers[0] == NULL)
      sres_parse_nameserver(c, "127.0.0.1");
#endif
      
    for (i = 0; c->c_nameservers[i] && i < SRES_MAX_NAMESERVERS; i++) {
      struct sockaddr_in *sin = (void *)c->c_nameservers[i]->ns_addr;
      sin->sin_port = htons(c->c_port);
    }

    sres_parse_options(c, getenv("RES_OPTIONS"));
    
    if (options)
      for (i = 0; options[i]; i++)
	sres_parse_options(c, options[i]);

    sres_parse_options(c, getenv("SRES_OPTIONS"));

    su_home_threadsafe(c->c_home);
  }

  return c;
}

/** Parse config file. 
 *
 * @return Number of search domains, if successful.
 * @retval -1 upon an error (never happens).
 */
static
int sres_parse_config(sres_config_t *c, FILE *f)
{
  su_home_t *home = c->c_home;
  int line;
  char const *localdomain;
  char *search = NULL, *domain = NULL;
  char buf[1025];
  int i = 0;

  localdomain = getenv("LOCALDOMAIN");

  /* Default values */
  c->c_opt.ndots = 1;
  c->c_opt.check_names = 1;
  c->c_opt.timeout = SRES_RETRY_INTERVAL;
  c->c_opt.attempts = SRES_MAX_RETRY_COUNT;
  c->c_port = 53;

  if (f != NULL) {  
    for (line = 1; fgets(buf, sizeof(buf), f); line++) {
      size_t len;
      char *value, *b;

      /* Skip whitespace at the beginning ...*/
      b = buf + strspn(buf, " \t");

      /* ... and at the end of line */
      for (len = strlen(b); len > 0 && strchr(" \t\r\n", b[len - 1]); len--)
	;

      if (len == 0 || b[0] == '#') 	/* Empty line or comment */
	continue;

      b[len] = '\0';

      len = strcspn(b, " \t");
      value = b + len; value += strspn(value, " \t");

#define MATCH(token) (len == strlen(token) && strncasecmp(token, b, len) == 0)

      if (MATCH("nameserver")) {
	if (sres_parse_nameserver(c, value) < 0)
	  return -1;
      }
      else if (MATCH("domain")) {
	if (localdomain)	/* LOCALDOMAIN overrides */
	  continue;
	if (search)
	  su_free(home, search), search = NULL;
	if (domain)
	  su_free(home, domain), domain = NULL;
	domain = su_strdup(home, value);
	if (!domain)
	  return -1;
      }
      else if (MATCH("search")) {
	if (localdomain)	/* LOCALDOMAIN overrides */
	  continue;
	if (search) su_free(home, search), search = NULL;
	if (domain) su_free(home, domain), domain = NULL;
	search = su_strdup(home, value);
	if (!search)
	  return -1;
      }
      else if (MATCH("port")) {
	unsigned long port = strtoul(value, NULL, 10);
	if (port < 65536)
	  c->c_port = port;
      }
      else if (MATCH("options")) {
	sres_parse_options(c, value);
      }
    }
  }

  if (f)
    c->c_modified = sres_config_timestamp(c);

  if (localdomain)
    c->c_search[0] = localdomain;
  else if (domain)
    c->c_search[0] = domain;
  else if (search) {
    for (i = 0; search[0] && i < SRES_MAX_SEARCH; i++) {
      c->c_search[i] = search;
      search += strcspn(search, " \t");
      if (*search) {
	*search++ = '\0';
	search += strspn(search, " \t");
      }
    }
  }

  return i;
}

#if DOXYGEN_ONLY
/**@ingroup sresolv_env
 *
 * Environment variable containing options for Sofia resolver. The options
 * recognized by Sofia resolver are as follows:
 * - @b debug           turn on debugging (no effect)
 * - @b ndots:<i>n</i>  when searching, try first to query name as absolute
 *                      domain if it contains at least <i>n</i> dots
 * - @b timeout:<i>secs</i> timeout in seconds
 * - @b attempts:<i>n</i> fail after <i>n</i> retries
 * - @b rotate          use round robin selection of nameservers
 * - @b no-check-names  do not check names for invalid characters
 * - @b inet6           (no effect) 
 * - @b ip6-dotint      IPv6 addresses are resolved using suffix ".ip6.int"
 *                      instead of the standard ".ip6.arpa" suffix
 * - @b ip6-bytestring  (no effect)
 * The following option is a Sofia-specific extension:
 * - @b no-edns0        do not try to use EDNS0 extension (@RFC2671)
 *
 * The same options can be listed in @b options directive in resolv.conf, or
 * in #RES_OPTIONS environment variable. Note that options given in
 * #SRES_OPTIONS override those specified in #RES_OPTIONS which in turn
 * override options specified in the @b options directive of resolve.conf.
 *
 * The meaning of an option can be reversed with prefix "no-".
 *
 * @sa Manual page for resolv.conf, #RES_OPTIONS.
 */
extern SRES_OPTIONS;

/**@ingroup sresolv_env
 *
 * Environment variable containing resolver options. This environment
 * variable is also used by standard BIND resolver.
 *
 * @sa Manual page for resolv.conf, #SRES_OPTIONS.
 */
extern RES_OPTIONS;

/**@ingroup sresolv_env
 *
 * Environment variable containing search domain. This environment
 * variable is also used by standard BIND resolver.
 *
 * @sa Manual page for resolv.conf, #RES_OPTIONS, #SRES_OPTIONS.
 */
extern LOCALDOMAIN;
#endif

/* Parse options line or #SRES_OPTIONS or #RES_OPTIONS environment variable. */
static int 
sres_parse_options(sres_config_t *c, char const *value)
{
  if (!value)
    return -1;

  while (value[0]) {
    char const *b;
    size_t len, extra = 0;
    unsigned long n = 0;

    b = value; len = strcspn(value, " \t:");
    value += len;

    if (value[0] == ':') {
      len++;
      n = strtoul(++value, NULL, 10);
      value += extra = strcspn(value, " \t");
    }

    if (*value)
      value += strspn(value, " \t");

    if (n > 65536) {
      SU_DEBUG_3(("sres: %s: invalid %*.0s\n", c->c_filename,
		  (int)(len + extra), b));
      continue;
    }

    /* Documented by BIND9 resolv.conf */
    if (MATCH("no-debug")) c->c_opt.debug = 0;
    else if (MATCH("debug")) c->c_opt.debug = 1;
    else if (MATCH("ndots:")) c->c_opt.ndots = n;
    else if (MATCH("timeout:")) c->c_opt.timeout = n;
    else if (MATCH("attempts:")) c->c_opt.attempts = n;
    else if (MATCH("no-rotate")) c->c_opt.rotate = 0;
    else if (MATCH("rotate")) c->c_opt.rotate = 1;
    else if (MATCH("no-check-names")) c->c_opt.check_names = 0;
    else if (MATCH("check-names")) c->c_opt.check_names = 1;
    else if (MATCH("no-inet6")) c->c_opt.ip6int = 0;
    else if (MATCH("inet6")) c->c_opt.inet6 = 1;
    else if (MATCH("no-ip6-dotint")) c->c_opt.ip6int = 0;
    else if (MATCH("ip6-dotint")) c->c_opt.ip6int = 1;
    else if (MATCH("no-ip6-bytestring")) c->c_opt.ip6bytestring = 0;
    else if (MATCH("ip6-bytestring")) c->c_opt.ip6bytestring = 1;
    /* Sofia-specific extensions: */
    else if (MATCH("no-edns0")) c->c_opt.edns = edns_not_supported;
    else if (MATCH("edns0")) c->c_opt.edns = edns0_configured;
    else {
      SU_DEBUG_3(("sres: %s: unknown option %*.0s\n",
		  c->c_filename, (int)(len + extra), b));
    }
  }

  return 0;
}

static
int sres_parse_nameserver(sres_config_t *c, char const *server)
{
  sres_nameserver_t *ns;
  struct sockaddr *sa;
  int err, i;

  for (i = 0; i < SRES_MAX_NAMESERVERS; i++)
    if (c->c_nameservers[i] == NULL)
      break;

  if (i >= SRES_MAX_NAMESERVERS)
    return 0 /* Silently discard extra nameservers */;

  ns = su_zalloc(c->c_home, (sizeof *ns) + strlen(server) + 1);
  if (!ns)
    return -1;

  sa = (void *)ns->ns_addr;

#if HAVE_SIN6
  if (strchr(server, ':')) {
    struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
    memset(sa, 0, ns->ns_addrlen = sizeof *sin6);
    err = inet_pton(sa->sa_family = AF_INET6, server, &sin6->sin6_addr);
  } 
  else 
#endif
    {
      struct sockaddr_in *sin = (struct sockaddr_in *)sa;
      memset(sa, 0, ns->ns_addrlen = sizeof *sin);
      err = inet_pton(sa->sa_family = AF_INET, server, &sin->sin_addr);
    }

  if (err <= 0) {
    SU_DEBUG_3(("sres: nameserver %s: invalid address\n", server));
    su_free(c->c_home, ns);
    return 0;
  }

#if HAVE_SA_LEN
  sa->sa_len = ns->ns_addrlen;
#endif

  c->c_nameservers[i] = ns;

  return 1;
}

/** Get current timestamp of resolv.conf file */
static
time_t sres_config_timestamp(sres_config_t const *c)
{
#ifndef HAVE_WIN32
  struct stat st;

  if (stat(c->c_filename, &st) == 0)
    return st.st_mtime;

  /** @return If the resolv.conf file does not exists, return old timestamp. */
  return c->c_modified;
#else
  /** On WIN32, return always different timestamp */
  return c->c_modified + SRES_UPDATE_INTERVAL_SECS;
#endif
}


/* ---------------------------------------------------------------------- */

/** Check if the new configuration has different servers than the old */
static 
int sres_config_changed_servers(sres_config_t const *new_c, 
				sres_config_t const *old_c)
{
  int i;
  sres_nameserver_t const *new_ns, *old_ns;

  if (old_c == NULL)
    return 1;

  for (i = 0; i < SRES_MAX_NAMESERVERS; i++) {
    new_ns = new_c->c_nameservers[i];
    old_ns = old_c->c_nameservers[i];

    if (!new_ns != !old_ns)
      return 1;
    if (!new_ns)
      return 0;
    if (new_ns->ns_addrlen != old_ns->ns_addrlen)
      return 1;
    if (memcmp(new_ns->ns_addr, old_ns->ns_addr, new_ns->ns_addrlen))
      return 1;
  }

  return 0;
}

/** Allocate new servers structure */
static
sres_server_t **sres_servers_new(sres_resolver_t *res,
				 sres_config_t const *c)
{
  sres_server_t **servers, *dns;
  sres_nameserver_t *ns;
  int N, i;
  size_t size;

  for (N = 0; c->c_nameservers[N] && N < SRES_MAX_NAMESERVERS; N++)
    ;

  size = (N + 1) * (sizeof *servers) + N * (sizeof **servers);

  servers = su_zalloc(res->res_home, size); if (!servers) return servers;
  dns = (void *)(servers + N + 1);
  for (i = 0; i < N; i++) {
    dns->dns_socket = INVALID_SOCKET;
    ns = c->c_nameservers[i];
    memcpy(dns->dns_addr, ns->ns_addr, dns->dns_addrlen = ns->ns_addrlen);
    inet_ntop(dns->dns_addr->ss_family, SS_ADDR(dns->dns_addr), 
	      dns->dns_name, sizeof dns->dns_name);
    dns->dns_edns = c->c_opt.edns;
    servers[i] = dns++;
  }

  return servers;
}

static
void sres_servers_close(sres_resolver_t *res,
			sres_server_t **servers)
{
  int i;

  if (res == NULL || servers == NULL)
    return;

  for (i = 0; i < SRES_MAX_NAMESERVERS; i++) {
    if (!servers[i])
      break;

    if (servers[i]->dns_socket != INVALID_SOCKET) {
      if (res->res_updcb)
	res->res_updcb(res->res_async, INVALID_SOCKET, servers[i]->dns_socket);
      sres_close(servers[i]->dns_socket);
    }
  }
}

static
int sres_servers_count(sres_server_t *const *servers)
{
  int i;

  if (!servers)
    return 0;

  for (i = 0; i < SRES_MAX_NAMESERVERS; i++) {
    if (!servers[i])
      break;
  }  

  return i;
}

static
sres_socket_t sres_server_socket(sres_resolver_t *res, sres_server_t *dns)
{
  int family = dns->dns_addr->ss_family;
  sres_socket_t s;

  if (dns->dns_socket != INVALID_SOCKET)
    return dns->dns_socket;

  s = socket(family, SOCK_DGRAM, IPPROTO_UDP);
  if (s == -1) {
    SU_DEBUG_1(("%s: %s: %s\n", "sres_server_socket", "socket",
		su_strerror(su_errno())));
    return s;
  }

#if HAVE_IP_RECVERR
  if (family == AF_INET || family == AF_INET6) {
    int const one = 1;
    if (setsockopt(s, SOL_IP, IP_RECVERR, &one, sizeof(one)) < 0) {
      if (family == AF_INET)
	SU_DEBUG_3(("setsockopt(IPVRECVERR): %s\n", su_strerror(su_errno())));
    }
  }
#endif
#if HAVE_IPV6_RECVERR
  if (family == AF_INET6) {
    int const one = 1;
    if (setsockopt(s, SOL_IPV6, IPV6_RECVERR, &one, sizeof(one)) < 0)
      SU_DEBUG_3(("setsockopt(IPV6_RECVERR): %s\n", su_strerror(su_errno())));
  }
#endif

  if (connect(s, (void *)dns->dns_addr, dns->dns_addrlen) < 0) {
    char ipaddr[64];
    char const *lb = "", *rb = "";

    if (family == AF_INET) {
      void *addr = &((struct sockaddr_in *)dns->dns_addr)->sin_addr;
      inet_ntop(family, addr, ipaddr, sizeof ipaddr);
    }
#if HAVE_SIN6
    else if (family == AF_INET6) {
      void *addr = &((struct sockaddr_in6 *)dns->dns_addr)->sin6_addr;
      inet_ntop(family, addr, ipaddr, sizeof ipaddr);
      lb = "[", rb = "]";
    }
#endif
    else
      snprintf(ipaddr, sizeof ipaddr, "<af=%u>", family);

    SU_DEBUG_1(("%s: %s: %s: %s%s%s:%u\n", "sres_server_socket", "connect",
		su_strerror(su_errno()), lb, ipaddr, rb,
		ntohs(((struct sockaddr_in *)dns->dns_addr)->sin_port)));
    sres_close(s);
    return INVALID_SOCKET;
  }
  
  if (res->res_updcb) {
    if (res->res_updcb(res->res_async, s, INVALID_SOCKET) < 0) {
      SU_DEBUG_1(("%s: %s: %s\n", "sres_server_socket", "update callback",
		  su_strerror(su_errno())));
      sres_close(s);
      return INVALID_SOCKET;
    }
  }

  dns->dns_socket = s;
  
  return s;
}

/* ---------------------------------------------------------------------- */

/** Send a query packet */
static 
int 
sres_send_dns_query(sres_resolver_t *res, 
		    sres_query_t *q)
{                        
  sres_message_t m[1];
  uint8_t i, i0, N = res->res_n_servers;
  sres_socket_t s;
  int error = 0;
  ssize_t size, no_edns_size, edns_size;
  uint16_t id = q->q_id;
  uint16_t type = q->q_type;
  char const *domain = q->q_name;
  time_t now = res->res_now;
  sres_server_t **servers = res->res_servers, *dns;
  char b[8];

  if (now == 0) time(&now);

  SU_DEBUG_9(("sres_send_dns_query(%p, %p) called\n", (void *)res, (void *)q));

  if (domain == NULL)
    return -1;
  if (servers == NULL)
    return -1;
  if (N == 0) 
    return -1;

  memset(m, 0, offsetof(sres_message_t, m_data[sizeof m->m_packet.mp_header]));

  /* Create a DNS message */
  size = sizeof(m->m_packet.mp_header);
  m->m_size = (uint16_t)sizeof(m->m_data);
  m->m_offset = (uint16_t)size;
  
  m->m_id = id;
  m->m_flags = htons(SRES_HDR_QUERY | SRES_HDR_RD);
  
  /* Query record */
  m->m_qdcount = htons(1);
  m_put_domain(m, domain, 0, NULL);
  m_put_uint16(m, type);
  m_put_uint16(m, sres_class_in);
  
  no_edns_size = m->m_offset;

  /* EDNS0 record (optional) */
  m_put_domain(m, ".", 0, NULL);
  m_put_uint16(m, sres_type_opt);
  m_put_uint16(m, sizeof(m->m_packet)); /* Class: our UDP payload size */
  m_put_uint32(m, 0);		/* TTL: extended RCODE & flags */
  m_put_uint16(m, 0);
  
  edns_size = m->m_offset;

  if (m->m_error) {
    SU_DEBUG_3(("%s(): encoding: %s\n", "sres_send_dns_query", m->m_error));
    su_seterrno(EIO);
    return -1;
  }

  i0 = q->q_i_server;
  if (i0 > N) i0 = 0; /* Number of DNS servers reduced */
  dns = servers[i = i0];

  if (res->res_config->c_opt.rotate || dns->dns_error || dns->dns_icmp)
    dns = sres_next_server(res, &q->q_i_server, 1), i = q->q_i_server;

  for (; dns; dns = sres_next_server(res, &i, 1)) {
    /* If server supports EDNS, include EDNS0 record */
    q->q_edns = dns->dns_edns;
    /* 0 (no EDNS) or 1 (EDNS supported) additional data records */
    m->m_arcount = htons(q->q_edns != 0); 
    /* Size with or without EDNS record */
    size = q->q_edns ? edns_size : no_edns_size; 

    s = sres_server_socket(res, dns);

    if (s == INVALID_SOCKET) {
      dns->dns_icmp = now;
      dns->dns_error = SRES_TIME_MAX;
      continue;
    }

    /* Send the DNS message via the UDP socket */
    if (sres_send(s, m->m_data, size, 0) == size)
      break;
    error = su_errno();

    dns->dns_icmp = now;
    dns->dns_error = now;	/* Mark as a bad destination */
  }

  if (!dns) {
    /* All servers have reported errors */
    SU_DEBUG_5(("%s(): sendto: %s\n", "sres_send_dns_query",
		su_strerror(error)));
    return su_seterrno(error);
  }

  q->q_i_server = i;

  SU_DEBUG_5(("%s(%p, %p) id=%u %s %s (to [%s]:%u)\n", 
	      "sres_send_dns_query",
	      (void *)res, (void *)q, id, sres_record_type(type, b), domain, 
	      dns->dns_name, 
	      htons(((struct sockaddr_in *)dns->dns_addr)->sin_port)));

  return 0;
}

/** Retry time after ICMP error */
#define DNS_ICMP_TIMEOUT 60

/** Retry time after immediate error */
#define DNS_ERROR_TIMEOUT 10

/** Select next server.
 *
 * @param res resolver object
 * @param[in,out] in_out_i index to DNS server table
 * @param always return always a server
 */
static
sres_server_t *sres_next_server(sres_resolver_t *res, 
				uint8_t *in_out_i,
				int always)
{
  int i, j, N;
  sres_server_t *dns, **servers;
  time_t now = res->res_now;

  N = res->res_n_servers;
  servers = res->res_servers;
  i = *in_out_i;

  assert(res->res_servers && res->res_servers[i]);

  for (j=0; j < N; j++) {
    dns = servers[j]; if (!dns) continue;
    if (dns->dns_icmp + DNS_ICMP_TIMEOUT < now)
      dns->dns_icmp = 0;
    if (dns->dns_error + DNS_ERROR_TIMEOUT < now &&
	dns->dns_error != SRES_TIME_MAX)
      dns->dns_error = 0;
  }
  
  /* Retry using another server? */
  for (j = (i + 1) % N; (j != i); j = (j + 1) % N) {
    dns = servers[j]; if (!dns) continue;
    if (dns->dns_icmp == 0) {
      return *in_out_i = j, dns;
    }
  }

  for (j = (i + 1) % N; (j != i); j = (j + 1) % N) {
    dns = servers[j]; if (!dns) continue;
    if (dns->dns_error == 0) {
      return *in_out_i = j, dns;
    }
  }

  if (!always)
    return NULL;

  dns = servers[i]; 
  if (dns && dns->dns_error < now && dns->dns_error != SRES_TIME_MAX)
    return dns;

  for (j = (i + 1) % N; j != i; j = (j + 1) % N) {
    dns = servers[j]; if (!dns) continue;
    if (dns->dns_error < now && dns->dns_error != SRES_TIME_MAX)
      return *in_out_i = j, dns;
  }
  
  return NULL;
}

/**
 * Callback function for subqueries
 */
static
void sres_answer_subquery(sres_context_t *context, 
			  sres_query_t *query,
			  sres_record_t **answers)
{
  sres_resolver_t *res;
  sres_query_t *top = (sres_query_t *)context;
  int i;
  assert(top); assert(top->q_n_subs > 0); assert(query);

  res = query->q_res;

  for (i = 0; i <= SRES_MAX_SEARCH; i++) {
    if (top->q_subqueries[i] == query)
      break;
  }
  assert(i <= SRES_MAX_SEARCH);
  if (i > SRES_MAX_SEARCH || top->q_n_subs == 0) {
    sres_free_answers(res, answers);
    return;
  }

  if (answers) {
    int j, k;
    for (j = 0, k = 0; answers[j]; j++) {
      if (answers[j]->sr_status)
	sres_free_answer(query->q_res, answers[j]);
      else
	answers[k++] = answers[j];
    }
    answers[k] = NULL;
    if (!answers[0])
      sres_free_answers(query->q_res, answers), answers = NULL;
  }

  top->q_subqueries[i] = NULL;
  top->q_subanswers[i] = answers;
  top->q_n_subs--;

  if (answers && top->q_callback) {
    sres_answer_f *callback = top->q_callback;

    top->q_callback = NULL;
    sres_remove_query(top->q_res, top, 1);
    callback(top->q_context, top, answers);
  }
  else if (top->q_n_subs == 0 && top->q_id == 0) {
    sres_query_report_error(top, NULL);
  };
}

/** Report sres error */
static void
sres_query_report_error(sres_query_t *q,
			sres_record_t **answers)
{
  int i;

  if (q->q_callback) {
    for (i = 0; i <= SRES_MAX_SEARCH; i++) {
      if (q->q_subqueries[i])	/* a pending query... */
	return;

      if (q->q_subanswers[i]) {
	answers = q->q_subanswers[i];
	q->q_subanswers[i] = NULL;
	break;
      }
    }

    SU_DEBUG_5(("sres(q=%p): reporting errors for %u %s\n",
		(void *)q, q->q_type, q->q_name));
 
    sres_remove_query(q->q_res, q, 1);
    (q->q_callback)(q->q_context, q, answers);
  }

  sres_free_query(q->q_res, q);
}

/** Resolver timer function.
 *
 * The function sresolver_timer() should be called in regular intervals. We
 * recommend calling it in 500 ms intervals.
 *
 * @param res pointer to resolver object
 * @param dummy argument for compatibility 
 */
void sres_resolver_timer(sres_resolver_t *res, int dummy)
{
  size_t i;
  sres_query_t *q;
  time_t now, retry_time;

  if (res == NULL)
    return;

  now = time(&res->res_now);

  if (res->res_queries->qt_used) {
    SU_DEBUG_9(("sres_resolver_timer() called at %lu\n", (long) now));

    /** Every time it is called it goes through all query structures, and
     * retransmits all the query messages, which have not been answered yet.
     */
    for (i = 0; i < res->res_queries->qt_size; i++) {
      q = res->res_queries->qt_table[i];
      
      if (!q)
	continue;
      
      /* Exponential backoff */
      retry_time = q->q_timestamp + ((time_t)1 << q->q_retry_count);
      
      if (now < retry_time)
	continue;
      
      sres_resend_dns_query(res, q, 1);

      if (q != res->res_queries->qt_table[i])
	i--;
    }

    if (res->res_schedulecb && res->res_queries->qt_used)
      res->res_schedulecb(res->res_async, SRES_RETRANSMIT_INTERVAL); 
  }

  sres_cache_clean(res->res_cache, res->res_now);
}

/** Resend DNS query, report error if cannot resend any more.
 * 
 * @param res  resolver object
 * @param q    query object
 * @param timeout  true if resent because of timeout
 *                (false if because icmp error report)
 */
static void
sres_resend_dns_query(sres_resolver_t *res, sres_query_t *q, int timeout)
{
  uint8_t i, N;
  sres_server_t *dns;
  
  SU_DEBUG_9(("sres_resend_dns_query(%p, %p, %s) called\n",
	      (void *)res, (void *)q, timeout ? "timeout" : "error"));
  
  N = res->res_n_servers;

  if (N > 0 && q->q_retry_count < SRES_MAX_RETRY_COUNT) {
    i = q->q_i_server;
    dns = sres_next_server(res, &i, timeout);

    if (dns) {
      res->res_i_server = q->q_i_server = i;

      if (q->q_retry_count > res->res_n_servers + 1 &&
	  dns->dns_edns == edns_not_tried)
	q->q_edns = edns_not_supported;
      
      sres_send_dns_query(res, q);

      if (timeout)
	q->q_retry_count++;
      
      return;
    }
  }

  /* report timeout/network error */
  q->q_id = 0;

  if (q->q_n_subs)
    return;			/* let subqueries also timeout */

  sres_query_report_error(q, NULL);
}


/** Get a server by socket */
static 
sres_server_t *
sres_server_by_socket(sres_resolver_t const *res, sres_socket_t socket)
{
  int i;

  if (socket == -1)
    return NULL;

  for (i = 0; i < res->res_n_servers; i++) {
    if (socket == res->res_servers[i]->dns_socket)
      return res->res_servers[i];
  }

  return NULL;
}

static
void
sres_canonize_sockaddr(struct sockaddr_storage *from, socklen_t *fromlen)
{
#if HAVE_SIN6
  struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)from;

  size_t sin6_addrsize =
    offsetof(struct sockaddr_in6, sin6_addr) +
    (sizeof sin6->sin6_addr);

  if (from->ss_family == AF_INET6) {
    struct in6_addr const *ip6 = &sin6->sin6_addr;
  
    if (IN6_IS_ADDR_V4MAPPED(ip6) || IN6_IS_ADDR_V4COMPAT(ip6)) {
      /* Convert to a IPv4 address */
      struct sockaddr_in *sin = (struct sockaddr_in *)from;
      memcpy(&sin->sin_addr, ip6->s6_addr + 12, sizeof sin->sin_addr);
      sin->sin_family = AF_INET;
      *fromlen = sizeof (*sin);
#if HAVE_SA_LEN
      sin->sin_len = sizeof (*sin);
#endif
    }
    else if (sin6_addrsize < *fromlen) {
      /* Zero extra sin6 members like sin6_flowinfo or sin6_scope_id */
      memset((char *)from + sin6_addrsize, 0, *fromlen - sin6_addrsize);
    }
  }
#endif

  if (from->ss_family == AF_INET) {
    struct sockaddr_in *sin = (struct sockaddr_in *)from;
    memset(sin->sin_zero, 0, sizeof (sin->sin_zero));
  }
}

#if HAVE_IP_RECVERR || HAVE_IPV6_RECVERR
#include <linux/types.h>
#include <linux/errqueue.h>
#include <sys/uio.h>
#endif

static
int sres_no_update(sres_async_t *async,
		   sres_socket_t new_socket,
		   sres_socket_t old_socket)
{
  return 0;
}

/** Create connected sockets for resolver.
 */
int sres_resolver_sockets(sres_resolver_t *res,
			  sres_socket_t *return_sockets, 
			  int n)
{
  sres_socket_t s = INVALID_SOCKET;
  int i, retval;

  if (!sres_resolver_set_async(res, sres_no_update,
			       (sres_async_t *)-1, 1))
    return -1;

  retval = res->res_n_servers; assert(retval <= SRES_MAX_NAMESERVERS);

  if (!return_sockets || n == 0)
    return retval;

  for (i = 0; i < retval && i < n;) {
    sres_server_t *dns = res->res_servers[i];

    s = sres_server_socket(res, dns);

    if (s == INVALID_SOCKET) {	/* Mark as a bad destination */
      dns->dns_icmp = SRES_TIME_MAX;
      dns->dns_error = SRES_TIME_MAX;	
    }

    return_sockets[i++] = s;
  }

  return retval;
}

#if 0
/** Get a server by socket address */
static
sres_server_t *
sres_server_by_sockaddr(sres_resolver_t const *res, 
			void const *from, socklen_t fromlen)
{
  int i;

  for (i = 0; i < res->res_n_servers; i++) {
    sres_server_t *dns = res->res_servers[i];
    if (dns->dns_addrlen == fromlen && 
	memcmp(dns->dns_addr, from, fromlen) == 0)
      return dns;
  }
  
  return NULL;
}
#endif

/** Receive error message from socket. */
#if HAVE_IP_RECVERR || HAVE_IPV6_RECVERR
int sres_resolver_error(sres_resolver_t *res, int socket)
{
  int errcode = 0;
  struct cmsghdr *c;
  struct sock_extended_err *ee;
  struct sockaddr_storage *from;
  char control[512];
  char errmsg[64 + 768];
  struct iovec iov[1];
  struct msghdr msg[1] = {{ 0 }};
  struct sockaddr_storage name[1] = {{ 0 }};
  int n;
  char info[128] = "";

  SU_DEBUG_9(("%s(%p, %u) called\n", "sres_resolver_error",
	      (void *)res, socket));

  msg->msg_name = name, msg->msg_namelen = sizeof(name);
  msg->msg_iov = iov, msg->msg_iovlen = 1;
  iov->iov_base = errmsg, iov->iov_len = sizeof(errmsg);
  msg->msg_control = control, msg->msg_controllen = sizeof(control);

  n = recvmsg(socket, msg, MSG_ERRQUEUE);

  if (n < 0) {
    int error = su_errno();
    if (error != EAGAIN && error != EWOULDBLOCK)
      SU_DEBUG_1(("%s: recvmsg: %s\n", __func__, su_strerror(error)));
    return n;
  }

  if ((msg->msg_flags & MSG_ERRQUEUE) != MSG_ERRQUEUE) {
    SU_DEBUG_1(("%s: recvmsg: no errqueue\n", __func__));
    return su_seterrno(EIO);
  }

  if (msg->msg_flags & MSG_CTRUNC) {
    SU_DEBUG_1(("%s: extended error was truncated\n", __func__));
    return su_seterrno(EIO);
  }

  if (msg->msg_flags & MSG_TRUNC) {
    /* ICMP message may contain original message... */
    SU_DEBUG_5(("%s: icmp(6) message was truncated (at %d)\n", __func__, n));
  }

  /* Go through the ancillary data */
  for (c = CMSG_FIRSTHDR(msg); c; c = CMSG_NXTHDR(msg, c)) {
    if (0
#if HAVE_IP_RECVERR
	|| (c->cmsg_level == SOL_IP && c->cmsg_type == IP_RECVERR)
#endif
#if HAVE_IPV6_RECVERR
	|| (c->cmsg_level == SOL_IPV6 && c->cmsg_type == IPV6_RECVERR)
#endif
	) {
      char const *origin;

      ee = (struct sock_extended_err *)CMSG_DATA(c);
      from = (void *)SO_EE_OFFENDER(ee);
      info[0] = '\0';

      switch (ee->ee_origin) {
      case SO_EE_ORIGIN_LOCAL:
	strcpy(info, origin = "local");
	break;
      case SO_EE_ORIGIN_ICMP:
	snprintf(info, sizeof(info), "%s type=%u code=%u", 
		 origin = "icmp", ee->ee_type, ee->ee_code);
	break;
      case SO_EE_ORIGIN_ICMP6:
	snprintf(info, sizeof(info), "%s type=%u code=%u", 
		 origin = "icmp6", ee->ee_type, ee->ee_code);
	break;
      case SO_EE_ORIGIN_NONE:
	strcpy(info, origin = "none");
	break;
      default:
	strcpy(info, origin = "unknown");
	break;
      }

      if (ee->ee_info)
	snprintf(info + strlen(info), sizeof(info) - strlen(info), 
		 " info=%08x", ee->ee_info);
      errcode = ee->ee_errno;

      if (from->ss_family != AF_UNSPEC) {
	socklen_t fromlen = ((char *)c + c->cmsg_len) - (char *)from;

	sres_canonize_sockaddr(from, &fromlen);

	snprintf(info + strlen(info), sizeof(info) - strlen(info), 
		 " reported by ");
	inet_ntop(from->ss_family, SS_ADDR(from), 
		  info + strlen(info), sizeof(info) - strlen(info));
      }

      if (msg->msg_namelen <= 0)
	break;

      {
	int error;
	socklen_t errorlen = sizeof error;
	/* Get error, if any */
	getsockopt(socket, SOL_SOCKET, SO_ERROR, (void *)&error, &errorlen);
      }
      
      if (sres_resolver_report_error(res, socket, errcode, 
				     msg->msg_name, msg->msg_namelen,
				     info))
	return errcode;
      break;
    }
  }

  if (errcode)
    sres_resolver_report_error(res, socket, errcode, NULL, 0, info);

  return errcode;
}

#else
int sres_resolver_error(sres_resolver_t *res, int socket)
{
  int errcode = 0;
  socklen_t errorlen = sizeof(errcode);

  SU_DEBUG_9(("%s(%p, %u) called\n", "sres_resolver_error",
	      (void *)res, socket));

  getsockopt(socket, SOL_SOCKET, SO_ERROR, (void *)&errcode, &errorlen);

  return sres_resolver_report_error(res, socket, errcode, NULL, 0, "");
}
#endif


/** Report error */
static
int 
sres_resolver_report_error(sres_resolver_t *res,
			   sres_socket_t socket,
			   int errcode,
			   struct sockaddr_storage *remote,
			   socklen_t remotelen, 
			   char const *info)
{
  char buf[80];

  buf[0] = '\0';

  if (remote) {
    sres_canonize_sockaddr(remote, &remotelen);

    if (remote->ss_family == AF_INET) {
      struct sockaddr_in const *sin = (struct sockaddr_in *)remote;
      uint8_t const *in_addr = (uint8_t*)&sin->sin_addr;
      inet_ntop(AF_INET, in_addr, buf, sizeof(buf));
    } 
#if HAVE_SIN6
    else if (remote->ss_family == AF_INET6) {
      struct sockaddr_in6 const *sin6 = (struct sockaddr_in6 *)remote;
      uint8_t const *in_addr = (uint8_t*)&sin6->sin6_addr;
      inet_ntop(AF_INET6, in_addr, buf, sizeof(buf));
    }
#endif
  }

  SU_DEBUG_5(("sres: network error %u (%s)%s%s%s%s\n", 
	      errcode, su_strerror(errcode),
	      buf[0] ? " from " : "", buf, 
	      info ? " by " : "",
	      info ? info : ""));

  if (res->res_queries->qt_used) {
    /* Report error to queries */
    sres_server_t *dns;
    sres_query_t *q;
    size_t i;

    dns = sres_server_by_socket(res, socket);

    if (dns) {
      time(&res->res_now);
      dns->dns_icmp = res->res_now;

      for (i = 0; i < res->res_queries->qt_size; i++) {
	q = res->res_queries->qt_table[i];
      
	if (!q || dns != res->res_servers[q->q_i_server])
	  continue;

	/* Resend query/report error to application */
	sres_resend_dns_query(res, q, 0);

	if (q != res->res_queries->qt_table[i])
	  i--;
      }
    }
  }
  
  return 1;
}


/** Receive a response packet from socket. */
int 
sres_resolver_receive(sres_resolver_t *res, int socket)
{
  ssize_t num_bytes;
  int error;
  sres_message_t m[1];

  sres_query_t *query = NULL;
  sres_record_t **reply;
  sres_server_t *dns;

  struct sockaddr_storage from[1];
  socklen_t fromlen = sizeof from;

  SU_DEBUG_9(("%s(%p, %u) called\n", "sres_resolver_receive",
	      (void *)res, socket));

  memset(m, 0, offsetof(sres_message_t, m_data)); 
  
  num_bytes = sres_recvfrom(socket, m->m_data, sizeof (m->m_data), 0,
			    (void *)from, &fromlen);

  if (num_bytes <= 0) {
    SU_DEBUG_5(("%s: %s\n", "sres_resolver_receive", su_strerror(su_errno())));
    return 0;
  }

  if (num_bytes > 65535)
    num_bytes = 65535;

  dns = sres_server_by_socket(res, socket);
  if (!dns)
    return 0;

  m->m_size = (uint16_t)num_bytes;

  /* Decode the received message and get the matching query object */
  error = sres_decode_msg(res, m, &query, &reply);

  sres_log_response(res, m, from, query, reply);

  if (query == NULL)
    ;
  else if (error == SRES_EDNS0_ERR) {
    dns->dns_edns = edns_not_supported;
    assert(query->q_id);
    sres_remove_query(res, query, 0);
    query->q_id = sres_new_id(res);
    query->q_hash = query->q_id * Q_PRIME;
    sres_qtable_append(res->res_queries, query);
    sres_send_dns_query(res, query);
    query->q_retry_count++;
  } 
  else if (!error && reply) {
    /* Remove the query from the pending list and notify the listener */
    sres_remove_query(res, query, 1);
    if (query->q_callback != NULL)
      (query->q_callback)(query->q_context, query, reply); 
    sres_free_query(res, query);
  }
  else {
    sres_query_report_error(query, reply);
  }

  return 1;
}

static
void sres_log_response(sres_resolver_t const *res, 
		       sres_message_t const *m,
		       struct sockaddr_storage const *from,
		       sres_query_t const *query,
		       sres_record_t * const *reply)
{
  if (SU_LOG->log_level >= 5) {
#ifndef ADDRSIZE
#define ADDRSIZE 48
#endif
    char host[ADDRSIZE] = "*";

    if (from == NULL)
      ;
    else if (from->ss_family == AF_INET) {
      struct sockaddr_in const *sin = (void *)from;
      inet_ntop(AF_INET, &sin->sin_addr, host, sizeof host);
    } 
#if HAVE_SIN6
    else if (from->ss_family == AF_INET6) {
      struct sockaddr_in6 const *sin6 = (void *)from;
      inet_ntop(AF_INET6, &sin6->sin6_addr, host, sizeof host);
    }
#endif

    SU_DEBUG_5(("sres_resolver_receive(%p, %p) id=%u (from [%s]:%u)\n", 
		(void *)res, (void *)query, m->m_id, 
		host, ntohs(((struct sockaddr_in *)from)->sin_port)));
  }
}

/** Decode DNS message.
 *
 *
 * @retval 0 if successful
 * @retval >0 if message indicated error
 * @retval -1 if decoding error
 */
static
int
sres_decode_msg(sres_resolver_t *res, 
		sres_message_t *m,
		sres_query_t **qq,
		sres_record_t ***return_answers)
{
  sres_record_t *rr = NULL, **answers = NULL, *error = NULL;
  sres_query_t *query = NULL, **hq;
  su_home_t *chome = CHOME(res->res_cache);
  hash_value_t hash;
  int err;
  unsigned i, total, errorcount = 0;

  assert(res && m && return_answers);

  time(&res->res_now);

  *qq = NULL;
  *return_answers = NULL;

  m->m_offset = sizeof(m->m_packet.mp_header);

  if (m->m_size < m->m_offset) {
    SU_DEBUG_5(("sres_decode_msg: truncated message\n"));
    return -1;
  }

  m->m_flags   = ntohs(m->m_flags);
  m->m_qdcount = ntohs(m->m_qdcount);
  m->m_ancount = ntohs(m->m_ancount); 
  m->m_nscount = ntohs(m->m_nscount); 
  m->m_arcount = ntohs(m->m_arcount); 

  hash = Q_PRIME * m->m_id;

  /* Search for query with this ID */
  for (hq = sres_qtable_hash(res->res_queries, hash);
       *hq;
       hq = sres_qtable_next(res->res_queries, hq))
    if (hash == (*hq)->q_hash)
      break;

  *qq = query = *hq;

  if (!query) {
    SU_DEBUG_5(("sres_decode_msg: matching query for id=%u\n", m->m_id));
    return -1;
  }

  assert(query && m->m_id == query->q_id);

  if ((m->m_flags & 15) == SRES_FORMAT_ERR && query->q_edns)
    return SRES_EDNS0_ERR;

  /* Scan question section */
  for (i = 0; i < m->m_qdcount; i++) {
    char name[1024];
    uint16_t qtype, qclass;
    m_get_domain(name, sizeof(name), m, 0); /* Query domain */
    qtype = m_get_uint16(m);  /* Query type */
    qclass = m_get_uint16(m); /* Query class */
  }

  if (m->m_error) {
    SU_DEBUG_5(("sres_decode_msg: %s\n", m->m_error));
    return -1;
  }

  err = m->m_flags & SRES_HDR_RCODE;

  if (m->m_ancount == 0 && err == 0)
    err = SRES_RECORD_ERR;

  if (err == SRES_RECORD_ERR || 
      err == SRES_NAME_ERR || 
      err == SRES_UNIMPL_ERR)
    errorcount = 1;

  total = errorcount + m->m_ancount + m->m_nscount + m->m_arcount;

  answers = su_zalloc(chome, (total + 1) * sizeof answers[0]);
  if (!answers)
    return -1;

  /* Scan resource records */
  for (i = 0; i < total; i++) {
    if (i < errorcount)
      rr = error = sres_create_error_rr(res->res_cache, query, err);
    else
      rr = sres_create_record(res, m);
 
    if (!rr) {
      SU_DEBUG_5(("sres_create_record: %s\n", m->m_error));
      break;
    }

    if (error && rr->sr_type == sres_type_soa) {
      sres_soa_record_t *soa = (sres_soa_record_t *)rr;
      if (error->sr_ttl > soa->soa_minimum && soa->soa_minimum > 10)
	  error->sr_ttl = soa->soa_minimum;
    }

    answers[i] = rr;
  }

  if (i < total) {
    for (i = 0; i < total; i++)
      sres_cache_free_record(res->res_cache, answers[i]);
    su_free(chome, answers);
    return -1;
  }

  for (i = 0; i < total; i++) {
    rr = answers[i];

    if (i < m->m_ancount + errorcount)
      /* Increase reference count of entry passed in answers */
      rr->sr_refcount++;
    else
      /* Do not pass extra records to user */
      answers[i] = NULL;	

    sres_cache_store(res->res_cache, rr, res->res_now);
  }

  *return_answers = answers;

  return err;
}  

static
sres_record_t *
sres_create_record(sres_resolver_t *res, sres_message_t *m)
{
  sres_cache_t *cache = res->res_cache;
  sres_record_t *sr, sr0[1];

  uint16_t m_size;
  char name[1025];
  int len;
  char btype[8], bclass[8];

  sr = memset(sr0, 0, sizeof sr0);
  
  len = m_get_domain(sr->sr_name = name, sizeof(name) - 1, m, 0); /* Name */
  sr->sr_type = m_get_uint16(m);  /* Type */
  sr->sr_class = m_get_uint16(m); /* Class */
  sr->sr_ttl = m_get_uint32(m);   /* TTL */
  sr->sr_rdlen = m_get_uint16(m); /* rdlength */
  sr->sr_parsed = 1;		
  if (m->m_error)
    goto error;

  name[len] = 0;

  SU_DEBUG_9(("RR received %s %s %s %d rdlen=%d\n", name,
	      sres_record_type(sr->sr_type, btype),
	      sres_record_class(sr->sr_class, bclass),
	      sr->sr_ttl, sr->sr_rdlen));

  if (m->m_offset + sr->sr_rdlen > m->m_size) {
    m->m_error = "truncated message";
    goto error;
  }

  m_size = m->m_size;
  /* limit m_size to indicated rdlen, check whether record is truncated */
  m->m_size = m->m_offset + sr->sr_rdlen;

  switch (sr->sr_type) {
  case sres_type_soa:
    sr = sres_init_rr_soa(cache, sr->sr_soa, m);
    break;
  case sres_type_a:
    sr = sres_init_rr_a(cache, sr->sr_a, m);
    break;
  case sres_type_a6:
    sr = sres_init_rr_a6(cache, sr->sr_a6, m);
    break;
  case sres_type_aaaa:
    sr = sres_init_rr_aaaa(cache, sr->sr_aaaa, m);
    break;
  case sres_type_cname:
    sr = sres_init_rr_cname(cache, sr->sr_cname, m);
    break;
  case sres_type_ptr:
    sr = sres_init_rr_ptr(cache, sr->sr_ptr, m);
    break;
  case sres_type_srv:
    sr = sres_init_rr_srv(cache, sr->sr_srv, m);
    break;
  case sres_type_naptr:
    sr = sres_init_rr_naptr(cache, sr->sr_naptr, m);
    break;
  default:
    sr = sres_init_rr_unknown(cache, sr->sr_record, m);
    break;
  }

  if (m->m_error)
    goto error;

  if (sr == sr0)
    sr = sres_cache_alloc_record(cache, sr, 0);

  if (sr == NULL) {
    m->m_error = "memory exhausted";
    goto error;
  }

  /* Fill in the common fields */
  m->m_size = m_size;

  return sr;

 error:  
  if (sr && sr != sr0)
    sres_cache_free_record(cache, sr);
  SU_DEBUG_5(("%s: %s\n", "sres_create_record", m->m_error));
  return NULL;
}

/** Decode SOA record */
static sres_record_t *sres_init_rr_soa(sres_cache_t *cache,
				       sres_soa_record_t *soa,
				       sres_message_t *m)
{
  uint16_t moffset, roffset;
  int mnamelen, rnamelen;

  soa->soa_record->r_size = sizeof *soa;

  moffset = m->m_offset, mnamelen = m_get_domain(NULL, 0, m, 0) + 1;
  roffset = m->m_offset, rnamelen = m_get_domain(NULL, 0, m, 0) + 1;

  soa->soa_serial = m_get_uint32(m);
  soa->soa_refresh = m_get_uint32(m);
  soa->soa_retry = m_get_uint32(m);
  soa->soa_expire = m_get_uint32(m);
  soa->soa_minimum = m_get_uint32(m);
  
  if (m->m_error)
    return NULL;

  soa = (void *)sres_cache_alloc_record(cache, (void *)soa,
					mnamelen + rnamelen);

  if (soa) {
    char *mname, *rname;

    assert(moffset > 0 && roffset > 0 && mnamelen > 1 && rnamelen > 1);

    m_get_domain(mname = (char *)(soa + 1), mnamelen, m, moffset);
    soa->soa_mname = mname;

    m_get_domain(rname = mname + mnamelen, rnamelen, m, roffset);
    soa->soa_rname = rname;
  }

  return (sres_record_t *)soa;
}

/** Decode A record */
static sres_record_t *sres_init_rr_a(sres_cache_t *cache,
				     sres_a_record_t *a,
				     sres_message_t *m)
{
  a->a_record->r_size = sizeof *a;

  a->a_addr.s_addr = htonl(m_get_uint32(m));

  return (sres_record_t *)a;
}

/** Decode A6 record. See @RFC2874 */
static sres_record_t *sres_init_rr_a6(sres_cache_t *cache,
				      sres_a6_record_t *a6,
				      sres_message_t *m)
{

  int suffixlen = 0, i;
  int prefixlen = 0;
  uint16_t offset;

  a6->a6_record->r_size = sizeof *a6;

  a6->a6_prelen = m_get_uint8(m);

  if (a6->a6_prelen > 128) {
    m->m_error = "Invalid prefix length in A6 record";
    return NULL;
  }

  suffixlen = (128 + 7 - a6->a6_prelen) / 8;
  for (i = 16 - suffixlen; i < 16; i++)
    a6->a6_suffix.u6_addr[i] = m_get_uint8(m);

  if (a6->a6_prelen > 0) {
    /* Zero pad bits */
    a6->a6_suffix.u6_addr[16 - suffixlen] &= 0xff >> (a6->a6_prelen & 7);

    offset = m->m_offset, prefixlen = m_get_domain(NULL, 0, m, 0) + 1;

    if (m->m_error)
      return NULL;

    a6 = (void *)sres_cache_alloc_record(cache, (void *)a6, prefixlen);
    if (a6)
      m_get_domain(a6->a6_prename = (char *)(a6 + 1), prefixlen, m, offset);
  }

  return (sres_record_t *)a6;
}

/** Decode AAAA record */
static sres_record_t *sres_init_rr_aaaa(sres_cache_t *cache,
					sres_aaaa_record_t *aaaa,
					sres_message_t *m)
{
  aaaa->aaaa_record->r_size = sizeof *aaaa;

  if (m->m_offset + sizeof(aaaa->aaaa_addr) <= m->m_size) {
    memcpy(&aaaa->aaaa_addr, m->m_data + m->m_offset, sizeof(aaaa->aaaa_addr));
    m->m_offset += sizeof(aaaa->aaaa_addr);
  }
  else
    m->m_error = "truncated AAAA record";

  return (sres_record_t *)aaaa;
}

/** Decode CNAME record */
static sres_record_t *sres_init_rr_cname(sres_cache_t *cache,
					 sres_cname_record_t *cn,
					 sres_message_t *m)
{
  uint16_t offset;
  int dlen;

  cn->cn_record->r_size = sizeof *cn;

  offset = m->m_offset, dlen = m_get_domain(NULL, 0, m, 0) + 1;

  if (m->m_error)
    return NULL;

  cn = (void *)sres_cache_alloc_record(cache, (void *)cn, dlen);
  if (cn)
    m_get_domain(cn->cn_cname = (char *)(cn + 1), dlen, m, offset);

  return (sres_record_t *)cn;
}

/** Decode PTR record */
static sres_record_t *sres_init_rr_ptr(sres_cache_t *cache,
				       sres_ptr_record_t *ptr,
				       sres_message_t *m)
{
  uint16_t offset;
  int dlen;

  ptr->ptr_record->r_size = sizeof *ptr;

  offset = m->m_offset, dlen = m_get_domain(NULL, 0, m, 0) + 1;

  if (m->m_error)
    return NULL;

  ptr = (void *)sres_cache_alloc_record(cache, (void *)ptr, dlen);
  if (ptr)
    m_get_domain(ptr->ptr_domain = (char *)(ptr + 1), dlen, m, offset);

  return (sres_record_t *)ptr;
}

/** Decode SRV record */
static sres_record_t *sres_init_rr_srv(sres_cache_t *cache,
				       sres_srv_record_t *srv,
				       sres_message_t *m)
{
  uint16_t offset;
  int dlen;

  srv->srv_record->r_size = sizeof *srv;

  srv->srv_priority = m_get_uint16(m);
  srv->srv_weight = m_get_uint16(m);
  srv->srv_port = m_get_uint16(m);
  offset = m->m_offset, dlen = m_get_domain(NULL, 0, m, 0) + 1;
  if (m->m_error)
    return NULL;
  
  srv = (void *)sres_cache_alloc_record(cache, (void *)srv, dlen);
  if (srv)
    m_get_domain(srv->srv_target = (char *)(srv + 1), dlen, m, offset);

  return (sres_record_t *)srv;
}

/** Decode NAPTR record */
static sres_record_t *sres_init_rr_naptr(sres_cache_t *cache,
					 sres_naptr_record_t *na,
					 sres_message_t *m)
{
  uint16_t offset[4];
  int len[4];

  na->na_record->r_size = sizeof *na;

  na->na_order = m_get_uint16(m);
  na->na_prefer = m_get_uint16(m);

  offset[0] = m->m_offset, len[0] = m_get_string(NULL, 0, m, 0) + 1;
  offset[1] = m->m_offset, len[1] = m_get_string(NULL, 0, m, 0) + 1;
  offset[2] = m->m_offset, len[2] = m_get_string(NULL, 0, m, 0) + 1;
  offset[3] = m->m_offset, len[3] = m_get_domain(NULL, 0, m, 0) + 1;

  if (m->m_error)
    return NULL;

  na = (void *)sres_cache_alloc_record(cache, (void *)na,
				       len[0] + len[1] + len[2] + len[3]);
  if (na) {
    char *s = (char *)(na + 1);
    m_get_string(na->na_flags = s, len[0], m, offset[0]), s += len[0];
    m_get_string(na->na_services = s, len[1], m, offset[1]), s += len[1];
    m_get_string(na->na_regexp = s, len[2], m, offset[2]), s += len[2];
    m_get_domain(na->na_replace = s, len[3], m, offset[3]), s += len[3];
  }

  return (sres_record_t *)na;
}

/** Decode unknown record */
static sres_record_t *sres_init_rr_unknown(sres_cache_t *cache,
					   sres_common_t *r,
					   sres_message_t *m)
{
  if (m->m_offset + r->r_rdlen > m->m_size)
    m->m_error = "truncated record";

  if (m->m_error)
    return NULL;

  r->r_size = sizeof *r;

  r = (void *)sres_cache_alloc_record(cache, (void *)r, r->r_rdlen + 1);
  if (r) {
    char *data = (char *)(r + 1);

    r->r_parsed = 0;

    memcpy(data, m->m_data + m->m_offset, r->r_rdlen);
    m->m_offset += r->r_rdlen;
    data[r->r_rdlen] = 0;
  }

  return (sres_record_t *)r;
}

static
sres_record_t *sres_create_error_rr(sres_cache_t *cache,
				    sres_query_t const *q,
				    uint16_t errcode)
{
  sres_record_t *sr, r[1];
  char buf[SRES_MAXDNAME];

  sr = memset(r, 0, sizeof *sr);

  sr->sr_name = (char *)sres_toplevel(buf, sizeof buf, q->q_name);
  sr->sr_size = sizeof *sr;
  sr->sr_status = errcode;
  sr->sr_type = q->q_type;
  sr->sr_class = q->q_class;
  sr->sr_ttl = 10 * 60;

  return sres_cache_alloc_record(cache, sr, 0);
}

/* Message processing primitives */

static
void
m_put_uint16(sres_message_t *m, 
	     uint16_t h)
{
  uint8_t *p;

  if (m->m_error)
    return;

  p = m->m_data + m->m_offset;
  m->m_offset += sizeof h;

  if (m->m_offset > m->m_size) {
    m->m_error = "message size overflow";
    return;
  }

  p[0] = h >> 8; p[1] = h;
}

static
void 
m_put_uint32(sres_message_t *m, 
	     uint32_t w)
{
  uint8_t *p;

  if (m->m_error)
    return;

  p = m->m_data + m->m_offset;
  m->m_offset += sizeof w;

  if (m->m_offset > m->m_size) {
    m->m_error = "message size overflow";
    return;
  }

  p[0] = w >> 24; p[1] = w >> 16; p[2] = w >> 8; p[3] = w;
}

/*
 * Put domain into query
 */
static
uint16_t
m_put_domain(sres_message_t *m,
	     char const *domain, 
	     uint16_t top,
	     char const *topdomain)
{
  char const *label;
  size_t llen;

  if (m->m_error)
    return top;

  /* Copy domain into query label at a time */
  for (label = domain; label && label[0]; label += llen) {
    if (label[0] == '.' && label[1] != '\0') {
      m->m_error = "empty label";
      return 0;
    }

    llen = strcspn(label, ".");

    if (llen >= 64) {
      m->m_error = "too long label";
      return 0;
    }
    if (m->m_offset + llen + 1 > m->m_size) {
      m->m_error = "message size overflow";
      return 0;
    }

    m->m_data[m->m_offset++] = (uint8_t)llen;
    memcpy(m->m_data + m->m_offset, label, llen);
    m->m_offset += (uint8_t)llen;

    if (label[llen] == '\0')
      break;
    if (llen == 0)
      return top;
    if (label[llen + 1])
      llen++;
  }

  if (top) {
    m_put_uint16(m, 0xc000 | top);
    return top;
  }
  else if (topdomain) {
    uint16_t retval = m->m_offset;
    m_put_domain(m, topdomain, 0, NULL);
    return retval;
  }
  else if (m->m_offset < m->m_size)
    m->m_data[m->m_offset++] = '\0';
  else
    m->m_error = "message size overflow";

  return 0;
}

static
uint32_t
m_get_uint32(sres_message_t *m)
{
  uint8_t const *p = m->m_data + m->m_offset;

  if (m->m_error)
    return 0;

  m->m_offset += 4;

  if (m->m_offset > m->m_size) {
    m->m_error = "truncated message";
    return 0;
  }

  return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}

static
uint16_t 
m_get_uint16(sres_message_t *m)
{
  uint8_t const *p = m->m_data + m->m_offset;

  if (m->m_error)
    return 0;

  m->m_offset += 2;

  if (m->m_offset > m->m_size) {
    m->m_error = "truncated message";
    return 0;
  }

  return (p[0] << 8) | p[1];
}

static
uint8_t 
m_get_uint8(sres_message_t *m)
{
  uint8_t const *p = m->m_data + m->m_offset;

  if (m->m_error)
    return 0;

  m->m_offset += 1;

  if (m->m_offset > m->m_size) {
    m->m_error = "truncated message";
    return 0;
  }

  return p[0];
}

/**
 * Get a string.
 */
static int m_get_string(char *d, 
			int n,
			sres_message_t *m,
			uint16_t offset)
{
  uint8_t size;
  uint8_t *p = m->m_data;
  int save_offset;

  if (m->m_error)
    return 0;

  if (offset == 0)
    offset = m->m_offset, save_offset = 1;
  else
    save_offset = 0;

  size = p[offset++];
  
  if (size + offset >= m->m_size) {
    m->m_error = "truncated message";
    return size;
  }

  offset += size;

  if (save_offset)
    m->m_offset = offset;

  if (n == 0 || d == NULL)
    return size;		/* Just return the size (without NUL). */

  memcpy(d, p + offset - size, size < n ? size : n);

  if (size < n)
    d[size] = '\0';		/* NUL terminate */

  return size;
}

/**
 * Uncompress a domain.
 *
 * @param offset start uncompression from this point in message
 */
static int m_get_domain(char *d, 
			int n,
			sres_message_t *m,
			uint16_t offset)
{
  uint8_t cnt;
  int i = 0;
  uint8_t *p = m->m_data;
  uint16_t new_offset;
  int save_offset;

  if (m->m_error)
    return 0;

  if (d == NULL) 
    n = 0;

  if (offset == 0)
    offset = m->m_offset, save_offset = 1;
  else
    save_offset = 0;

  while ((cnt = p[offset++])) {
    if (cnt >= 0xc0) {
      if (offset >= m->m_size) {
        m->m_error = "truncated message";
        return 0;
      }

      new_offset = ((cnt & 0x3F) << 8) + p[offset++];

      if (save_offset)
        m->m_offset = offset;

      if (new_offset <= 0 || new_offset >= m->m_size) {
        m->m_error = "invalid domain compression";
        return 0;
      }

      offset = new_offset;
      save_offset = 0;
    } 
    else {
      if (offset + cnt >= m->m_size) {
        m->m_error = "truncated message";
        return 0;
      }
      if (i + cnt + 1 < n) {
        memcpy(d + i, p + offset, cnt);
        d[i + cnt] = '.';
      }

      i += cnt + 1;
      offset += cnt;
    }  
  }

  if (i == 0) { 
    if (i < n) 
      d[i] = '.';
    i++; 
  }

  if (i < n)
    d[i] = '\0';

  if (save_offset)
    m->m_offset = offset;

  return i;
}
