/*
 * Tentun - covert tunneling project
 *
 * Copyright (C) 2003 David Gordon
 *
 *      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 of the License, or
 *      (at your option) any later version.
 */

/*
 * IMPORTANT NOTICE: If this plugin receives more data than can fit in one
 * DNS packet, data will be fragmented and sent out in later packets.
 */


/* TODO:
 * Implement what is said in above commentary
 */

#include "config.h"
 
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include "tentun.h"
#include "plugins.h"

extern int debuglevel;


/* 
 * DNS plugin
 * Hides data in DNS request and reply
 * Limitations: maximum packet size of 63 bytes
 */

#define DNS_MAX_BUF             255     // Holds DNS message
#define DNS_MAX_READ		63	// Maximum number of bytes that
						// go in a single query.
#define DNS_Q_BEGIN_SIZE	12	// Header (Query).
#define DNS_Q_END_SIZE		4	// End (Query).
#define DNS_A_BEGIN_SIZE	12	// Same for Answer.
#define DNS_A_END_SIZE		20

const char DNS_Q_BEGIN_DATA[DNS_Q_BEGIN_SIZE] = {
    0x00, 0x00,			/* Query number */
    0x01, 0x00,			/* Flags */
    0x00, 0x01,			/* Questions */
    0x00, 0x00,			/* Awnser RR */
    0x00, 0x00,			/* Authority RR */
    0x00, 0x00,			/* Additional RR */
};

const char DNS_Q_END_DATA[DNS_Q_END_SIZE] = {
    0x00, 0x01,			/* Host address type */
    0x00, 0x01,			/* Inet class */
};


const char DNS_A_BEGIN_DATA[DNS_A_BEGIN_SIZE] = {
    0x00, 0x00,			/* Query number */
    0x85, 0x80,			/* Flags */
    0x00, 0x01,			/* Questions */
    0x00, 0x01,			/* Answer RR */
    0x00, 0x00,			/* Authority RR */
    0x00, 0x00			/* Additional RR */
};

const char DNS_A_END_DATA[DNS_A_END_SIZE] = {
    0x00, 0x01,			/* Host address type */
    0x00, 0x01,			/* Inet class */
    0xc0, 0x0c,			/* Name pointer (fixed) */
    0x00, 0x01,			/* Host address type */
    0x00, 0x01,			/* Inet class */
    0x00, 0x00,			/* TTL:            */
    0x09, 0x60,			/*      60 minutes */
    0x00, 0x04,			/* Data length (4) */
    0xd8, 0xef,			/* Google         */
    0x33, 0x64			/*        address */
};


/*
 * Internal function declarations
 */


int server_side;
static char *outgoing;
static char *incoming;

int dns_client_encode(int len, const char *source);
int dns_server_encode(int len, const char *source);
int dns_client_decode(int len, const char *source);
int dns_server_decode(int len, const char *source);


/* 
 * Initialization function.
 */
int dns_init(struct tentun_host *host)
{
  if (host->svr) server_side = 1;
  else server_side = 0;
  
  if( !(outgoing = (char *)plug_alloc(DNS_MAX_BUF)) ){
    TENTUN_PRINT("Can't allocate out buffer for DNS plugin\n");
    return 1;
  }

  if( !(incoming = (char *)plug_alloc(DNS_MAX_BUF)) ){
    TENTUN_PRINT("Can't allocate in buffer for DNS plugin\n");
    return 1;
  }

  memset(incoming, '\0', DNS_MAX_BUF);
  memset(outgoing, '\0', DNS_MAX_BUF);

  TENTUN_DEBUG(DEBUG_INIT, "DNS covert tunnel initialized\n");	
  return 0;
}




/* Encode data */
int dns_encode(int len, char *in, char **out)
{ 
  int final_len = 0;

  /* TODO: this should be enforced... */
  /*
  if (len > DNS_MAX_READ) {
    TENTUN_PRINT("Warning: losing some data in DNS encoding, data larger than %d\n",
		 DNS_MAX_READ);
    return -1;
  }
  */

  memcpy(outgoing, in, len);

  /*
  TENTUN_DEBUG(DEBUG_DNS, "dns encode new data: ");
  dump_buffer_ascii(DEBUG_DNS, in, len);
  dump_buffer_ascii(DEBUG_DNS, outgoing, len);
  */

  TENTUN_DEBUG(DEBUG_DNS,"server=%d\n",server_side);
  /* encode using server/client methods */
  if (server_side) final_len = dns_server_encode(len, in);
  else final_len = dns_client_encode(len, in);
  
  /* even if processing is not finished, length will be zero and no further */
  /* actions will occur at a higher level */
  *out = outgoing;

  TENTUN_DEBUG(DEBUG_DNS, "Original msg len=%d, msg encoded len=%d\n", len, final_len);
  return final_len;
}




int dns_decode(int len, char *in, char **out)
{ 
  int final_len = 0;

  /* TODO: this should be enforced... */
  /*
  if (len > DNS_MAX_READ) {
    TENTUN_PRINT("Warning: losing some data in DNS decoding, data larger than %d\n",
		 DNS_MAX_READ);
    return -1;
  }
  */

  memcpy(incoming, in, len);

  /*
  TENTUN_DEBUG(DEBUG_DNS, "dns decode new data: ");
  dump_buffer_ascii(DEBUG_DNS, in, len);
  dump_buffer_ascii(DEBUG_DNS, incoming, len);
  */

  /* decode using server/client methods */
  if (server_side) final_len = dns_server_decode(len, in);
  else final_len = dns_client_decode(len, in);

  /* even if processing is not finished, length will be zero and no further */
  /* actions will occur at a higher level */
  *out = incoming;

  TENTUN_DEBUG(DEBUG_DNS, "Original msg len=%d, msg decoded len=%d\n", len, final_len);
  return final_len;
}


int dns_free()
{
  plug_free(outgoing); outgoing = NULL;
  plug_free(incoming); incoming = NULL;
  return 0;
}


struct plug_mod plug_dns = {
  "DNS",
  dns_init,
  dns_encode,
  NULL,
  dns_decode,
  NULL,
  dns_free,
  NULL,
  NULL
};




/*******************
 * Utility functions
 *******************/

int dns_client_encode(int len, const char *source)
{
  int encoded_size;

  TENTUN_DEBUG(DEBUG_DNS, "Packet to be sent to server: ");
  dump_buffer_in_hex(DEBUG_DNS, source, len);
  encoded_size = len;

  memcpy(outgoing, DNS_Q_BEGIN_DATA, DNS_Q_BEGIN_SIZE );
  outgoing[DNS_Q_BEGIN_SIZE] = encoded_size;                /* insert data length */
  memcpy(outgoing + DNS_Q_BEGIN_SIZE + 1, source, encoded_size);  /* data */
  outgoing[DNS_Q_BEGIN_SIZE + encoded_size + 1] = 0;        /* insert a zero */
  memcpy(outgoing + DNS_Q_BEGIN_SIZE + encoded_size + 2, DNS_Q_END_DATA, DNS_Q_END_SIZE);

  TENTUN_DEBUG(DEBUG_DNS,"begin=%d encoded=%d end=%d\n'%s'\n",DNS_Q_BEGIN_SIZE,encoded_size,DNS_Q_END_SIZE,outgoing);
  return DNS_Q_BEGIN_SIZE + DNS_Q_END_SIZE + encoded_size + 2;   /* '2' is for data length and zero */
}



int dns_server_encode(int len, const char *source)
{
  int encoded_size;

  TENTUN_DEBUG(DEBUG_DNS, "Packet to be sent to server: ");
  dump_buffer_in_hex(DEBUG_DNS, source, len);
  encoded_size = len;

  memcpy(outgoing, DNS_A_BEGIN_DATA, DNS_A_BEGIN_SIZE );
  outgoing[DNS_A_BEGIN_SIZE] = encoded_size;                /* insert data length */
  memcpy(outgoing + DNS_A_BEGIN_SIZE + 1, source, encoded_size);  /* data */
  outgoing[DNS_A_BEGIN_SIZE + encoded_size + 1] = 0;        /* insert a zero */
  memcpy(outgoing + DNS_A_BEGIN_SIZE + encoded_size + 2, DNS_A_END_DATA, DNS_A_END_SIZE);

  TENTUN_DEBUG(DEBUG_DNS,"begin=%d encoded=%d end=%d\n'%s'\n",DNS_A_BEGIN_SIZE,encoded_size,DNS_A_END_SIZE,outgoing);
  return DNS_A_BEGIN_SIZE + DNS_A_END_SIZE + encoded_size + 2;   /* '2' is for data length and zero */
}


/* Decode data sent by server */
/* Method is to buffer data until entire message has been received */
/* Once all data is recovered and extracted, it will be placed in buffer incoming */
int dns_client_decode(int len, const char *source)
{
  int found = 0;
  int decoded_size;

    if (len > DNS_A_BEGIN_SIZE) {
	decoded_size = incoming[DNS_A_BEGIN_SIZE];

	if (len >= DNS_A_BEGIN_SIZE + decoded_size + 2 +
	    DNS_A_END_SIZE) {
	  /* Here must append data to current message being extracted 
	    seek_size = DNS_A_BEGIN_SIZE + decoded_size + 2 +
		DNS_A_END_SIZE - old_used_size;
	  */
	  found = 1;
	}
    }

  if (!found) {
    TENTUN_DEBUG(DEBUG_DNS, "DNS packet not part of covert tunnel\n");
    return -1;
  }

  /* Move the string to the beginning of the buffer */
  memmove(incoming, incoming + DNS_A_BEGIN_SIZE + 1, decoded_size);
  TENTUN_DEBUG(DEBUG_DNS, "Extracted packet: ");
  dump_buffer_in_hex(DEBUG_DNS, incoming, decoded_size);

  return decoded_size;
}


int dns_server_decode(int len, const char *source)
{
  int found = 0;
  int decoded_size;

    if (len > DNS_Q_BEGIN_SIZE) {
	decoded_size = incoming[DNS_Q_BEGIN_SIZE];

	if (len >= DNS_Q_BEGIN_SIZE + decoded_size + 2 +
	    DNS_Q_END_SIZE) {
	  /* Here must append data to current message being extracted 
	    seek_size = DNS_Q_BEGIN_SIZE + decoded_size + 2 +
		DNS_Q_END_SIZE - old_used_size;
	  */
	  found = 1;
	}
    }

  if (!found) {
    TENTUN_DEBUG(DEBUG_DNS, "DNS packet not part of covert tunnel\n");
    return -1;
  }

  /* Move the string to the beginning of the buffer */
  memmove(incoming, incoming + DNS_Q_BEGIN_SIZE + 1, decoded_size);
  TENTUN_DEBUG(DEBUG_DNS, "Extracted packet: ");
  dump_buffer_in_hex(DEBUG_DNS, incoming, decoded_size);

  return decoded_size;
}


