/*
 * 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 does not receive all data expected in the
 * given message, a length of zero is returned and all further processing by
 * other stacked plugins will be halted. Once data is completely recovered,
 * this plugin must resend all data back through the 'out' buffer to other
 * plugins for final processing.
 */


/*
 * TODO:
 * - implement what is said in the comment above... data buffering for
 *   incoming (assume outgoing can all fit in one packet)
 */


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

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

extern int debuglevel;


/* 
 * Simple HTTP plugin
 * Hides data in URL header parameter mainly
 */

#define	HTTP_BUF_SIZE		5000

#define HTTP_BEGIN_STRING_C  	"GET /cgi-bin/db_query?param1=foo&param2=bar&encap_data="
#define HTTP_END_STRING_C	" HTTP/1.0\r\n"\
				"Connection: Keep-Alive\r\n"\
				"User-Agent: Mozilla (X11; I; Linux 2.0.32 i586)\r\n"\
				"Host: www.google.ca\r\n"\
				"Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*\r\n"\
				"Accept-Encoding: gzip\r\n"\
				"Accept-Language: en\r\n"\
				"Accept-Charset: iso-8859-1,*,utf-8\r\n"\
				"\r\n"

#define HTTP_BEGIN_STRING_S	"HTTP/1.1 200 OK\r\n"\
				"Server: Apache/1.3.12 (Unix) mod_perl/1.23^M\r\n"\
				"Accept-Ranges: bytes\r\n"\
				"Content-Length: "

#define HTTP_LENGTH_STRING_S	"%d\r\n"

#define HTTP_END_STRING_S	"Connection: close\r\n"\
				"Content-Type: image/jpeg\r\n"\
				"\r\n"


/*
 * Internal function declarations
 */


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

int url_decode(int size, char *source);
int url_encode(int size, const char *source, char *dest);

int http_client_encode(int len, const char *source);
int http_server_encode(int len, const char *source);
int http_client_decode(int len, const char *source);
int http_server_decode();
int http_client_get_header();
int http_client_get_data(int len);


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

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

  index_cdata = 0;
  memset(incoming, '\0', HTTP_BUF_SIZE);
  memset(outgoing, '\0', HTTP_BUF_SIZE);

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




/* Encode data into HTTP protocol */
int simple_http_encode(int len, char *in, char **out)
{ 
  int final_len = 0;

  if (len > HTTP_BUF_SIZE) {
    TENTUN_PRINT("Warning: losing some data in HTTP encoding (increase, HTTP_BUF_SIZE=%d)\n",
		 HTTP_BUF_SIZE);
    return -1;
  }

  memcpy(outgoing, in, len);

  /*
  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE, "http simple encode new data: ");
  dump_buffer_ascii(DEBUG_HTTP_SIMPLE, in, len);
  dump_buffer_ascii(DEBUG_HTTP_SIMPLE, outgoing, len);
  */

  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE,"server=%d\n",server_side);
  /* encode using server/client methods */
  if (server_side) final_len = http_server_encode(len, in);
  else final_len = http_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_HTTP_SIMPLE, "Original msg len=%d, msg encoded len=%d\n", len, final_len);
  return final_len;
}




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

  if (len > HTTP_BUF_SIZE) {
    TENTUN_PRINT("Warning: losing some data in HTTP decoding (increase, HTTP_BUF_SIZE=%d)\n",
		 HTTP_BUF_SIZE);
    return -1;
  }

  /* Append new data to buffer */
  if (index_cdata > HTTP_BUF_SIZE - len) {
    TENTUN_PRINT("HTTP message has exceeded buffer capacity in simple HTTP plugin\n");
    return -1;
  }

  index_cdata = 0;
  memcpy(incoming + index_cdata, in, len);
  /*index_cdata += len;*/
  index_cdata = len;

  /*
  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE, "http simple decode new data: ");
  dump_buffer_ascii(DEBUG_HTTP_SIMPLE, in, len);
  dump_buffer_ascii(DEBUG_HTTP_SIMPLE, incoming, index_cdata);
  */

  /* decode using server/client methods */
  if (server_side) final_len = http_server_decode();
  else final_len = http_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_HTTP_SIMPLE, "Original msg len=%d, msg decoded len=%d\n", len, final_len);
  return final_len;
}


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


struct plug_mod plug_simple_http = {
  "Simple HTTP",
  simple_http_init,
  simple_http_encode,
  NULL,
  simple_http_decode,
  NULL,
  simple_http_free,
  NULL,
  NULL
};




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

int url_decode(int size, char *source)
{
  // Return the size of the decoded string.
  int dest_index = 0, index;

  for (index = 0; index < size;) {
    switch (source[index]) {
    case '+':
      source[dest_index] = ' ';
      index += 1;
      break;

    case '%':
      source[index] = source[index + 1];
      source[index + 1] = source[index + 2];
      source[index + 2] = 0;

      source[dest_index] = (char) strtol(source + index, NULL, 16);
      index += 3;
      break;

    default:
      source[dest_index] = source[index];
      index += 1;
    }

    // We always create one character.
    dest_index += 1;
  }

  return dest_index;
}


int url_encode(int size, const char *source, char *dest)
{
  // Encode source as dest. At worst dest will be 3 times larger than 
  // source. Returns the used size.
  static const char *hex = "0123456789abcdef";
  int dest_size = 0, index;

  for (index = 0; index < size; index++) {
    unsigned char ch = source[index];

    if (ch == ' ') {
      dest[dest_size] = '+';
      dest_size++;
    }

    else if (isalnum(ch)
	     || ch == '.' || ch == '*' || ch == '-' || ch == '_') {
      dest[dest_size] = ch;
      dest_size++;
    }

    else {
      dest[dest_size + 0] = '%';
      dest[dest_size + 1] = hex[(ch >> 4) & 0xF];
      dest[dest_size + 2] = hex[ch % 16];
      dest_size += 3;
    }
  }

  return dest_size;
}



int http_client_encode(int len, const char *source)
{
  int encoded_size;
  int start_size = sizeof(HTTP_BEGIN_STRING_C) - 1;
  int end_size = sizeof(HTTP_END_STRING_C) - 1;

  /* Build the HTTP message */

  /* URL-encrypt the data. */
  /* if all data is special characters, URL encoding will triple the size */

  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE, "Packet to be sent to server: ");
  dump_buffer_in_hex(DEBUG_HTTP_SIMPLE, source, len);
  encoded_size = url_encode(len, source, outgoing+start_size );

  /* forge the header */
  /* data to encode is already at right place in the buffer */
  memcpy(outgoing, HTTP_BEGIN_STRING_C, start_size );
  memcpy(outgoing + start_size + encoded_size, HTTP_END_STRING_C, end_size);

  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE,"begin=%d encoded=%d end=%d\n'%s'\n",start_size,encoded_size,end_size,outgoing);
  return start_size + end_size + encoded_size;
}



int http_server_encode(int len, const char *source)
{
  int encoded_size, header_size;
  int begin_size = sizeof(HTTP_BEGIN_STRING_S) - 1;
  int end_size = sizeof(HTTP_END_STRING_S) - 1;
  const int size_first_part = begin_size + 15;
  char temp_http[ size_first_part ];

  /* Build the HTTP message */

  /* URL-encrypt the data. */
  /* if all data is special characters, URL encoding will triple the size */

  encoded_size = url_encode(len, source, outgoing);

  /* forge the header */
  memcpy(temp_http, HTTP_BEGIN_STRING_S, begin_size);
  header_size = begin_size;
  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE,"begin=%d ",header_size);

  header_size += sprintf(temp_http+begin_size, 
			 HTTP_LENGTH_STRING_S, 
			 encoded_size);  /* Content-length */
  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE,"with content length=%d content=%d",header_size,encoded_size);

  header_size += end_size;
  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE,"with end=%d ",header_size);

  /* move encoded data to leave room for header */
  memmove(outgoing + header_size, outgoing, encoded_size);

  if (size_first_part < header_size - end_size) {
    TENTUN_PRINT("Should never happen\n");
    return -1;
  }
  memcpy(outgoing, temp_http, header_size - end_size );
  memcpy(outgoing + header_size - end_size, HTTP_END_STRING_S, end_size);

  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE,"total=%d \n'%s'\n",header_size+encoded_size,outgoing);
  return header_size + encoded_size;
}


/* 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 http_client_decode(int len, const char *source)
{
  static int content_length = 0;
  int data_finished = 0;        /* will contain size of final encoded data once extracted */

  /* First, must obtain entire header to read Content-length in order */
  /* to know length of tunneled data */
  if (!content_length) {
    content_length = http_client_get_header();
  }

  if (content_length) {
    data_finished = http_client_get_data(content_length);
  }

  /* if we recovered all tunneled data, return length of data, */
  /* data is in buffer incoming */
  if (data_finished) {
    index_cdata = 0;
    content_length = 0;
    return data_finished;
  }

  /* else wait for more data */
  return 0;
}


int http_server_decode()
{
  int found = 0;
  int index;
  int begin_size = sizeof(HTTP_BEGIN_STRING_C) - 1;
  int end_size = sizeof(HTTP_END_STRING_C) - 1;
  int data_size, decoded_size;

  /* scan for '\r\n\r\n' which signifies end of header */
  for (index = 0; index < index_cdata - 3; index++) {
    if (incoming[index + 0] == '\r' && 
	incoming[index + 1] == '\n' && 
	incoming[index + 2] == '\r' && 
	incoming[index + 3] == '\n') {
      found = 1;
    }
  }

  /* need more data */
  if (!found) {
    TENTUN_DEBUG(DEBUG_HTTP_SIMPLE, "All encoded data not received from client yet\n");
    return 0;
  }

  /* we got the message, extract the data encapsulated */
  data_size = index_cdata - begin_size - end_size;

  /* url-decode the data */
  decoded_size = url_decode(data_size, incoming + begin_size);

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

  return decoded_size;
}


/* Client gets header from message sent by server */
/* Returns 0 if header not entirely sent yet */
/* Returns Content-length if entire header is read */
int http_client_get_header()
{
  int index, content_length, found = 0;
  const int begin_size = sizeof(HTTP_BEGIN_STRING_S) - 1;
  int data_start = 0;

  /* Data read must at least be size of expected header */
  if (index_cdata < begin_size) return 0;

  /* Scan for '\r\n\r\n', it is the 'tag' at end of header. */
  for (index = begin_size; index < index_cdata; index++) {
    if (incoming[index] == '\r' &&
        incoming[index + 1] == '\n' &&
	incoming[index + 2] == '\r' && 
	incoming[index + 3] == '\n') {
      found = 1;
      data_start = index + 4;
      break;
    }
  }

  if (!found)
    return 0;

  /* Get content length */
  content_length = atoi( &incoming[ begin_size ] );

  /* erase all header data (useless) by moving the data to */
  /* the beginning of the buffer */
  memmove(incoming, incoming + data_start, content_length);
  index_cdata -= data_start;

  if (index_cdata < 0) {
    TENTUN_PRINT("Attempting to erase header, but not enough data read.\nShouldn't happen\n");
    return -1;
  }

  TENTUN_DEBUG(DEBUG_HTTP_SIMPLE, "content length is: %d\n", content_length);
  return content_length;
}


/* Client extracts data from message sent by server */
/* Returns 0 if data not entirely sent yet */
/* Returns length of data extracted and puts data in incoming buffer */
int http_client_get_data(int len)
{
  int decoded_size;
  
  /* not enough data yet */
  if (index_cdata < len) {
    TENTUN_DEBUG(DEBUG_HTTP_SIMPLE, "Not enough data received\n");
    return 0;
  }

  /* url-decode the data */
  decoded_size = url_decode(len, incoming);

  return decoded_size;
}


