// This file is a part of the xMule Project.
// 
// Copyright (c) 2004 Theodore R. Smith (hopeseekr@xmule.ws / http://www.xmule.ws/)
// DSA-1024 Fingerprint: 10A0 6372 9092 85A2 BB7F 907B CB8B 654B E33B F1ED
// 
// Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.xmule-project.net )
// 
// 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.
// 
// 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.

#ifdef PRECOMP
    #include "xmule-headers.h"
#endif

#include "ClientCredits.h"                  // Needed for Module's Prototype(s) - audited 5 Nov 2004

#include "config.h"                         // Needed for HAVE_CRYPTO - audited 5 Nov 2004
#include "NewFunctions.h"                   // Needed for MAP - audited 5 Nov 2004
#include "opcodes.h"                        // Needed for CREDITFILE_VERSION - audited 4 Nov 2004
#include "otherfunctions.h"                 // Needed for md4cpy - audited 4 Nov 2004
#include "resource.h"                       // Needed for IDS_ERR_CREDITFILEOLD - audited 4 Nov 2004
#include "sockets.h"                        // Needed for CServerConnect - audited 5 Nov 2004
#include "wintypes.h"                       // Needed for BYTE - audited 5 Nov 2004
#include "xmule.h"                          // Needed for theApp - audited 4 Nov 2004
#include "xmuleDlg.h"                       // Needed for theApp.xmuledlg - audited 4 Nov 2004

#include <DynPrefs/DynPrefs.h>              // Needed for DynamicPreferences

#include <cmath>
#include <ctime>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <wx/utils.h>
#include <wx/file.h>                        // Needed for wxFile

#if !HAVE_CRYPTOPP
    #include "CryptoXMpp.h"
#else
    #include <cryptopp/config.h>
    #include <cryptopp/osrng.h>
    #include <cryptopp/base64.h>
    #include <cryptopp/files.h>
    #include <cryptopp/sha.h>
    #define CryptoXMpp CryptoPP
#endif

#define		CLIENTS_MET_FILENAME	"clients.met"

MAP * mcc = NULL;

uint32_t ldwForIP;

using namespace CryptoXMpp;

CClientCredits::CClientCredits(CreditStruct * in_credits)
{
    m_pCredits = in_credits;
    InitalizeIdent();
    m_dwUnSecureWaitTime = 0;
    m_dwSecureWaitTime = 0;
    m_dwWaitTimeIP = 0;
}

CClientCredits::CClientCredits(unsigned char * key)
{
    m_pCredits = new CreditStruct;
    memset(m_pCredits, 0, sizeof(CreditStruct));
    md4cpy(m_pCredits->abyKey, key);
    InitalizeIdent();
    m_dwUnSecureWaitTime =::GetTickCount();
    m_dwSecureWaitTime =::GetTickCount();
    m_dwWaitTimeIP = 0;
}

CClientCredits::~ CClientCredits()
{
    delete m_pCredits;
}

void CClientCredits::AddDownloaded(uint32_t bytes, uint32_t dwForIP)
{
    if ((GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY ||
    GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable())
    {
        return;
    }
    //encode
    uint64_t current = m_pCredits->nDownloadedHi;
    current = (current << 32) + m_pCredits->nDownloadedLo + bytes;
    //recode
    m_pCredits->nDownloadedLo = (uint32_t) current;
    m_pCredits->nDownloadedHi = (uint32_t)(current >> 32);
}

void CClientCredits::ShowCHandPK(uint32_t challenge, unsigned char * pk, int length)
{
    printf("    credits=%u  challenge=%u  length=%d  publickey=", (uint32_t) this, challenge, length);
    for (int i = 0 ; i < length ; i++)
    {
        if (i < 5 || i > length - 6)
        {
            printf("%02x", pk[i]);
        }
    }
    printf("\n");
}

void CClientCredits::AddUploaded(uint32_t bytes, uint32_t dwForIP)
{
    if ((GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDBADGUY || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable())
    {
        return;
    }
    else
    {
        uint64_t current = m_pCredits->nUploadedHi;
        current = (current << 32) + m_pCredits->nUploadedLo + bytes;
        m_pCredits->nUploadedLo = (uint32_t) current;
        m_pCredits->nUploadedHi = (uint32_t)(current >> 32);
    }
}

uint64_t CClientCredits::GetUploadedTotal()
{
    return((uint64_t) m_pCredits->nUploadedHi << 32) + m_pCredits->nUploadedLo;
}

uint64_t CClientCredits::GetDownloadedTotal()
{
    return((uint64_t) m_pCredits->nDownloadedHi << 32) + m_pCredits->nDownloadedLo;
}

float CClientCredits::GetScoreRatio(uint32_t dwForIP)
{
    // Check the client ident status
    if ((GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) &&
    theApp.clientcredits->CryptoAvailable() == true)
    {
        // bad guy - no credits for you
        return 1;
    }
    else
    {
        // Cache value
        const uint64_t downloadTotal = GetDownloadedTotal();
        // Check if this client has any credit (sent >1MB)
        if (downloadTotal < 1000000)
        {
            return 1.0f;
        }
        else
        {
            // Cache value
            const uint64_t uploadTotal = GetUploadedTotal();
            // Factor 1
            float result = (uploadTotal == 0) ?
            10.0f: (float)(2 * downloadTotal) / (float) uploadTotal;
            // Factor 2
            float trunk = (float) sqrt(2.0 + (double) downloadTotal/ 1048576.0);
            if (result > trunk)
            {
                result = trunk;
            }
            // Trunk final result 1..10
            if (result < 1.0f)
            {
                return 1.0f;
            }
            else if(result > 10.0f)
            {
                return 10.0f;
            }
            else
            {
                return result;
            }
        }
    }
}

// Maella end

// added by sivka [VQB: -ownCredits-]
float CClientCredits::GetMyScoreRatio(uint32_t dwForIP)
{
    // check the client ident status
    if ((GetCurrentIdentState(dwForIP) == IS_IDFAILED || GetCurrentIdentState(dwForIP) == IS_IDNEEDED) && theApp.clientcredits->CryptoAvailable())
    {
        // bad guy - no credits for...me?
        return 1;
    }
    if (GetUploadedTotal() < 1000000)
    return 1;
    float result = 0;
    if (!GetDownloadedTotal())
    {
        result = 10;
    }
    else
    {
        result = (float)(((double) GetUploadedTotal() * 2.0) / (double) GetDownloadedTotal());
    }
    float result2 = 0;
    result2 = (float) GetUploadedTotal() / 1048576.0;
    result2 += 2;
    result2 = (double) sqrt((double) result2);
    if (result > result2)
    result = result2;
    if (result < 1)
    return 1;
    else if(result > 10)
    return 10;
    return result;
}

CClientCreditsList::CClientCreditsList(CPreferences * in_prefs)
{
    MapData_Init(mcc);
    m_pAppPrefs = in_prefs;
    m_nLastSaved =::GetTickCount();
    LoadList();
    InitalizeCrypting();
    ldwForIP = 0;
}

CClientCreditsList::~ CClientCreditsList()
{
    SaveList();

    MAP * pos = mcc->next;
    while (pos->Key)
    {
        pos = MapData_Remove(mcc, pos);
    }
   
    if (m_pSignkey)
    {
        delete m_pSignkey;
        m_pSignkey = NULL;
    }
}

// VQB: ownCredits // Athlazan

void CClientCreditsList::LoadList()
{
    wxString strFileName(theApp.dynprefs->Get<wxString>("app-directory") + wxString(CLIENTS_MET_FILENAME, *wxConvCurrent));
    wxFile loadlist;
    
    if (loadlist.Open(strFileName, wxFile::read))
    {
        uint8_t version;
        loadlist.Read(&version, 1);

        if (version != CREDITFILE_VERSION && version != CREDITFILE_VERSION_29)
        {
            theApp.xmuledlg->AddLogLine(false, GetResString(IDS_ERR_CREDITFILEOLD));
            loadlist.Close();
        }
        else
        {
            struct stat loadlistbuf;
            fstat(loadlist.fd(), &loadlistbuf);
            wxString strBakFileName(strFileName + wxT(".BAK"));
            bool bCreateBackup = TRUE;
            wxFile hBakFile;

            if (hBakFile.Open(strBakFileName, wxFile::read))
            {
                struct stat hBakFilebuf;
                fstat(hBakFile.fd(), &hBakFilebuf);

                if (hBakFilebuf.st_size > loadlistbuf.st_size)
                {
                    bCreateBackup = FALSE;
                }

                hBakFile.Close();
            }

            if (bCreateBackup)
            {
                loadlist.Close();

                if (!SafeCopyFile(strFileName, strBakFileName))
                {
                    printf("info: Could not create backup file '%s'\n", strFileName.c_str());
                }

                if (loadlist.Open(strFileName, wxFile::read))
                {
                    loadlist.Read(&version, 1);
                }
            }

            uint32_t count;
            loadlist.Read(&count, 4);
            // TODO: should be prime number...and 20% larger
            const uint32_t dwExpired = time(NULL) - 12960000;
            // today - 150 day
            uint32_t cDeleted = 0;
            for (uint32_t i = 0 ; i < count ; i++)
            {
                CreditStruct * newcstruct = new CreditStruct;
                memset(newcstruct, 0, sizeof(CreditStruct));
                if (version == CREDITFILE_VERSION_29)
                {
                    loadlist.Read(newcstruct, sizeof(CreditStruct_29a));
                }
                else
                {
                    loadlist.Read(newcstruct, sizeof(CreditStruct));
                }
                if (newcstruct->nLastSeen < dwExpired)
                {
                    cDeleted++;
                    delete newcstruct;
                    continue;
                }
                CClientCredits * newcredits = new CClientCredits(newcstruct);
                MapData_Insert(mcc, newcredits, newcredits->GetKey(), 16);
            }
            loadlist.Close();
        }
    }
    else
    {
        theApp.xmuledlg->AddLogLine(true, GetResString(IDS_ERR_LOADCREDITFILE));
    }
}

#include <fstream>
using std::endl;
using std::ofstream;

void CClientCreditsList::SaveList()
{
    m_nLastSaved =::GetTickCount();
    wxString name(theApp.dynprefs->Get<wxString>("app-directory") + wxString(CLIENTS_MET_FILENAME, *wxConvCurrent));

    ofstream savelist;

    savelist.open(name.c_str(), ofstream::out | ofstream::binary);

    if (savelist && (savelist.good() == true))
    {
        BYTE* pBuffer = new BYTE[MapData_GetCount(mcc) * sizeof(CreditStruct)];
        uint32_t count = 0;
        CClientCredits* cur_client;
        MAP* pos;
        pos = mcc;

        do
        {
            pos = MapData_GetNext(pos);

            if (pos)
            {
                cur_client = static_cast<CClientCredits *>(MapData_GetIt(pos));

                if (cur_client)
                {
                    if (cur_client->GetUploadedTotal() || cur_client->GetDownloadedTotal())
                    {
                        memcpy(pBuffer + (count * sizeof(CreditStruct)), cur_client->GetDataStruct(),
                        sizeof(CreditStruct));
                        count++;
                    }
                }
            }
        }
        while (pos);

        wxUint8 version = CREDITFILE_VERSION;

        savelist.write(reinterpret_cast<char *>(&version), 1);
        savelist.write(reinterpret_cast<char *>(&count), sizeof(count));
        savelist.write(reinterpret_cast<char *>(pBuffer), sizeof(CreditStruct) * count);
        savelist.flush();
        savelist.close();

        delete[] pBuffer;
    }
}

CClientCredits * CClientCreditsList::GetCredit(unsigned char * key)
{
    CClientCredits * result;
    MAP * pos;
    pos = MapData_Find(mcc, key, 16);
    if (pos)
    {
        result = (CClientCredits *) MapData_GetIt(pos);
    }
    else
    {
        result = new CClientCredits(key);
        MapData_Insert(mcc, result, key, 16);
    }
    result->SetLastSeen();
    return result;
}

void CClientCreditsList::Process()
{
    if (::GetTickCount() - m_nLastSaved > 13 * 60000)
    {
        SaveList();
    }
}

void CClientCredits::InitalizeIdent()
{
    if (m_pCredits->nKeySize == 0)
    {
        memset(m_abyPublicKey, 0, 80);
        // for debugging
        m_nPublicKeyLen = 0;
        IdentState = IS_NOTAVAILABLE;
    }
    else
    {
        m_nPublicKeyLen = m_pCredits->nKeySize;
        memcpy(m_abyPublicKey, m_pCredits->abySecureIdent, m_nPublicKeyLen);
        IdentState = IS_IDNEEDED;
    }
    m_dwCryptRndChallengeFor = 0;
    m_dwCryptRndChallengeFrom = 0;
    m_dwIdentIP = 0;
}

void CClientCredits::Verified(uint32_t dwForIP)
{
    m_dwIdentIP = dwForIP;
    // client was verified, copy the keyto store him if not done already
    if (m_pCredits->nKeySize == 0)
    {
        m_pCredits->nKeySize = m_nPublicKeyLen;
        memcpy(m_pCredits->abySecureIdent, m_abyPublicKey, m_nPublicKeyLen);
        if (GetDownloadedTotal() > 0)
        {
            // for security reason, we have to delete all prior credits here
            m_pCredits->nDownloadedHi = 0;
            m_pCredits->nDownloadedLo = 1;
            m_pCredits->nUploadedHi = 0;
            m_pCredits->nUploadedLo = 1;
            // in order to safe this client, set 1 byte
        }
    }
    IdentState = IS_IDENTIFIED;
}

bool CClientCredits::SetSecureIdent(unsigned char * pachIdent, uint8_t nIdentLen)
{
    // verified Public key cannot change, use only if there is not public key yet
    if (MAXPUBKEYSIZE < nIdentLen || m_pCredits->nKeySize != 0)
    {
        return false;
    }
    else
    {
        memcpy(m_abyPublicKey, pachIdent, nIdentLen);
        m_nPublicKeyLen = nIdentLen;
        IdentState = IS_IDNEEDED;
        return true;
    }
}

EIdentState CClientCredits::GetCurrentIdentState(uint32_t dwForIP)
{
    if (ldwForIP != dwForIP)
    {
        ldwForIP = dwForIP;
    }
    if (IdentState != IS_IDENTIFIED)
    {
        return IdentState;
    }
    else if(dwForIP == m_dwIdentIP)
    {
        return IS_IDENTIFIED;
    }
    else
    {
        return IS_IDBADGUY;
        // mod note: clients which just reconnected after an IP change and have to ident yet will also have this state for 1-2 seconds
        // so don't try to spam such clients with "bad guy" messages (besides: spam messages are always bad)
    }
}

void CClientCreditsList::InitalizeCrypting()
{
    m_nMyPublicKeyLen = 0;
    memset(m_abyMyPublicKey, 0, 80);
    // not really needed; better for debugging tho
    m_pSignkey = NULL;
    if (theApp.dynprefs->Get<bool>("secure-ident") == true)
    {
        bool bCreateNewKey = false;
        if (theApp.dynprefs->Get<bool>("renew-priv-key") == true)
        {
            bCreateNewKey = true;
        }
        else
        {
            FILE * hKeyFile = NULL;
            hKeyFile = fopen(static_cast<const char *>(wxString((theApp.dynprefs->Get<wxString>("app-directory") + wxT("cryptkey.dat"))).c_str()), "r");
            if (hKeyFile != NULL)
            {
                (void) fclose(hKeyFile);
            }
            else
            {
                bCreateNewKey = true;
            }
        }
        if (bCreateNewKey)
        {
            printf("Creating new Cryptkey.dat ... %08x %08x\n", static_cast<unsigned int>(time(NULL)), static_cast<unsigned int>(time(NULL)));
            for (int i = 0 ; i < (unsigned char) time(NULL) ; i++)
            {
                (void) rand();
            }
            CreateKeyPair();
        }
        printf("boo\n");
        char erg[500];
        sprintf(erg, "%s/.xMule/%s", getenv("HOME"), "cryptkey.dat");
        FileSource filesource(erg, true, new Base64Decoder);
        m_pSignkey = new RSASSA_PKCS1v15_SHA_Signer(filesource);
        // calculate and store public key
        RSASSA_PKCS1v15_SHA_Verifier pubkey(*m_pSignkey);
        ArraySink asink(m_abyMyPublicKey, 80);
        pubkey.DEREncode(asink);
        m_nMyPublicKeyLen = asink.TotalPutLength();
        asink.MessageEnd();
        printf("PublicKey: keylen=%u\n", m_nMyPublicKeyLen);
        for (int i = 0 ; i < m_nMyPublicKeyLen ; i++)
        {
            if ((i >= 12) && (i < 28 || i >= (m_nMyPublicKeyLen - 16)))
            {
                printf("%02x ", m_abyMyPublicKey[i]);
            }
        }
        printf("\n");
    }
}

void CClientCreditsList::CreateKeyPair()
{
    AutoSeededRandomPool rng;
    InvertibleRSAFunction privkey;
    privkey.Initialize(rng, 384);
    Base64Encoder privkeysink(new FileSink(wxString(theApp.dynprefs->Get<wxString>("app-directory") + wxT("cryptkey.dat"))));
    privkey.DEREncode(privkeysink);
    privkeysink.MessageEnd();
}

uint8_t CClientCreditsList::CreateSignature(CClientCredits * pTarget, unsigned char * pachOutput, uint8_t nMaxSize, uint32_t ChallengeIP, uint8_t byChaIPKind)
{
    RSASSA_PKCS1v15_SHA_Signer * sigkey = NULL;
    StringSource ss_Pubkey((byte *) pTarget->GetSecureIdent(), pTarget->GetSecIDKeyLen(), true, 0);
    RSASSA_PKCS1v15_SHA_Verifier pubkey(ss_Pubkey);
    sigkey = m_pSignkey;
    // create a signature of the public key from pTarget
    SecByteBlock sbbSignature(sigkey->SignatureLength());
    AutoSeededRandomPool rng;
    byte abyBuffer[MAXPUBKEYSIZE + 9];
    uint32_t keylen = pTarget->GetSecIDKeyLen();
    memcpy(abyBuffer, pTarget->GetSecureIdent(), keylen);
    // 4 additional bytes random data send from this client
    uint32_t challenge = pTarget->m_dwCryptRndChallengeFrom;
    memcpy(abyBuffer + keylen, & challenge, 4);
    uint16_t ChIpLen = 4;
    if (byChaIPKind != 0)
    {
        memcpy(abyBuffer + keylen + 4, & ChallengeIP, 4);
        memcpy(abyBuffer + keylen + 4 + 4, & byChaIPKind, 1);
        ChIpLen += 5;
    }
    for (unsigned int i = 0 ; i < sbbSignature.size() ; i++)
    {
        sbbSignature.begin() [i] = 0;
    }
    sigkey->SignMessage(rng, abyBuffer, keylen + ChIpLen, sbbSignature.begin());
    if (nMaxSize > sbbSignature.size())
    {
        nMaxSize = sbbSignature.size();
    }
    for (int i = 0 ; i < nMaxSize ; i++)
    {
        pachOutput[i] = ((unsigned char *) sbbSignature.begin()) [i];
    }
    return(uint8_t) nMaxSize;
}

bool CClientCreditsList::VerifyIdent(CClientCredits * pTarget, unsigned char * pachSignature, uint8_t nInputSize, uint32_t dwForIP, uint8_t byChaIPKind)
{
    bool bResult;
    StringSource ss_Pubkey((byte *) pTarget->GetSecureIdent(), pTarget->GetSecIDKeyLen(), true, 0);
    RSASSA_PKCS1v15_SHA_Verifier pubkey(ss_Pubkey);
    // 4 additional bytes random data send from this client +5 bytes v2
    byte abyBuffer[MAXPUBKEYSIZE + 9];
    memcpy(abyBuffer, m_abyMyPublicKey, m_nMyPublicKeyLen);
    uint32_t challenge = pTarget->m_dwCryptRndChallengeFor;
    memcpy(abyBuffer + m_nMyPublicKeyLen, & challenge, 4);
    // v2 security improvments (not supported by 29b, not used as default by 29c)
    uint8_t nChIpSize = 0;
    if (byChaIPKind != 0)
    {
        nChIpSize = 5;
        uint32_t ChallengeIP = 0;
        switch (byChaIPKind)
        {
        case CRYPT_CIP_LOCALCLIENT:
            ChallengeIP = dwForIP;
            break;
        case CRYPT_CIP_REMOTECLIENT:
            if ((theApp.serverconnect->GetClientID() == 0) || theApp.serverconnect->IsLowID())
            {
                ChallengeIP = theApp.serverconnect->GetLocalIP();
            }
            else
            {
                ChallengeIP = theApp.serverconnect->GetClientID();
            }
            break;
            // maybe not supported in future versions:
        case CRYPT_CIP_NONECLIENT:
            ChallengeIP = 0;
            break;
        }
        memcpy(abyBuffer + m_nMyPublicKeyLen + 4, & ChallengeIP, 4);
        memcpy(abyBuffer + m_nMyPublicKeyLen + 4 + 4, & byChaIPKind, 1);
    }
    //v2 end
    bResult = pubkey.VerifyMessage(abyBuffer, m_nMyPublicKeyLen + 4 + nChIpSize, pachSignature, nInputSize);
    if (!bResult)
    {
        if (pTarget->IdentState == IS_IDNEEDED)
        pTarget->IdentState = IS_IDFAILED;
    }
    else
    {
        pTarget->Verified(dwForIP);
    }
    return bResult;
}

bool CClientCreditsList::CryptoAvailable()
{
    return(m_nMyPublicKeyLen > 0 && m_pSignkey != 0 && theApp.dynprefs->Get<bool>("secure-ident"));
}

uint32_t CClientCredits::GetSecureWaitStartTime(uint32_t dwForIP)
{
    if (m_dwUnSecureWaitTime == 0 || m_dwSecureWaitTime == 0)
    SetSecWaitStartTime(dwForIP);
    if (m_pCredits->nKeySize != 0)
    {
        // this client is a SecureHash Client
        if (GetCurrentIdentState(dwForIP) == IS_IDENTIFIED)
        {
            // good boy
            return m_dwSecureWaitTime;
        }
        else
        {
            // not so good boy
            if (dwForIP == m_dwWaitTimeIP)
            {
                return m_dwUnSecureWaitTime;
            }
            else
            {
                // bad boy
                // this can also happen if the client has not identified himself yet, but will do later - so maybe he is not a bad boy :) .
                m_dwUnSecureWaitTime =::GetTickCount();
                m_dwWaitTimeIP = dwForIP;
                return m_dwUnSecureWaitTime;
            }
        }
    }
    else
    {
        // not a SecureHash Client - handle it like before for now (no security checks)
        return m_dwUnSecureWaitTime;
    }
}

void CClientCredits::SetSecWaitStartTime(uint32_t dwForIP)
{
    m_dwUnSecureWaitTime =::GetTickCount() - 1;
    m_dwSecureWaitTime =::GetTickCount() - 1;
    m_dwWaitTimeIP = dwForIP;
}

void CClientCredits::ClearWaitStartTime()
{
    m_dwUnSecureWaitTime = 0;
    m_dwSecureWaitTime = 0;
}

