/* ----------------------------------------------------------------------------
 *
    VDE_OVER_NS 
	(C) 2007 Daniele Lacamera

    Derived from:
    NSTX -- tunneling network-packets over DNS

     (C) 2000 by Florian Heinz and Julien Oster

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2, as 
    published by the Free Software Foundation.

    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; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

  -------------------------------------------------------------------------- */
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <syslog.h>
#include <unistd.h>
#include <assert.h>
#include <stdio.h>

#include <config.h>
#include <vde.h>
#include <vdecommon.h>
#include "fun.h"
#include "dns.h"
#include "dns_proto.h"
#define min(a,b) a<b?a:b


/* lbl2str: Decodes a domainname to a string
 * <len1><NAME1><len2><NAME2><len3><NAME3>'\0' ->
 * NAME1.NAME2.NAME3                                
 * A lenght-byte may not be larger than 63 bytes (the two most significant
 * bits are reserved/used for compression */

/* Reverses lbl2str */

static const char *
str2lbl(const char *data)
{
   const char *ptr;
   char *ptr2, *buf;
   unsigned int buflen, chunklen;
   
   ptr = data;
   buf = NULL;
   buflen = 0;
   
   while ((ptr2 = strchr(ptr, '.'))) {
      chunklen = ptr2 - ptr;
      if ((chunklen > 63) || (chunklen <= 0)) {
	 DEBUG("Too long or zero-length label");
	 if (buf)
	   free(buf);
	 return NULL;
      }
      buf = realloc(buf, buflen + chunklen + 1);
      buf[buflen] = chunklen;
      memcpy(buf+buflen+1, ptr, chunklen);
      buflen += chunklen + 1;
      ptr = ptr2 + 1;
   }
   chunklen = strlen(ptr);
   buf = realloc(buf, buflen + chunklen + 2);
   buf[buflen] = chunklen;
   memcpy(buf+buflen+1, ptr, chunklen);
   buflen += chunklen+1;
   buf[buflen] = '\0';
   buflen++;
   
   return buf;
}

/* decompress_label decompresses the label pointed to by 'lbl' inside the
 * DNS-packet 'msg'. */

static char *
decompress_label(const char *msg, unsigned int msglen, const char *lbl)
{
   const char *ptr = lbl;
   char *buf;
   unsigned int chunklen, offset, buflen, followed = 0;
   
   buf = NULL;
   buflen = 0;
   
   while ((chunklen = *ptr)) {
      if (chunklen > 63) {
	 if ((ptr-msg) >= ((signed int)msglen-1)) {
	    DEBUG("Bad pointer at end of msg");
	    if (buf)
	      free(buf);
	    return NULL;
	 }	    
	 if (followed > 20) {
	    DEBUG("Too many pointer-loops");
	    if (buf)
	      free(buf);
	    return NULL;
	 }
	 offset = (chunklen % 64)*256 + *(ptr+1);
	 if (offset >= msglen) {
	    DEBUG("offset behind message");
	    if (buf)
	      free(buf);
	    return NULL;
	 }
	 ptr = msg + offset;
	 followed++;
      }
      else {
	 buf = realloc(buf, buflen + chunklen + 2);
	 if ((ptr + chunklen + 1) >= (msg + msglen)) {
	    DEBUG("Invalid chunklen");
	    if (buf)
	      free(buf);
	    return NULL;
	 }
	 memcpy(buf+buflen, ptr, chunklen + 1);
	 buflen += chunklen + 1;
	 ptr += chunklen + 1;
      }
   }
   if (buf) {
     buf[buflen] = 0;
     buflen++;
   }
   return buf;
}

static const unsigned char *
_cstringify(const unsigned char *data, int *dlen, unsigned int clen)
{
   static unsigned char *buf;
   
   const unsigned char *s = data;
   unsigned char *d;
   unsigned int llen, len;
   
   len = *dlen;
   *dlen = 0;
   
   d = buf = realloc(buf, len+len/clen+2);
   
   while (len > 0) {
      llen = (len > clen) ? clen : len;
      *(d++) = llen;
      memcpy(d, s, llen);
      d += llen;
      (*dlen) += llen + 1;
      s += llen;
      len -= llen;
   }
   *d = '\0';
   (*dlen)++;
   
   return buf;
}

static const unsigned char *
data2lbl (const unsigned char *data)
{
   int len;
   
   len = strlen((char*)data);
   return _cstringify(data, &len, 63);
}

const unsigned char *
data2txt (const unsigned char *data, int *len)
{
   return _cstringify(data, len, 255);
}

const unsigned char *
txt2data (const unsigned char *data, int *dlen)
{
   static unsigned char *buf;
   
   const unsigned char *s = data;
   unsigned char *d;
   unsigned int len, llen;
   
   len = *dlen;
   
   d = buf = realloc(buf, len);
   do
     {
	llen = *s++;
	if (llen > len - (s - data))
	  return NULL;
	memcpy(d, s, llen);
	s += llen;
	d += llen;
     } while (llen);
   
   *d = '\0';
   *dlen = d - buf;
   return buf;
}

static const unsigned char *
lbl2data (const unsigned char *data, size_t len)
{
   static signed char *buf = NULL;
   const unsigned char *s = data;
   signed char *d;
   signed int llen;
   
   d = buf = realloc(buf, len);
   assert(d);
   do
     {
	llen = *s++;
	if ((llen > 63) || (llen > (signed int)(len - (s - data))))
	  break;
	memcpy(d, s, llen);
	s += llen;
	d += llen;
     } while (llen);
   *d = '\0';
   return (const unsigned char*)buf;
}

/* New DNS-Code */

static struct rr *_new_listitem (struct rr **list)
{
   struct rr *rrp, *tmp;

   rrp = malloc(sizeof(struct rr));
   memset(rrp, 0, sizeof(struct rr));
   
   if (!*list)
      *list = rrp;
   else {
      for (tmp = *list; tmp->next; tmp = tmp->next)
	;
      tmp->next = rrp;
   }
   
   return rrp;
}

static const unsigned char *
_skip_lbl (const unsigned char *ptr, u_int16_t *len)
{
   while (*ptr) {
      if (*len < 2)
	return NULL;
      if ((*ptr & 0xc0)) {
	 ptr++;
	 (*len)--;
	 break;
      }
      *len -= *ptr;
      if (*len < 1)
	return NULL;
      ptr += *ptr+1;
   }
   
   ptr++;
   (*len)--;
   return ptr;
}
	   

static __inline__ int _get_listlen (const struct rr *list)
{
   int nr = 0;
   
   while (list) {
      list = list->next;
      nr++;
   }
   
   return nr;
}

static const char *suffix = NULL;
static int suffixlen = 0;

void dns_setsuffix (char *suf)
{
   suffix = str2lbl(suf);
   suffixlen = strlen(suf)+1;
}

struct dnspkt *dns_alloc (void)
{
   void *ptr;
   
   ptr = malloc(sizeof(struct dnspkt));
   memset(ptr, 0, sizeof(struct dnspkt));
   
   return ptr;
}

void dns_free (struct dnspkt *pkt)
{
   struct rr *list, *next;
   
   list = pkt->query;
   while (list) {
      if (list->data)
	free(list->data);
      next = list->next;
      free(list);
      list = next;
   }

   list = pkt->answer;
   while (list) {
      if (list->data)
	free(list->data);
      next = list->next;
      free(list);
      list = next;
   }
   
  free(pkt);
}

void dns_setid (struct dnspkt *pkt, unsigned short id)
{
   pkt->id = id;
}

void dns_settype (struct dnspkt *pkt, int type)
{
   pkt->type = type;
}

const char *
dns_data2fqdn (const char *data)
{
   const char *ptr;
   static char *fqdn;
   
   ptr = (char*)data2lbl((unsigned char*)data);
   fqdn = realloc(fqdn, strlen(ptr)+strlen(suffix)+1);
   strcpy(fqdn, ptr);
   strcat(fqdn, suffix);
   
   return fqdn;
}

const char *
dns_fqdn2data (const char *fqdn)
{
   static char	*buf;
   const char	*off;
   
   if (buf)
     free(buf);
   
   off = strstr(fqdn, suffix);
   /* only parse if the fqdn was found, and there is more than the fqdn */
   if (off && off != fqdn)
	buf = strdup((char*)lbl2data((unsigned char*)fqdn, off - fqdn));
   else
	/* Our suffix not found... */
  	buf = NULL; 

   return buf;
}

int
dns_addquery (struct dnspkt *pkt, const char *data)
{
   struct rr *rrp;

   rrp = _new_listitem(&pkt->query);
   rrp->data = strdup(data);
   rrp->len = strlen(data)+1;
   
   return _get_listlen(pkt->query) - 1;
}

int
dns_addanswer(struct dnspkt *pkt, const char *data, int len, int link)
{
   struct rr *rrp;
   const char *ptr;
   char *buf;
   
   ptr = (char*)data2txt((unsigned char*)data, &len);
   buf = malloc(len);
   memcpy(buf, ptr, len);
   
   rrp = _new_listitem(&pkt->answer);
   rrp->data = buf;
   rrp->len = len;
   rrp->link = link;
   
   return _get_listlen(pkt->query) - 1;
}

int
dns_getpktsize(const struct dnspkt *pkt)
{
   int size;
   struct rr *list;
   
   size = 12; /* DNS-header */
   
   for (list = pkt->query; list; list = list->next)
     size += list->len + 4;

   for (list = pkt->answer; list; list = list->next)
     size += list->len + 12;
   
   return size;
}

unsigned char *dns_constructpacket (struct dnspkt *pkt, int *l)
{
   static unsigned char *buf, *ptr;
   int len, *offsets, qdcount, i;
   struct rr *list;
   
   qdcount = _get_listlen(pkt->query);

   len = dns_getpktsize(pkt);
   ptr = buf = malloc(len);
   memset(buf, 0, len);
   
   if (len > 512)
     syslog(LOG_WARNING, "WARNING: Constructed non-conform DNS-packet (size: %d)\n", len);
   
   offsets = alloca(qdcount * 4);
   
   /* Header */
   buf[0] = pkt->id / 256;
   buf[1] = pkt->id % 256;
   if (pkt->type == DNS_RESPONSE) {
      buf[2] = 0x84; /* Flags: Response, Authoritative Answer */
      buf[3] = 0x80; /* Flag: Recursion available */
   } else
     buf[2] = 0x01; /* Flags: Recursion desired */
   buf[5] = qdcount;
   buf[7] = _get_listlen(pkt->answer);
   ptr += 12;
   
   /* Query section */
   for (list = pkt->query, i=0; list; list = list->next, i++) {
      offsets[i] = ptr-buf;
      memcpy(ptr, list->data, list->len);
      ptr += list->len;
      ptr[1] = 16;
      ptr[3] = 1;
      ptr += 4;
   }
   
   /* Answer section */
   for (list = pkt->answer; list; list = list->next) {
      ptr[0] = 0xc0 | (offsets[list->link]/256);
      ptr[1] = offsets[list->link]%256;
      ptr[3] = 16;
      ptr[5] = 1;
      ptr[10] = list->len / 256;
      ptr[11] = list->len % 256;
      ptr += 12;
      memcpy(ptr, list->data, list->len);
      ptr += list->len;
   }
   *l = len;
   dns_free (pkt);
   return buf;
}


struct dnspkt *
dns_extractpkt(const unsigned char *buf, int len)
{
   u_int16_t qdcount, ancount, remain, *offsets, i, j, off;
   const unsigned char *ptr;
   struct dnspkt *pkt;
   struct rr *rrp;
   struct ns_answer_header *nsh;
   
   if (len < 17)
     return NULL;
   
   pkt = dns_alloc();
   struct ns_transaction_header *nsth=
	   (struct ns_transaction_header*)buf;
   pkt->id = ntohs(nsth->tid);
   
   qdcount = ntohs(nsth->questions);
   ancount = ntohs(nsth->answers);
   offsets = malloc(qdcount * 4);
   
   ptr = buf + sizeof(struct ns_transaction_header);
   remain = len - sizeof(struct ns_transaction_header);
   
   i = 0;
   while (qdcount--)
     {
	offsets[i++] = ptr - buf;
	rrp = _new_listitem(&pkt->query);
	rrp->data = decompress_label((char*)buf, len, (char*)ptr);
	if (!rrp->data)
	  {
	     syslog(LOG_ERR, "dns_extractpkt: decompress_label choked in qd\n");
	     free(offsets);
	     dns_free(pkt);
	     return NULL;
	  }
	rrp->len = strlen(rrp->data)+1;
	ptr = _skip_lbl(ptr, &remain);
	if (!ptr)
	  {
	     syslog(LOG_ERR, "dns_extractpkt: _skip_lbl choked in qd\n");
	     free(offsets);
	     dns_free(pkt);
	     return NULL;
	  }
	ptr += 4;
	remain -= 4;
     }
   while (ancount--)
   {
	rrp = _new_listitem(&pkt->answer);
	rrp->link = -1;
	if ((ptr[0] & 0xc0) == 0xc0)
	  {
	     off = (ptr[0] & ~(0xc0)) * 256 + ptr[1];
	     for (j = 0; j < i; j++)
	       if (offsets[j] == off)
		 break;
	     if (j < i)
	       rrp->link = j;
	  }
	ptr=_skip_lbl(ptr,&remain);
	nsh=(struct ns_answer_header *)ptr;
	ptr+=sizeof(struct ns_answer_header);
	remain-=sizeof(struct ns_answer_header);
	//printf("REMAIN=%u, datalen=%u, type= 0x%02x, sizeof ns_answer_header=%u\n",remain,ntohs(nsh->datalen), ntohs(nsh->type),sizeof(struct ns_answer_header));
	if (ntohs(nsh->type) != NSTYPE_TXT){
		ptr+=ntohs(nsh->datalen);
		remain-=ntohs(nsh->datalen);
		continue;
	}
	rrp->len = ntohs(nsh->datalen);
	rrp->data = malloc(rrp->len);
	memcpy(rrp->data, ptr,rrp->len);
	remain -= rrp->len;
	ptr += rrp->len;
   }
   return(pkt);
}
	   /*
	    *
	    * old code:
	    *
	if (remain < 12)
	  {
	     syslog(LOG_ERR, "dns_extractpkt: too few bytes in an\n");
	     free(offsets);
	     dns_free(pkt);
	     return NULL;
	  }
	rrp = _new_listitem(&pkt->answer);
	rrp->link = -1;
	if ((ptr[0] & 0xc0) == 0xc0)
	  {
	     off = (ptr[0] & ~(0xc0)) * 256 + ptr[1];
	     for (j = 0; j < i; j++)
	       if (offsets[j] == off)
		 break;
	     if (j < i)
	       rrp->link = j;
	  }
		ptr = _skip_lbl(ptr, &remain);
		rrp->len = ptr[9]+((ptr[8])<<8);
		type=ptr[1];
	//	rrp->len = ptr[10]*256+ptr[11];
	ptr += 10;
	remain -= 13;
	//printf("Len: %u, remain: %u\n",rrp->len,remain);
	if (remain < rrp->len)
	  {
	     syslog(LOG_ERR, "dns_extractpkt: record too long in an (%d->%d)\n",
		    remain, rrp->len);
	     return(pkt);
	 //    dns_free(pkt);
	 //    return NULL;
	  }
	  if(type==0x10){
		rrp->data = malloc(rrp->len);
		memcpy(rrp->data, ptr,rrp->len);
		remain -= rrp->len;
	  }
	ptr += rrp->len;
   }
   return pkt;
}

*/

const char *
dns_getquerydata(struct dnspkt *pkt)
{
   struct rr *q;
   static char *ret = NULL;
   
   if (!pkt->query)
     return NULL;
   
   if (ret)
     {
	free(ret);
	ret = NULL;
     }
   
   q = pkt->query;
   pkt->query = pkt->query->next;

   ret = q->data;
   free(q);
   
   return ret;
}

char *dns_getanswerdata (struct dnspkt *pkt, int *len)
{
   struct rr *q;
   static char *ret = NULL;
   
   if (!pkt->answer)
     return NULL;
   
   q = pkt->answer;
   pkt->answer = pkt->answer->next;
   
   if (ret)
     free(ret);
   
   ret = q->data;
   *len = q->len;
   
   free(q);
   return ret;
}

int
dns_getfreespace(const struct dnspkt *pkt, int type)
{
   int raw, ret = 0, maxq;
   
   raw = DNS_MAXPKT - dns_getpktsize(pkt);
   
   if (raw < 0)
     return -1;
   
   if (type == DNS_RESPONSE) {
      ret = raw - 14;
      if (ret > 253)
	ret = 253;
   } else if (type == DNS_QUERY)
     {
//	ret = ((raw-suffixlen)*189-759)/256;
	ret = (189*(254-suffixlen))/256-6;
	if (ret > (maxq = (183-(189*suffixlen)/256)))
	  ret = maxq;
     }
   
   return (ret > 0) ? ret : 0;
}
