//
// C++ Implementation: c6proxysocket
//
// Description:
//
//
// Author: Giorgio A. <openc6@hotmail.com>, (C) 2005
//
// Copyright: See COPYING file that comes with this distribution
//
//

#define PROXY_SIG "C6PROXY:"

#include "c6proxysocket.h"
#include "c6utils.h"
#include "c6logger.h"
#include <qregexp.h>
#include <qhostaddress.h>

extern "C"
{
    #include <netdb.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
}

C6ProxySocket::C6ProxySocket(QObject *parent,bool isSock4,bool hasPw,const char* plogin,const char* ppsw) : QSocket(parent)
        ,_sockState (0)
        ,_isSocks4(isSock4)
        ,_bind(false)            
        ,_hasLogin(hasPw)            
{
    connect(this,SIGNAL(connected()),SLOT(socket_Connected()));

    if (plogin != 0)
    {
        _login.setLatin1(plogin);
    }

    if(ppsw != 0)
    {
        _password.setLatin1(ppsw);
    }
}

void C6ProxySocket::socket_Connected()
{
    C6Logger::getInstance().debugPrint(PROXY_SIG,"Connected socks4(%d)\n",_isSocks4);

    if (_isSocks4 == false)
    {
        unsigned char request[3];

        request[0] = (char)SOCKS_VERSION_V5;
        request[1] = 1;

        if (_hasLogin == true)
        {
            request[2] = (char)USERNAME_PASSWORD;
        }
        else
        {
            request[2] = (char)NO_AUTHENTICATION_REQUIRED;
        }

        writeBlock(reinterpret_cast<char*>(request),3);
        flush();
    }
    else
    {
        C6Logger::getInstance().debugPrint(PROXY_SIG,"Connecting to %s %d\n",_localIP.latin1(),_localPort);
        socketRequestV4();
    }
}

void C6ProxySocket::socket_ReadyRead()
{
    int avail = bytesAvailable();
    char *answer = new char [avail];

    readBlock(answer,avail);
    
    C6Utils::dumpPacket(PROXY_SIG,reinterpret_cast<unsigned char*>(answer),avail);    
    
    switch(_sockState)
    {
    case 0:
        {
            if (_isSocks4 == true)
            {
                if (answer[0] == 0)  // SUCCESS
                {
                    if (answer[1] == (char)SOCKSV4_REQUEST_GRANTED)
                    {
                        disconnect(this,SIGNAL(readyRead()),this,SLOT(socket_ReadyRead()));
                        emit proxyConnected();
                    }
                    else
                    {
                        abortConnection("No valid method found\n",METHOD_ERROR);
                        C6Utils::dumpPacket(PROXY_SIG,reinterpret_cast<unsigned char*>(answer),avail);
                    }
                }
                else
                {
                    abortConnection("Bad proxy answer or invalid socks version\n",BAD_ANSWER);
                    C6Utils::dumpPacket(PROXY_SIG,reinterpret_cast<unsigned char*>(answer),avail);
                }
            }
            else
            {
                if (answer[0] == (char)SOCKS_VERSION_V5)
                {
                    if (answer[1] == (char)NO_ACCEPTABLE_METHODS)
                    {
                        abortConnection("No valid method found\n",METHOD_ERROR);
                    }
                    else
                    {
                        if (_hasLogin == true)
                        {
                            sendLoginPassword();
                        }
                        else
                        {
                            socketRequestV5();
                        }
                    }
                }
                else
                {
                    abortConnection("Bad proxy answer or invalid socks version\n",BAD_ANSWER);
                    C6Utils::dumpPacket(PROXY_SIG,reinterpret_cast<unsigned char*>(answer),avail);
                }
            }
        }
        break;

    case 1:
        {
            if (_hasLogin == false)
            {
                if (answer[0] == (char)SOCKS_VERSION_V5)
                {
                    if (answer[1] == (char)SUCCESS)
                    {
                        disconnect(this,SIGNAL(readyRead()),this,SLOT(socket_ReadyRead()));                    
                        if (_bind == false)
                        {                         
                            emit proxyConnected();
                        }
                        else 
                        {
                            QString ip;     
                            ip = QString::number((unsigned char)answer[4])+"."+QString::number((unsigned char)answer[5])
                                    +"."+QString::number((unsigned char)answer[6])+"."+QString::number((unsigned char)answer[7]);   
                            
                            int port = (int)answer[8] << 8 | answer[9];       
                            emit proxyConnected(ip,port);
                        }                                                           
                    }
                    else
                    {
                        C6Utils::dumpPacket(PROXY_SIG,reinterpret_cast<unsigned char*>(answer),avail);
                        abortConnection("Bad proxy answer \n",BAD_ANSWER);
                    }
                }
                else
                {
                    C6Utils::dumpPacket(PROXY_SIG,reinterpret_cast<unsigned char*>(answer),avail);
                    abortConnection("Bad proxy answer or invalid socks version\n",BAD_ANSWER);
                }
            }
            else
            {
               if (answer[0] == (char)AUTH_NO)
                {
                    if (answer[1] == (char) AUTH_SUCCESS)
                    {
                        socketRequestV5();
                        _sockState--;                          // HACK
                        _hasLogin = false;                     // after auth , ordinary behavior
                    }
                    else
                    {
                        abortConnection("Authentication failed\n",BAD_AUTH);
                    }
                }
                else
                {
                    C6Utils::dumpPacket(PROXY_SIG,reinterpret_cast<unsigned char*>(answer),avail);
                    abortConnection("Bad proxy answer or invalid socks version\n",BAD_ANSWER);
                }
            }
        }
        break;
        case 2:
        {
                
            
        }
        break;                
    }

    _sockState++;

    delete [] answer;
}

void C6ProxySocket::socketRequestV4()
{
    int len = _login.length();
    unsigned char *request = new unsigned char[8+len+1];

    request[0] = SOCKS_VERSION_V4;
    request[1] = SOCKS4_CONNECT;
    request[2] = (_localPort & 0xff00) >> 8;
    request[3] = (_localPort & 0x00ff);

    struct hostent *host= gethostbyname(_localIP.latin1());

    if (host != 0)
    {
        _localIP.setLatin1(inet_ntoa(*(reinterpret_cast<in_addr*>(host->h_addr))));

        QHostAddress ip;
        ip.setAddress(_localIP);

        request[4] = *(host->h_addr);
        request[5] = *(host->h_addr+1);
        request[6] = *(host->h_addr+2);
        request[7] = *(host->h_addr+3);

        if (_hasLogin == true)
        {
            memcpy(&request[8],_login.latin1(),len);
            request[8+len]=0;
        }
        writeBlock(reinterpret_cast<char*>(request),8+len+1);
    }
    flush();

    delete [] request;
}

void C6ProxySocket::socketRequestV5()
{
    unsigned char *request = 0;
    QRegExp ipNumber("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b");
    int bytesToWrite = 0;

    if (_bind == false)
    {          
        C6Logger::getInstance().debugPrint(PROXY_SIG,"Connecting to %s %d\n",_localIP.latin1(),_localPort);
    }
    else         
    {
        C6Logger::getInstance().debugPrint(PROXY_SIG,"Binding to %s %d\n",_localIP.latin1(),_localPort);
    }      

    int pos = _localIP.length();
    int found = ipNumber.search(_localIP);

    if (found != -1)
    {
        request = new unsigned char[10];
    }
    else
    {
        request = new unsigned char[7+pos];
    }

    request[0] = SOCKS_VERSION_V5;
    request[1] = (_bind == false) ? CONNECT : BIND;
    request[2] = 0;
    request[3] = (found !=-1) ? (char)IP_V4 : (char)DOMAIN_NAME;

    if (found != -1)
    {
        QHostAddress ip;
        ip.setAddress(_localIP);
        
#if (QT_VERSION <= 0x030210)      
        struct hostent *host= gethostbyname(_localIP.latin1());
        if (host != 0)
        {         
            request[4] = *(host->h_addr);
            request[5] = *(host->h_addr+1);
            request[6] = *(host->h_addr+2);
            request[7] = *(host->h_addr+3);
        }
        else
        {
            return;
        }                     
#else
        request[4] = (ip.toIPv4Address() & 0xff000000) >> 24;
        request[5] = (ip.toIPv4Address() & 0x00ff0000) >> 16;
        request[6] = (ip.toIPv4Address() & 0x0000ff00) >> 8;
        request[7] = (ip.toIPv4Address() & 0x000000ff);
#endif         
        request[8] = (_localPort & 0xff00) >> 8;
        request[9] = (_localPort & 0x00ff);
        bytesToWrite = 10;
    }
    else
    {
        request[4] = pos;
        memcpy(&request[5],_localIP.latin1(),pos);
        request[5+pos] = (_localPort & 0xff00) >> 8;
        request[6+pos] = (_localPort & 0x00ff);
        bytesToWrite = 7 + pos;
    }

    writeBlock(reinterpret_cast<char*>(request),bytesToWrite);
    flush();

    delete [] request;
}

void C6ProxySocket::setDestConnection(QString const &ip,QString const &port,COMMANDS command)
{
    _localIP = ip.stripWhiteSpace();
    _localPort = port.toInt();
    _bind = (command == BIND) ? true : false;     
}

void C6ProxySocket::connectToHost(const QString & host, Q_UINT16 port)
{
    _sockState = 0;
    connect(this,SIGNAL(readyRead()),SLOT(socket_ReadyRead()));
    QSocket::connectToHost(host,port);
}

void C6ProxySocket::sendLoginPassword()
{
    int llogin = _login.length();
    int lpass = _password.length();
    int bytesToWrite = 3+llogin+lpass;
    unsigned char *buffer = new unsigned char[bytesToWrite];

    buffer[0] = AUTH_NO;
    buffer[1] = llogin;
    memcpy(&buffer[2],_login.latin1(),llogin);
    buffer[2+llogin]=lpass;
    memcpy(&buffer[2+llogin+1],_password.latin1(),lpass);

    writeBlock(reinterpret_cast<char*>(buffer),bytesToWrite);
    flush();

    delete [] buffer;
}

void C6ProxySocket::abortConnection(QString mess,SOCKET_ERROR err)
{
    C6Logger::getInstance().debugPrint(PROXY_SIG,mess.latin1());
    close();
    emit proxyError(err);
}

#include "c6proxysocket.moc"
