Logo Search packages:      
Sourcecode: iacd version File versions  Download package

dns.c

/* dns.c - Asynchronous DNS resolver by Roman Senn <smoli@paranoya.ch>
 *
 * Partial rewrite of DNS client code from libowfat by fefe
 * (http://www.fefe.de/libowfat/), originally written and put
 * into public domain July 4 2002 (http://online.securityfocus.com/archive/1/280642)
 * by D.J. Bernstein (http://cr.yp.to/).
 *
 * The idea was to provide a simple async resolver API in standard libc style
 * which does not base on crappy resolver functions from ISC libresolv.
 * To keep this piece of code simple, slim and portable I decided to reimplement
 * some of the functions using libc functions/types rather than the ones from fefe.
 *
 * Known BUGS / TODO:
 *
 * - IPv6 support has not been tested at this time.
 * - callbacks have not been implemented yet.
 * - rewrite ip6_scan using inet_pton.
 * - too much time() system calls -> use a global variable
 * - /etc/hosts is currently ignored.
 *
 * Bug reports and stuff go to <smoli@paranoya.ch>
 * 
 * $Id: dns.c,v 1.1.2.2 2002/11/13 17:33:30 tgr Exp $
 */
/* This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.
 * If not, write to the Free Software Foundation,
 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#define _GNU_SOURCE

#define BSD_COMP

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <time.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "dns.h"

#define __DNS_INTERNAL_BUFSIZE__ 256  

#define ip6_mapv4(x)       ((struct in_addr *)(((char *)(x)) + 12))
#define ip6_isv4mapped(ip) (!memcmp(ip, &in6addr_ip4mapped, 12))

static const struct in6_addr in6addr_ip4mapped = { { { 0,0,0,0,0,0,0,0,0,0,0xff,0xff } } };
static int                   ok = 0;
static unsigned int          uses;
static time_t                deadline;
static char                  ip[256]; /* defined if ok */
static int                   iplen;
static const int             timeouts[4] = { 1, 3, 11, 45 };
#ifdef HAVE_IPV6
static int                   noipv6 = 0;
#endif

#ifndef EAFNOSUPPORT
#define EAFNOSUPPORT EINVAL
#endif

static __inline__ void uint16_unpack_big(const char *in, unsigned short *out)
{
  *out = ((unsigned short)((unsigned char)in[0]) << 8) + (unsigned char)in[1];
}

static __inline__ void uint16_pack_big(char *out, unsigned short in)
{
  out[0] = in >> 8;
  out[1] = in & 255;
}

static __inline__ void packetfree(struct dns_resolver *d)
{
  if(!d->packet) return;
  free(d->packet);
  d->packet = 0;
}

static __inline__ void queryfree(struct dns_resolver *d)
{
  if(!d->query) return;
  free(d->query);
  d->query = 0;
}

static __inline__ void socketfree(struct dns_resolver *d)
{
  if(!d->s1) return;
  close(d->s1 - 1);
  d->s1 = 0;
}

static __inline__ int fromhex(unsigned char c)
{
  if (c >= '0' && c <= '9')
    return c - '0';
  else if (c >= 'A' && c <= 'F')
    return c - 'A' + 10;
  else if (c >= 'a' && c <= 'f')
    return c - 'a' + 10;
  return -1;
}

/* converts unsigned int to string. returns length of the string. */
static unsigned int utoa(char *buf, unsigned int i)
{
    register char *p = buf;
    register unsigned int n = 0; /* buffer index */
    register unsigned int v;     /* digit value */
  
    if((v = i / 1000000000) || n) p[n++] = v + '0'; i %= 1000000000;
    if((v = i / 100000000) || n)  p[n++] = v + '0'; i %= 100000000;
    if((v = i / 10000000) || n)   p[n++] = v + '0'; i %= 10000000;
    if((v = i / 1000000) || n)    p[n++] = v + '0'; i %= 1000000;
    if((v = i / 100000) || n)     p[n++] = v + '0'; i %= 100000;
    if((v = i / 10000) || n)      p[n++] = v + '0'; i %= 10000;
    if((v = i / 1000) || n)       p[n++] = v + '0'; i %= 1000;
    if((v = i / 100) || n)        p[n++] = v + '0'; i %= 100;
    if((v = i / 10) || n)         p[n++] = v + '0'; i %= 10;
  
    /* last remainder is last char */
    p[n++] = i + '0';
  
    /* null terminate the stuff */
    p[n] = '\0';
  
    return n;
}

static void dns_name4_domain(char name[DNS_NAME4_DOMAIN], struct in_addr *addr)
{
  unsigned int namelen;
  unsigned int i;
  char *ip = (char *)addr;

  namelen = 0;
  i = utoa(&name[namelen + 1], (unsigned int)(unsigned char)ip[3]);
  name[namelen++] = i;
  namelen += i;
  i = utoa(&name[namelen + 1], (unsigned int)(unsigned char)ip[2]);
  name[namelen++] = i;
  namelen += i;
  i = utoa(&name[namelen + 1], (unsigned int)(unsigned char)ip[1]);
  name[namelen++] = i;
  namelen += i;
  i = utoa(&name[namelen + 1], (unsigned int)(unsigned char)ip[0]);
  name[namelen++] = i;
  namelen += i;
  memcpy(name + namelen, "\7in-addr\4arpa\0", 14);
}

static int case_diffb(register const char *s, register unsigned int len, register const char *t)
{
  register unsigned char x;
  register unsigned char y;

  while(len > 0)
    {
      len--;
      x = *s++ - 'A';
      
      if(x <= 'Z' - 'A') x += 'a'; else x += 'A';
      
      y = *t++ - 'A';
      
      if(y <= 'Z' - 'A') y += 'a'; else y += 'A';
      
      if(x != y)
        return (int)(unsigned int)x - ((int)(unsigned int)y);
    }
  
  return 0;
}

/*
   IPip6 addresses are really ugly to parse.
   Syntax: (h = hex digit)
     1. hhhh:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh:hhhh
     2. any number of 0000 may be abbreviated as "::", but only once
     3. The last two words may be written as IPv4 address
 */

static unsigned int scan_ip6(const char *s, struct in6_addr *addr)
{
  unsigned int i;
  unsigned int len = 0;
  unsigned long u;
  char *ip = (char *)addr;
  char *invalid;

  char suffix[16];
  int prefixlen = 0;
  int suffixlen = 0;

  if((i = inet_aton(s, (struct in_addr *)&ip[12])))
    {
      memcpy(ip, &in6addr_ip4mapped, 12);
      return i;
    }
  
  for(i = 0; i < 16; i++) ip[i] = 0;
  
  for(;;)
    {
      if(*s == ':')
        {
          len++;
          
          if(s[1] == ':') /* Found "::", skip to part 2 */
            {
              s += 2;
              len++;
              break;
            }
          
          s++;
        }
      
      u = strtol(s, &invalid, 16);
      
      if(!u && invalid == s) return 0;
      
      if(prefixlen == 12 && invalid && *invalid == '.')
        {
          /* the last 4 bytes may be written as IPv4 address */
          i = inet_aton(s, (struct in_addr *)&ip[12]);
          
          if(i)
            return i + len;
          else
            return 0;
        }
      
      ip[prefixlen++] = (u >> 8);
      ip[prefixlen++] = (u & 255);
      s += i; len += i;
      
      if(prefixlen == 16)
        return len;
    }

  /* part 2, after "::" */
  for(;;)
    {
      if(*s == ':')
        {
          if(suffixlen == 0)
            break;
          s++;
          len++;
        }
      else if(suffixlen != 0)
        break;
      
      u = strtol(s, &invalid, 16);
      
      if(!u && invalid == s)
        {
          len--;
          break;
        }
      
      if(suffixlen + prefixlen <= 12 && invalid && *invalid == '.')
        {
          int j = inet_aton(s, (struct in_addr *)&suffix[suffixlen]);
          
          if(j)
            {
              suffixlen += 4;
              len += j;
              break;
            }
          else
            prefixlen = 12 - suffixlen;   /* make end-of-loop test true */
        }
      
      suffix[suffixlen++] = (u >> 8);
      suffix[suffixlen++] = (u & 255);
      s += i; len += i;
      
      if(prefixlen + suffixlen == 16)
        break;
    }
  
  for(i = 0; i < suffixlen; i++)
    ip[16 - suffixlen + i] = suffix[i];
  
  return len;
}

static int socket_connected(int s)
{
  struct sockaddr si;
  
  socklen_t sl = sizeof(struct sockaddr);
  
  if(getpeername(s, &si, &sl))
    return 0;
  
  return 1;
}

static int socket_connect4(int s, struct in_addr *ip, unsigned short port)
{
  struct sockaddr_in si;
  
  memset(&si, 0, sizeof(struct sockaddr_in));
  
  si.sin_family = AF_INET;
  si.sin_port = htons(port);
  si.sin_addr = *ip;
  return connect(s, (struct sockaddr *)&si, sizeof(struct sockaddr_in));
}

static int socket_connect6(int s, struct in6_addr *ip, unsigned short port, unsigned int scope_id)
{
  struct in_addr loopback = { INADDR_LOOPBACK };
#ifdef HAVE_IPV6
  struct sockaddr_in6 sa;

  if(noipv6)
    {
#endif /* HAVE_IPV6 */
      if(ip6_isv4mapped(ip))
        return socket_connect4(s, ip6_mapv4(ip), port);
      if(!memcmp(ip, &in6addr_any, sizeof(struct in6_addr)))
        return socket_connect4(s, &loopback, port);
#ifdef HAVE_IPV6
    }
  
  memset(&sa, 0, sizeof(struct sockaddr_in6));
  sa.sin6_family = PF_INET6;
  sa.sin6_port = htons(port);
  sa.sin6_flowinfo = 0;
  sa.sin6_scope_id = scope_id;
  sa.sin6_addr = *ip;

  return connect(s, (struct sockaddr *)&sa, sizeof(struct sockaddr_in6));
#else
  errno = EPROTONOSUPPORT;
  
  return -1;
#endif /* HAVE_IPV6 */
}

static int socket_udp6()
{
#ifdef HAVE_IPV6
  int s;

  if(noipv6) goto compat;
  
  s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
  
  if(s == -1)
    {
      if(errno == EINVAL || errno == EAFNOSUPPORT)
        {
          compat:
          
          s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
          noipv6 = 1;
          
          if(s == -1) return -1;
        }
      else
        return -1;
    }
  
  return s;
#else
  return socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
#endif /* HAVE_IPV6 */
}
static int socket_bind4(int s, const char *ip, unsigned short port)
{
  struct sockaddr_in si;
  
  memset(&si, 0, sizeof(struct sockaddr_in));
  
  si.sin_family = AF_INET;
  si.sin_port = htons(port);
  *(unsigned int *)&si.sin_addr = *(unsigned int *)ip;
  
  return bind(s, (struct sockaddr *)&si, sizeof(struct sockaddr_in));
}

static int socket_tcp6(void)
{
#ifdef HAVE_IPV6
  int s;

  if(noipv6) goto compat;
  
  s = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
  
  if(s == -1)
    {
      if(errno == EINVAL || errno == EAFNOSUPPORT)
        {
          compat:
          s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
          noipv6 = 1;
          
          if(s == -1)
            return -1;
        }
      else
        return -1;
    }
  
  return s;
#else
  return socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif /* HAVE_IPV6 */
}

static int socket_bind6(int s, struct in6_addr *addr, unsigned short port, unsigned int scope_id)
{
  char *ip = (char *)addr;
#ifdef HAVE_IPV6 
  struct sockaddr_in6 sa;

  if(noipv6)
    {
#endif /* HAVE_IPV6 */
      int i;
      
      for(i = 0; i < 16; i++)
        if(ip[i] != 0) break;
      
      if(i == 16 || ip6_isv4mapped(ip))
        return socket_bind4(s, ip + 12, port);
#ifdef HAVE_IPV6
    }
  
  memset(&sa, 0, sizeof(sa));
  sa.sin6_family = AF_INET6;
  sa.sin6_port = htons(port);
  /*  implicit: sa.sin6_flowinfo = 0; */
  memcpy((char *)&sa.sin6_addr, ip, 16);
  sa.sin6_scope_id = scope_id;

  return bind(s, (struct sockaddr *)&sa, sizeof(sa));
#else
  errno = EPROTONOSUPPORT;
  return -1;
#endif /* HAVE_IPV6 */
}

static unsigned int dns_domain_length(const char *dn)
{
  const char *x;
  unsigned char c;

  x = dn;
  while((c = *x++))
    x +=(unsigned int) c;
  return x - dn;
}

static int dns_domain_equal(const char *dn1, const char *dn2)
{
  unsigned int len;

  len = dns_domain_length(dn1);
  if(len != dns_domain_length(dn2)) return 0;

  if(case_diffb(dn1, len, dn2)) return 0;
  return 1;
}

/*
  DNS should have used LZ77 instead of its own sophomoric compression algorithm.
*/

static unsigned int dns_packet_copy(const char *buf, unsigned int len,
                                    unsigned int pos, char *out,
                                    unsigned int outlen)
{
  while(outlen)
    {
      if(pos >= len) { errno = EPROTO; return 0; }
      *out = buf[pos++];
      out++; outlen--;
    }
  
  return pos;
}

static unsigned int dns_packet_skipname(const char *buf, unsigned int len, 
                                        unsigned int pos)
{
  unsigned char ch;

  for(;;)
    {
      if(pos >= len) break;
      ch = buf[pos++];
      if(ch >= 192) return pos + 1;
      if(ch >= 64) break;
      if(!ch) return pos;
      pos += ch;
    }

  errno = EPROTO;
  return 0;
}

static unsigned int dns_packet_getname(const char *buf, unsigned int len,
                                       unsigned int pos, char *d, size_t n)
{
  unsigned int loop = 0;
  unsigned int state = 0;
  unsigned int firstcompress = 0;
  unsigned int where;
  unsigned char ch;
  char name[255];
  unsigned int namelen = 0;

  for(;;)
    {
      if(pos >= len) goto PROTO;
      
      ch = buf[pos++];
      
      if(++loop >= 1000) goto PROTO;

      if(state)
        {
          if(namelen + 1 > sizeof name) goto PROTO; name[namelen++] = ch;
          state--;
        }
      else
        {
          while(ch >= 192)
            {
              where = ch; where -= 192; where <<= 8;
              if(pos >= len) goto PROTO; ch = buf[pos++];
              if(!firstcompress) firstcompress = pos;
              pos = where + ch;
              if(pos >= len) goto PROTO; ch = buf[pos++];
              if(++loop >= 1000) goto PROTO;
            }
          
          if(ch >= 64) goto PROTO;
          if(namelen + 1 > sizeof name) goto PROTO; name[namelen++] = ch;
          if(!ch) break;
          state = ch;
        }
    }
  
  strncpy(d, name, n);
  d[n] = '\0';

  if(firstcompress) return firstcompress;
  return pos;

  PROTO:
  errno = EPROTO;
  return 0;
}

static int serverwantstcp(const char *buf, unsigned int len)
{
  char out[12];

  if(!dns_packet_copy(buf, len, 0, out, 12)) return 1;
  if(out[2] & 2) return 1;
  return 0;
}

static int serverfailed(const char *buf, unsigned int len)
{
  char out[12];
  unsigned int rcode;

  if(!dns_packet_copy(buf, len, 0, out, 12)) return 1;
  rcode = out[3];
  rcode &= 15;
  if(rcode && (rcode != 3)) { errno = EAGAIN; return 1; }
  return 0;
}

static int irrelevant(const struct dns_resolver *d, const char *buf, 
                      unsigned int len)
{
  char out[12];
  char dn[256];
  unsigned int pos;

  pos = dns_packet_copy(buf, len, 0, out, 12); if (!pos) return 1;
  if(memcmp(out, d->query + 2, 2)) return 1;
  if(out[4] != 0) return 1;
  if(out[5] != 1) return 1;

  pos = dns_packet_getname(buf, len, pos, dn, 256); if (!pos) return 1;
  if(!dns_domain_equal(dn, d->query + 14)) { free(dn); return 1; }

  pos = dns_packet_copy(buf, len, pos, out, 4); if (!pos) return 1;
  if(memcmp(out, d->qtype, 2)) return 1;
  if(memcmp(out + 2, DNS_C_IN, 2)) return 1;

  return 0;
}

static int randombind(struct dns_resolver *d)
{
  int j;

  for(j = 0; j < 10; j++)
    if(socket_bind6(d->s1 - 1, &d->localip, 1025 + rand() % 64510,
                    d->scope_id) == 0)
      return 0;
  
  if(socket_bind6(d->s1 - 1, &d->localip, 0, d->scope_id) == 0)
    return 0;
  
  return -1;
}

static int thisudp(struct dns_resolver *d)
{
  const char *ip;

  socketfree(d);

  while(d->udploop < 4)
    {
      for(; d->curserver < 16; d->curserver++)
        {
          ip = d->servers + 16 * d->curserver;
          if(memcmp(ip, &in6addr_any, 16))
            {
              d->query[2] = rand() & 0xff;
              d->query[3] = rand() & 0xff;
              
              d->s1 = 1 + socket_udp6();
              
              if(!d->s1) { dns_free(d); return -1; }
              if(randombind(d) == -1) { dns_free(d); return -1; }

              if(socket_connect6(d->s1 - 1, (struct in6_addr *)ip, 53, d->scope_id) == 0)
                if(send(d->s1 - 1, d->query + 2, d->querylen - 2, 0) ==
                   d->querylen - 2)
                  {
                    time_t now;
                    time(&now);
                    
                    d->deadline = now + timeouts[d->udploop];
                    d->tcpstate = 0;
                    return 0;
                  }
  
              socketfree(d);
            }
        }
      
      d->udploop++;
      d->curserver = 0;
    }

  dns_free(d); return -1;
}

static int firstudp(struct dns_resolver *d)
{
  d->curserver = 0;
  return thisudp(d);
}

static int nextudp(struct dns_resolver *d)
{
  d->curserver++;
  return thisudp(d);
}

static int thistcp(struct dns_resolver *d)
{
  const char *ip;

  socketfree(d);
  packetfree(d);

  for(; d->curserver < 16; d->curserver++)
    {
      ip = d->servers + 16 * d->curserver;
      
      if(memcmp(ip, &in6addr_any, 16))
        {
          d->query[2] = rand() & 0xff;
          d->query[3] = rand() & 0xff;
          
          d->s1 = 1 + socket_tcp6();
          if(!d->s1) { dns_free(d); return -1; }
          if(randombind(d) == -1) { dns_free(d); return -1; }
          
          d->deadline = time(0) + 10;
          
          if(socket_connect6(d->s1 - 1, (struct in6_addr *)ip, 53, d->scope_id) == 0)
            {
              d->tcpstate = 2;
              return 0;
            }
          
          if((errno == EINPROGRESS) || (errno == EWOULDBLOCK))
            {
              d->tcpstate = 1;
              return 0;
            }
  
          socketfree(d);
        }
    }
  
  dns_free(d); return -1;
}

static int firsttcp(struct dns_resolver *d)
{
  d->curserver = 0;
  return thistcp(d);
}

static int nexttcp(struct dns_resolver *d)
{
  d->curserver++;
  return thistcp(d);
}

static int dns_transmit_start(struct dns_resolver *d, const char servers[256],
                              int flagrecursive, const char *q, 
                              const char qtype[2], const struct in6_addr *localip)
{
  unsigned int len;

  dns_free(d);
  errno = EIO;

  len = dns_domain_length(q);
  d->querylen = len + 18;
  d->query = malloc(d->querylen);
  
  if (!d->query) return -1;

  uint16_pack_big(d->query, len + 16);
  memcpy(d->query + 2, flagrecursive ? "\0\0\1\0\0\1\0\0\0\0\0\0" : "\0\0\0\0\0\1\0\0\0\0\0\0gcc-bug-workaround", 12);
  memcpy(d->query + 14, q, len);
  memcpy(d->query + 14 + len, qtype, 2);
  memcpy(d->query + 16 + len, DNS_C_IN, 2);

  memcpy(d->qtype, qtype, 2);
  d->servers = servers;
  d->localip = *localip;

  d->udploop = flagrecursive ? 1 : 0;

  if(len + 16 > 512)
    return firsttcp(d);
  
  return firstudp(d);
}

/*
   Tries to get nameserver from resolv.conf.
 */
static int init()
{
  FILE *f;
  static char buf[__DNS_INTERNAL_BUFSIZE__];

  iplen = 0;
  
  /* read resolv.conf */      
  f = fopen("/etc/resolv.conf", "r");
  
  if(f == NULL)
    return -1;
      
  while(fgets(buf, __DNS_INTERNAL_BUFSIZE__, f))
    {
      char *p = buf;
      
      while(*p == ' ' || *p == '\t')
        p++;
      
      if(!strncmp("nameserver", p, 10))
        {
          p += 10;
          
          if(*p != ' ' && *p != '\t')
        return 0;
          
          p++;
          
          if(iplen > 60)
            return 1;
          
          if(scan_ip6(p, (struct in6_addr *)&ip[iplen]))
            iplen += 16;
        }
    }
  
  fclose(f);
  
  /* if we found no ip in config file we initialize to a default */
  if(!iplen)
    {
      memcpy(ip, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1", 16);
      iplen = 16;
    }
  
  /* clear unused space of ip array */
  memset(&ip[iplen], 0, 256 - iplen);
  
  return 0;
}

static int dns_resolvconfip()
{
  if(deadline < time(0))
    ok = 0;
  
  if(!uses) ok = 0;

  if(!ok)
    {
      if(init() == -1)
        return -1;

      deadline = time(0) + 600;
      
      uses = 10000;
      ok = 1;
    }

  uses--;
  
  return 0;
}

static int dns_domain_todot(char *out, const char *d, size_t n)
{
  char ch;
  char ch2;
/*  char ch3;*/
  int idx = 0;

  if(!n)
    return 0;
  
  if(!*d)
    {
      *out = '\0';
      return 0;
    }

  for(;idx < n;)
    {
      ch = *d++;
      
      while(ch--)
        {
          ch2 = *d++;
          
          if((ch2 >= 'A') && (ch2 <= 'Z'))
            ch2 += 0x20;

          if(((ch2 >= 'a') && (ch2 <= 'z')) ||
             ((ch2 >= '0') && (ch2 <= '9')) ||
              (ch2 == '-') || (ch2 == '_'))
            {
              out[idx++] = ch2;
              
              if(idx == n) break;
            }
/*          else
            {
              ch3 = ch2;
              buf[3] = '0' + (ch3 & 7); ch3 >>= 3;
              buf[2] = '0' + (ch3 & 7); ch3 >>= 3;
              buf[1] = '0' + (ch3 & 7);
              buf[0] = '\\';
            }*/
        }
      
      if(!*d || idx == n) break;
  
      out[idx++] = '.';
    }
  
  out[idx] = '\0';
  
  return idx;
}

/*
   Converts a domain name from dotted notation into dns style.
 */
static int dns_domain_fromdot(char *out, const char *buf, unsigned int n)
{
  char     label[63];
  unsigned int labellen = 0; /* <= sizeof label */
  char     name[255];
  unsigned int namelen = 0; /* <= sizeof name */
  char     ch;

  while(*buf)
    {
      ch = *buf++;
      
      if(ch == '.')
        {
          if(labellen)
            {
              if(namelen + labellen + 1 > sizeof(name))
                return 0;
              
              name[namelen++] = labellen;
              memcpy(&name[namelen], label, labellen);
              namelen += labellen;
              labellen = 0;
            }
          
          continue;
        }
      
      if(ch == '\\')
        {
          if(!n--) break;
          
          ch = *buf++;
          
          if((ch >= '0') && (ch <= '7'))
            {
              ch -= '0';
              
              if(n && (*buf >= '0') && (*buf <= '7'))
                {
                  ch <<= 3;
                  ch += *buf - '0';
                  buf++; n--;
                  
                  if(n && (*buf >= '0') && (*buf <= '7'))
                    {
                      ch <<= 3;
                      ch += *buf - '0';
                      ++buf; --n;
                    }
                }
            }
        }

      if(labellen >= sizeof(label))
        return 0;
      
      if(namelen > n)
        break;
      
      label[labellen++] = ch;
    }
  
  if(labellen)
    {
      if(namelen + labellen + 1 > sizeof(name)) return 0;
      name[namelen++] = labellen;
      memcpy(&name[namelen], label, labellen);
      namelen += labellen;
      labellen = 0;
    }
  
  if(namelen + 1 > sizeof name) return 0;
  name[namelen++] = '\0';

  memcpy(out, name, n);

  return 1;
}

static int dns_ip4_packet(struct in_addr *out, int n, const char *buf, unsigned int len)
{
  unsigned int pos;
  char header[12];
  unsigned short numanswers;
  unsigned short datalen;
  int count = 0;

  pos = dns_packet_copy(buf, len, 0, header, 12);
  
  if(!pos)
    return 0;
 
  uint16_unpack_big(&header[6], &numanswers);
  
  pos = dns_packet_skipname(buf, len, pos);
  
  if(!pos) return 0;
  
  pos += 4;

  if(n >= numanswers)
    return 0;
  
  while(numanswers--)
    {
      pos = dns_packet_skipname(buf, len, pos);
      
      if(!pos) return 0;
      
      pos = dns_packet_copy(buf, len, pos, header, 10);
      
      if(!pos) return 0;

      uint16_unpack_big(header + 8, &datalen);
      
      if(!memcmp(header, DNS_T_A, 2))
        if(!memcmp(&header[2], DNS_C_IN, 2))
          if(datalen == 4)
            {
              if(!dns_packet_copy(buf, len, pos, header, 4))
                return 0;
              
              if(count == n)
                {
                  out->s_addr = *(unsigned int *)header;
                  return 1;
                }
              
              count++;
            }
      
      pos += datalen;
    }

  return 0;
}

static int dns_event(struct dns_resolver *d)
{
  char udpbuf[513];
  unsigned char ch;
  int r;
  int fd = d->s1 - 1;

  if(d->tcpstate == 0)
    {
      /* have attempted to send UDP query to each server udploop times
         have sent query to curserver on UDP socket s
       */
      r = recv(fd, udpbuf, sizeof(udpbuf), 0);
      
      if(r <= 0)
        {
          if(errno == ECONNREFUSED) 
            if(d->udploop == 2) return 0;
          
          return nextudp(d);
        }
      
      if(r + 1 > sizeof(udpbuf)) return 0;
      
      if(irrelevant(d, udpbuf, r)) return 0;
      if(serverwantstcp(udpbuf, r)) return firsttcp(d);
      if(serverfailed(udpbuf, r))
        {
          if(d->udploop == 2) return 0;
          return nextudp(d);
        }
      
      socketfree(d);

      d->packetlen = r;
      d->packet = malloc(d->packetlen);
      if(!d->packet) { dns_free(d); return -1; }
      memcpy(d->packet, udpbuf, d->packetlen);
      queryfree(d);
      return 1;
    }
  
  if(d->tcpstate == 1)
    {
      /* have sent connection attempt to curserver on TCP socket s
         pos not defined
       */
      if(!socket_connected(fd)) return nexttcp(d);
      d->pos = 0;
      d->tcpstate = 2;
      return 0;
    }
  
  if(d->tcpstate == 2)
    {
      /* have connection to curserver on TCP socket s
         have sent pos bytes of query
       */
      r = write(fd, d->query + d->pos, d->querylen - d->pos);
      if(r <= 0) return nexttcp(d);
      d->pos += r;
      if(d->pos == d->querylen)
        {
          d->deadline = time(0) + 10;
          d->tcpstate = 3;
        }
      
      return 0;
    }

  if(d->tcpstate == 3)
    {
      /* have sent entire query to curserver on TCP socket s
         pos not defined
       */
      r = read(fd, &ch, 1);
      if(r <= 0) return nexttcp(d);
      d->packetlen = ch;
      d->tcpstate = 4;
      return 0;
    }

  if(d->tcpstate == 4)
    {
      /* have sent entire query to curserver on TCP socket s
         pos not defined have received one byte of packet
         length into packetlen
       */
      r = read(fd, &ch, 1);
      if(r <= 0) return nexttcp(d);
      d->packetlen <<= 8;
      d->packetlen += ch;
      d->tcpstate = 5;
      d->pos = 0;
      d->packet = malloc(d->packetlen);
      if(!d->packet) { dns_free(d); return -1; }
      return 0;
    }

  if(d->tcpstate == 5)
    {
      /* have sent entire query to curserver on TCP socket s
         have received entire packet length into packetlen
         packet is allocated have received pos bytes of packet
       */
      r = read(fd, d->packet + d->pos, d->packetlen - d->pos);
      if(r <= 0) return nexttcp(d);
      d->pos += r;
      if(d->pos < d->packetlen) return 0;

      socketfree(d);
      if(irrelevant(d, d->packet, d->packetlen)) return nexttcp(d);
      if(serverwantstcp(d->packet, d->packetlen)) return nexttcp(d);
      if(serverfailed(d->packet, d->packetlen)) return nexttcp(d);

      queryfree(d);
      return 1;
    }
  
  return 0;
}

/* The API functions */

int dns_name_lookup(struct dns_resolver *dns, const char *name)
{
  char domain[64];
  
  dns_domain_fromdot(domain, name, 64);
  
  if(dns_resolvconfip() == -1) return -1;
  
  if(dns_transmit_start(dns, ip, 1, domain, DNS_T_A, &in6addr_any) == -1)
    return -1;
  
  return 0;
}

int dns_addr_lookup(struct dns_resolver *dns, struct in_addr addr)
{
  char ptr[64];
  
  dns_name4_domain(ptr, &addr);
  
  if(dns_resolvconfip() == -1) return -1;
  
  if(dns_transmit_start(dns, ip, 1, ptr, DNS_T_PTR, &in6addr_any) == -1)
    return -1;
  
  return 0;
}

int dns_pre_poll(struct dns_resolver *dns, struct pollfd *pfd)
{
  if(dns->s1)
    {    
      pfd->fd = dns->s1 - 1;
      
      switch(dns->tcpstate)
        {
        case 0: case 3: case 4: case 5:
          pfd->events = POLLIN;
          break;
        case 1: case 2:
          pfd->events = POLLOUT;
          break;
        }
      
      return 1;
    }
  
  return 0;
}

int dns_pre_select(struct dns_resolver *dns, fd_set *readset, fd_set *writeset)
{
  int fd;
  
  if(dns->s1)
    {
      fd = dns->s1 - 1;
      
      switch(dns->tcpstate)
        {
        case 0: case 3: case 4: case 5:
          FD_SET(fd, readset);
          break;
        case 1: case 2:
          FD_SET(fd, writeset);
          break;
        }
      
      return 1;
    }
  
  return 0;
}

int dns_post_poll(struct dns_resolver *d, const struct pollfd *x)
{
  int fd;

  if(!d->s1) return 0;
  
  fd = d->s1 - 1;

  if(!x->revents)
    {
      if(time(0) < d->deadline) return 0;
      errno = ETIMEDOUT;
      if(d->tcpstate == 0) return nextudp(d);
      
      return nexttcp(d);
    }

  return dns_event(d);
}

int dns_post_select(struct dns_resolver *d, const fd_set *readset, const fd_set *writeset)
{
  int fd;
  
  if(!d->s1) return 0;
  
  fd = d->s1 - 1;
  
  if(!FD_ISSET(fd, readset) && !FD_ISSET(fd, writeset))
    {
      if(time(0) < d->deadline) return 0;
      errno = ETIMEDOUT;
      if(d->tcpstate == 0) return nextudp(d);
      
      return nexttcp(d);
    }
  
  return dns_event(d);
}

int dns_get_addr(struct dns_resolver *dns, struct in_addr *addr)
{
  return dns_ip4_packet(addr, dns->ans++, dns->packet, dns->packetlen);
}

int dns_get_name(struct dns_resolver *dns, char *out, size_t len)
{
  unsigned int pos;
  char header[12];
  unsigned short numanswers;
  unsigned short datalen;
  char name[256];

  pos = dns_packet_copy(dns->packet, dns->packetlen, 0, header,12);
  
  if(!pos)
    return -1;
  
  uint16_unpack_big(header + 6, &numanswers);
    
  pos = dns_packet_skipname(dns->packet, dns->packetlen, pos);
  
  if(!pos)
    return -1;
  
  pos += 4;

  while(numanswers--)
    {
      pos = dns_packet_skipname(dns->packet, dns->packetlen, pos);
      
      if(!pos)
        return -1;
      
      pos = dns_packet_copy(dns->packet, dns->packetlen, pos, header, 10);
      
      if(!pos)
        return -1;
      
      uint16_unpack_big(header + 8, &datalen);
      
      if(!memcmp(header, DNS_T_PTR, 2))
        if(!memcmp(header + 2, DNS_C_IN, 2))
          {
            if(!dns_packet_getname(dns->packet, dns->packetlen, pos, name, 255))
              return -1;
            
            if(!dns_domain_todot(out, name, len))
              return -1;
            
            return 1;
          }
      
      pos += datalen;
  }

  return 0;
}

void dns_free(struct dns_resolver *dns)
{
  queryfree(dns);
  socketfree(dns);
  packetfree(dns);
}

Generated by  Doxygen 1.6.0   Back to index