#include <memory.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <iostream>
//#include <string>
#include <openssl/ssl.h>
#include <unistd.h>

#include "fireflier_indep.h"
#define FF_DEBUG 0

using namespace std;

void setLabels(packet *p);
void view_source_received(char *source, int len);
void view_userspace_received(char *buffer, int len);
void view_iptables_received(char *buffer, int len);
void view_error(char *msg);

SSL*     ssl=0;
int authenticated=0;
int connected=0;

packet* scanpacket(unsigned char *buffer)
{
    packet *p=new packet();
    int i;

    p->packet_id=((unsigned long)buffer[0]<<24)+((unsigned long)buffer[1]<<16)+((unsigned long)buffer[2]<<8)+(unsigned long)buffer[3];
    p->arrived=((unsigned long)buffer[4]<<24)+((unsigned long)buffer[5]<<16)+((unsigned long)buffer[6]<<8)+(unsigned long)buffer[7];
    p->len=((unsigned short int)buffer[8]<<8)+(unsigned short int)buffer[9];
    p->ip_src=((unsigned long)buffer[13]<<24)+((unsigned long)buffer[12]<<16)+((unsigned long)buffer[11]<<8)+((unsigned long)buffer[10]);
    p->ip_dst=((unsigned long)buffer[17]<<24)+((unsigned long)buffer[16]<<16)+((unsigned long)buffer[15]<<8)+((unsigned long)buffer[14]);

    p->protocol=buffer[18];

    p->port_src=((unsigned short int)buffer[19]<<8)+(unsigned short int)buffer[20];
    p->port_dst=((unsigned short int)buffer[21]<<8)+buffer[22];

    p->tcp_flags=((unsigned short int)buffer[23]<<8)+(unsigned short int)buffer[24];
    p->icmp_type=buffer[25];

    p->hook=buffer[26];

    p->mac_addrlen=buffer[27];
    memcpy(p->mac_addr, (char*)buffer+28, 8);

    memcpy(p->interface_in, (char*)buffer+36, IFNAMSIZ);
    memcpy(p->interface_out, (char*)buffer+36+IFNAMSIZ, IFNAMSIZ);

    for (i=0;buffer[i+36+2*IFNAMSIZ]!=0;i++) ;
    p->programname=new char[i+1];
    if (buffer[36+2*IFNAMSIZ]>0)
	strcpy(p->programname, (char*)buffer+36+2*IFNAMSIZ+1);
    else
        strcpy(p->programname, "");

    return (p);
}

void sendCmd(int id, int ipsrc, int ipdst, int portsrc, int portdst, int intin, int intout, int macaddr, int protocol, int conntrack, int programname, int timeout, int action)
{
    char buffer[20];

    if (!authenticated) return;

    if (id==0) return; // should not happen
    buffer[0]=(action==0)?4:3;
    buffer[1]=0;
    buffer[2]=id;
    buffer[3]=ipsrc;
    buffer[4]=ipdst;
    buffer[5]=portsrc;
    buffer[6]=portdst;
    buffer[7]=intin;
    buffer[8]=intout;
    buffer[9]=macaddr;
    buffer[10]=protocol;
    buffer[11]=conntrack;
    buffer[12]=programname;
    buffer[13]=timeout>>24;
    buffer[14]=(timeout>>16)&255;
    buffer[15]=(timeout>>8)&255;
    buffer[16]=(timeout)&255;


    SSL_write(ssl, buffer, 17);
}

void sendSourceRequest()
{
    char buffer[2];
    buffer[0]=1;
    buffer[1]=0;
    if (!authenticated) return;
    SSL_write(ssl, buffer, 2);
}

void sendUSRulesRequest()
{
    char buffer[2];
    buffer[0]=6;
    buffer[1]=0;
    if (!authenticated) return;
    SSL_write(ssl, buffer, 2);
}

void sendIPTRulesRequest()
{
    char buffer[2];
    buffer[0]=2;
    buffer[1]=0;
    if (!authenticated) return;
    SSL_write(ssl, buffer, 2);
}

void sendUSdelCmd(int rulenum)
{
    char buffer[5];
    buffer[0]=7;
    buffer[1]=0;
    buffer[2]=0;
    buffer[3]=rulenum>>8;
    buffer[4]=rulenum&255;
    if (!authenticated) return;
    SSL_write(ssl, buffer, 5);
}

void sendIPTdelCmd(int chain, int rulenum)
{
    char buffer[5];
    buffer[0]=5;
    buffer[1]=0;
    buffer[2]=chain;
    buffer[3]=rulenum>>8;
    buffer[4]=rulenum&255;
    if (!authenticated) return;
    SSL_write(ssl, buffer, 5);
}

int sendLogin(const char *username, const char *password)
{
    char buffer[106];
    int len;
    if ((strlen(username)>50) || (strlen(password)>50))
        return 0;

    buffer[0]=8;
    buffer[1]=0;

    len=strlen(username);
    buffer[2]=len>>8;
    buffer[3]=len&255;
    memcpy(buffer+4, username, len);

    buffer[4+len]=strlen(password)>>8;
    buffer[5+len]=strlen(password)&255;
    memcpy(buffer+6+len, password, strlen(password));

    authenticated=-1; // auth in progress
    while (!connected)
        sleep(1);
    SSL_write(ssl, buffer, 6+len+strlen(password));

    return 0;
}

void *networkThread(void *clientParameter)
{
    int err;
    int sd;
    int i;
    packet *p=0;
    struct sockaddr_in sa;
    SSL_CTX* ctx;
//    char     buf [4096];
    SSL_METHOD *meth;
    char buffer[100];
    char msgtype;
    int msglen;
    char *msgbuf;
    unsigned char *param=(unsigned char*)clientParameter;

    connected=0;

    SSLeay_add_ssl_algorithms();
    meth = SSLv2_client_method();
    SSL_load_error_strings();
    ctx = SSL_CTX_new (meth);
    sd = socket (AF_INET, SOCK_STREAM, 0);     
 
    memset (&sa, '\0', sizeof(sa));
    sa.sin_family      = AF_INET;
    sprintf(buffer, "%d.%d.%d.%d", param[0], param[1], param[2], param[3]);
    sa.sin_addr.s_addr = inet_addr (buffer);   /* Server IP */
    sa.sin_port        = htons     (param[4]+(((int)(param[5]))<<8)); /* Server Port number */

    err = connect(sd, (struct sockaddr*) &sa, sizeof(sa));
    if (err==-1) return(0);
    ssl = SSL_new (ctx);
    if (!ssl) return(0);
    SSL_set_fd (ssl, sd);
    err = SSL_connect (ssl);
    if (err<=0) return(0);
/*
    // authenticate
    buffer[0]=8;
    buffer[1]=0;
    buffer[2]=8;
    buffer[3]=0;
    strcpy(buffer+4, "root");
    buffer[12]=8;
    buffer[13]=0;
    strcpy(buffer+14, "rootpwd");
    SSL_write(ssl, buffer, 22);
    SSL_read(ssl, buffer, 2);
    if ((buffer[0]!=8) || (buffer[1]!=1))
    {
	cout << "Authentication error" << endl;
        return 0;
    }
    authenticated=1;
    */
    connected=1;
    msgbuf=0;
    while (1)
    {
	if (msgbuf)
            delete(msgbuf);
        msgbuf=0;
	if (SSL_read(ssl, &msgtype, 1)<=0) // server died ?
	{
            if (FF_DEBUG)
		cout << "Got disconnected. " << endl;
            authenticated=0;
            connected=0;
	    return (0);
	}
	switch (msgtype)
	{
	case 0: // network packet
	    SSL_read(ssl, buffer, 2);
	    msglen=(unsigned char)buffer[0]*256+(unsigned char)buffer[1];
	    msgbuf=new char[msglen-3];
	    SSL_read(ssl, msgbuf, msglen-3);

            if (FF_DEBUG)
		for (i=0;i<msglen-3;i++)
		    cout << (unsigned int)(unsigned char)msgbuf[i] << " ";
            if (FF_DEBUG)
		cout << endl;

	    if (FF_DEBUG)
		cout << "Received message ... parsing" << endl;
	    if (msgbuf)
	    {
		p=scanpacket((unsigned char *)msgbuf);
		setLabels(p); // each interface should implement this one
	    }
	    break;
	case 1: // message source
	    msgbuf=new char[p->len+1];
            SSL_read(ssl, msgbuf, p->len);
	    view_source_received(msgbuf, p->len);
	    break;
	case 2:
	    SSL_read(ssl, buffer, 2);
	    msglen=(((unsigned char*)buffer)[0]<<8)+((unsigned char*)buffer)[1];
            if (FF_DEBUG)
		cout << "len=" << msglen << (int)((char*)buffer)[0] << " " << (int)((char*)buffer)[1] << endl;
	    if (msglen>0)
	    {
                msgbuf=new char[msglen];
		SSL_read(ssl, msgbuf, msglen);
                view_iptables_received(msgbuf, msglen);
	    }
            else
                view_iptables_received(0, 0);
            break;

	case 6:
	    SSL_read(ssl, buffer, 2);
	    msglen=(((unsigned char*)buffer)[0]<<8)+((unsigned char*)buffer)[1];
            if (FF_DEBUG)
		cout << "len=" << msglen << (int)((char*)buffer)[0] << " " << (int)((char*)buffer)[1] << endl;
	    if (msglen>0)
	    {
                msgbuf=new char[msglen];
		SSL_read(ssl, msgbuf, msglen);
                view_userspace_received(msgbuf, msglen);
	    }
            else
                view_userspace_received(0, 0);
	    break;
	case 8: // authenticate
		SSL_read(ssl, buffer, 1);
		authenticated=buffer[0];
		if (authenticated==2)
		{
			view_error("There is already a client logged in to the server.");
			connected=0;
			authenticated=0;
            return 0;
		}
		break;
	default:
	    cout << "Unknown message !!!" << endl;
            break;
	}

    }
    close (sd);
    SSL_free (ssl);
    SSL_CTX_free (ctx);
    return 0;
}
