525 lines
20 KiB
C++
525 lines
20 KiB
C++
// Copyright (c) 2014, IOnU Security, Inc.
|
|
// Copyright (c) 2016, Sequence Logic, Inc. All rights reserved.
|
|
|
|
#include "syncandmessagedisp.h"
|
|
|
|
#include "jsonobject.h"
|
|
|
|
#include "../internal.h"
|
|
|
|
#include "websvcs.h"
|
|
#include "peerobserver.h"
|
|
#include "../component/slfile.h"
|
|
#include "../component/pubsubmessage.h"
|
|
#include "../component/storagenode.h"
|
|
#include "../cppcoreobjects/permissions.h"
|
|
|
|
#include "../../K2Client/platform_binding.h"
|
|
#include "../../K2Client/K2IPC.h"
|
|
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
|
|
#ifdef WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <Windows.h>
|
|
#endif
|
|
|
|
using namespace sequencelogic;
|
|
|
|
|
|
namespace
|
|
{
|
|
#ifdef WIN32
|
|
const char K2_LOGFILENAME[] = "slK2Samd";
|
|
char k2FileSuffix = '0';
|
|
unsigned int nK2SamdLogFileSize = 0;
|
|
int THREAD_ID_SIZE = 7;
|
|
|
|
std::ofstream k2LogStream;
|
|
std::mutex logFileMutex;
|
|
|
|
void closeK2SamdLog()
|
|
{
|
|
std::lock_guard<std::mutex> lock(logFileMutex);
|
|
if (k2LogStream.is_open())
|
|
{
|
|
k2FileSuffix = '0';
|
|
nK2SamdLogFileSize = 0;
|
|
k2LogStream.close();
|
|
}
|
|
}
|
|
|
|
void openK2SamdLog()
|
|
{
|
|
std::lock_guard<std::mutex> lock(logFileMutex);
|
|
if (k2LogStream.is_open())
|
|
k2LogStream.close();
|
|
|
|
// Open the file..
|
|
std::string logFilename;
|
|
|
|
// Get the path to the temp folder. Should be in "c:\Windows\Temp", unless this install of Windows is on
|
|
// a different drive..
|
|
logFilename.resize(MAX_PATH, 0);
|
|
DWORD nLen = ::GetTempPath(MAX_PATH, &logFilename[0]);
|
|
logFilename.resize(nLen);
|
|
logFilename += K2_LOGFILENAME;
|
|
if (k2FileSuffix > '0')
|
|
logFilename += k2FileSuffix;
|
|
logFilename += ".log";
|
|
++k2FileSuffix;
|
|
if (k2FileSuffix >= '4')
|
|
k2FileSuffix = '0';
|
|
k2LogStream.open(logFilename, std::ios_base::out | std::ios_base::trunc);
|
|
}
|
|
|
|
void logK2SamdMsg(const char *pFmt, ...)
|
|
{
|
|
static int nTimeLength = 21;
|
|
{
|
|
std::lock_guard<std::mutex> lock(logFileMutex);
|
|
if (k2LogStream.is_open())
|
|
{
|
|
EyeTime curTime;
|
|
::va_list args;
|
|
va_start(args, pFmt);
|
|
std::string formatMsg;
|
|
formatMsg.resize(2048);
|
|
int nNumChars = ::vsnprintf_s(&formatMsg[0], 2048, _TRUNCATE, pFmt, args);
|
|
if (nNumChars > 0)
|
|
{
|
|
int nThreadId = ::GetCurrentThreadId();
|
|
std::ostringstream os;
|
|
os << std::right << std::setw(THREAD_ID_SIZE) << nThreadId;
|
|
std::string strThreadId = os.str();
|
|
|
|
nK2SamdLogFileSize += nNumChars + nTimeLength + static_cast<int>(strThreadId.length()) + 1;
|
|
formatMsg.resize(nNumChars);
|
|
k2LogStream << strThreadId << " " << curTime.GetLocalISO8601Time() << ": " << formatMsg << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nK2SamdLogFileSize >= (5 * 1024 * 1024))
|
|
{
|
|
closeK2SamdLog();
|
|
openK2SamdLog();
|
|
}
|
|
}
|
|
|
|
struct CriticalSectionLocker
|
|
{
|
|
CriticalSectionLocker (CRITICAL_SECTION &cs) : _critSec(cs) { ::EnterCriticalSection(&_critSec); }
|
|
~CriticalSectionLocker() { ::LeaveCriticalSection(&_critSec); }
|
|
|
|
private:
|
|
CRITICAL_SECTION &_critSec;
|
|
};
|
|
#endif
|
|
|
|
class EmptyPeerObserver : public PeerObserver
|
|
{
|
|
public:
|
|
virtual std::string getWorkingDirectory() { return ""; }
|
|
virtual void onSubscribedMessageReceived(const std::string &) { }
|
|
virtual void onResponseSent(const std::string &) { }
|
|
virtual void onMessageDisposed(const std::string &) { }
|
|
virtual void onMessagePersisted(const std::string &) { }
|
|
virtual void onMessageRemoved(const std::string &) { }
|
|
virtual std::string automaticReplyToManualMessage(const std::string &, const std::string &) { return NULL; }
|
|
virtual void onInactivityTimeReached() { }
|
|
virtual void onSyncStart(const std::string &) { }
|
|
virtual void onSyncComplete(const std::string &) { }
|
|
virtual void onSyncError(const std::string &, const std::string &) { }
|
|
virtual void onSyncUploaded(const std::string &) { }
|
|
virtual void onSyncDownload(const std::string &) { }
|
|
virtual void onSyncDelete(const std::string &) { }
|
|
virtual bool onConnectionRestored() { return true; }
|
|
virtual void onFatalError(int, const std::string &) { }
|
|
virtual void onServiceException(const std::string &, const std::string &) { }
|
|
virtual void onSyncAndDispatchStart(const std::string &) { }
|
|
virtual void onSyncAndDispatchStop(const std::string &) { }
|
|
};
|
|
|
|
const int STAT_BUF_SIZE = 4096;
|
|
|
|
const char DEBUG_MSG[] = "SAMD: DEBUG: ";
|
|
const char INFO_MSG[] = "SAMD: INFO: ";
|
|
const char ERROR_MSG[] = "SAMD: ERROR: ";
|
|
}
|
|
|
|
const char PeerObserver::ACTION_DOWNLOAD_FILE[] = "download";
|
|
const char PeerObserver::ACTION_UPLOAD_FILE[] = "upload";
|
|
const char PeerObserver::ACTION_METADATA[] = "metadata";
|
|
const char PeerObserver::ACTION_DELETE_FILE[] = "delete";
|
|
|
|
SyncAndMessageDispatch::UrnToDispMap SyncAndMessageDispatch::_k2ClientMap;
|
|
|
|
SyncAndMessageDispatch::SyncAndMessageDispatch(const std::string &K2Host, const std::string &devUrn, PeerObserver *pObserver, WebSvcs *pWebSvc) :
|
|
_pPeerObserver(pObserver), _pWebSvc(pWebSvc), _k2Host(K2Host), _devURN(devUrn), _k2Good(false), _k2Error(false), _inactivityLogoutTime(0)
|
|
{
|
|
}
|
|
|
|
SyncAndMessageDispatch::~SyncAndMessageDispatch()
|
|
{
|
|
}
|
|
|
|
PeerObserver *SyncAndMessageDispatch::getInstanceObserver()
|
|
{
|
|
static EmptyPeerObserver emptyObserver;
|
|
if (_pPeerObserver == NULL)
|
|
return &emptyObserver;
|
|
else
|
|
return _pPeerObserver;
|
|
}
|
|
|
|
void SyncAndMessageDispatch::terminate()
|
|
{
|
|
// Logout the K2 client...
|
|
K2_Stop();
|
|
_k2Status = "terminated";
|
|
_k2Good = false;
|
|
_k2Error = false;
|
|
}
|
|
|
|
bool SyncAndMessageDispatch::k2Login()
|
|
{
|
|
K2_Start(_devURN.c_str(), _k2Host.c_str(), &k2DataReceived);
|
|
_k2ClientMap.insert(std::make_pair(_devURN, this));
|
|
|
|
_k2Status = "login";
|
|
|
|
#ifdef WIN32
|
|
openK2SamdLog();
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string SyncAndMessageDispatch::k2Statistics()
|
|
{
|
|
static char statBuf[STAT_BUF_SIZE];
|
|
statBuf[0] = '\0';
|
|
|
|
K2_GetStats(statBuf, STAT_BUF_SIZE);
|
|
|
|
return statBuf;
|
|
}
|
|
|
|
void SyncAndMessageDispatch::k2Logout()
|
|
{
|
|
K2_Stop();
|
|
_k2Status = "logout";
|
|
_k2Good = false;
|
|
_k2Error = false;
|
|
|
|
#ifdef WIN32
|
|
closeK2SamdLog();
|
|
#endif
|
|
|
|
_k2ClientMap.erase(_devURN);
|
|
}
|
|
|
|
void SyncAndMessageDispatch::k2StatusSelect(const std::string &offices)
|
|
{
|
|
K2_StatusSelect(offices.c_str());
|
|
}
|
|
|
|
void SyncAndMessageDispatch::k2DataReceived(const char *pK2Info)
|
|
{
|
|
if ((pK2Info != NULL) && (pK2Info[0] != '\0'))
|
|
{
|
|
// Look up proper sync/disp in map! For now, should only be one, until K2 is updated to support
|
|
// multiple clients.
|
|
#ifdef WIN32
|
|
logK2SamdMsg("K2 Data Received: %s", pK2Info);
|
|
#endif
|
|
|
|
if (!_k2ClientMap.empty())
|
|
{
|
|
SyncAndMessageDispatch *pThis = _k2ClientMap.begin()->second;
|
|
|
|
pThis->_k2Status = pK2Info;
|
|
pThis->_k2Status.erase(0, pThis->_k2Status.find(':')+1);
|
|
|
|
pThis->_k2Good = ( (::strncmp(pK2Info, K2_MESSAGE_PREFIX_FROM_CLOUD, sizeof(K2_MESSAGE_PREFIX_FROM_CLOUD)-1) == 0) ||
|
|
(::strncmp(pK2Info, K2_MESSAGE_PREFIX_DEVICE_STATUS, sizeof(K2_MESSAGE_PREFIX_DEVICE_STATUS)-1) == 0) );
|
|
if (::strncmp(pK2Info, K2_MESSAGE_PREFIX_INTERNAL, sizeof(K2_MESSAGE_PREFIX_INTERNAL)-1) == 0)
|
|
pThis->_k2Good |= ( (pThis->_k2Status.find("heartbeat") == 0) ||
|
|
(pThis->_k2Status.find("status") == 0) ||
|
|
(pThis->_k2Status.find("logged in") == 0) );
|
|
if (!pThis->_k2Error)
|
|
pThis->_k2Error = (pThis->_k2Status.find(K2_MESSAGE_2ND_PREFIX_FOR_ERRORS) == 0);
|
|
|
|
std::cout << DEBUG_MSG << __FILE__ << "(" << __LINE__ << "): k2DataReceived: handle: " << pK2Info <<
|
|
" with _instance " << pThis <<
|
|
" _k2Status: " << pThis->_k2Status <<
|
|
" _k2Good: " << ((pThis->_k2Good) ? "true" : "false") <<
|
|
" _k2Error: " << ((pThis->_k2Error) ? "true" : "false") << std::endl;
|
|
|
|
if (pThis->_k2Error && (pThis->_k2Status.find("heartbeat") == 0))
|
|
{
|
|
pThis->_k2Error = false;
|
|
std::cout << DEBUG_MSG << __FILE__ << "(" << __LINE__ << "): Invoking onConnectionRestored" << std::endl;
|
|
#ifdef WIN32
|
|
logK2SamdMsg("K2 reconnected...");
|
|
#endif
|
|
// We lost the K2 connection. Check for pubsub messages...
|
|
pThis->subscribeAndDispatch();
|
|
if ((pThis->getInstanceObserver() != NULL) && !pThis->getInstanceObserver()->onConnectionRestored())
|
|
return;
|
|
}
|
|
|
|
if (pThis->getInstanceObserver() != NULL)
|
|
{
|
|
PeerObserver *pObserver = pThis->getInstanceObserver();
|
|
|
|
if (::strncmp(pK2Info, K2_MESSAGE_PREFIX_FROM_CLOUD, sizeof(K2_MESSAGE_PREFIX_FROM_CLOUD)-1) == 0)
|
|
{
|
|
std::cout << DEBUG_MSG << __FILE__ << "(" << __LINE__ << "): Subscribing..." << std::endl;
|
|
pThis->subscribeAndDispatch();
|
|
// Hack! Using server version 1.2.8.5, k2 does not reliably notify
|
|
// all available events. Sweep up any pubsub messages still waiting.
|
|
// A Jira ticket will remind us to remove this once server logic is
|
|
// enhanced.
|
|
pThis->subscribeAndDispatch();
|
|
}
|
|
else if (::strncmp(pK2Info, K2_MESSAGE_PREFIX_INTERNAL, sizeof(K2_MESSAGE_PREFIX_INTERNAL)-1) == 0)
|
|
{
|
|
EyeTime curTime;
|
|
if (curTime.GetTimeMs() > pThis->getInactivityLogoutTime())
|
|
pObserver->onInactivityTimeReached();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string SyncAndMessageDispatch::getK2Status()
|
|
{
|
|
return _k2Status.c_str();
|
|
}
|
|
|
|
bool SyncAndMessageDispatch::isGoodK2Status()
|
|
{
|
|
return _k2Good;
|
|
}
|
|
|
|
long SyncAndMessageDispatch::getInactivityLogoutTime()
|
|
{
|
|
return _inactivityLogoutTime;
|
|
}
|
|
|
|
void SyncAndMessageDispatch::setInactivityLogoutTime(long nInactiveTime)
|
|
{
|
|
_inactivityLogoutTime = nInactiveTime;
|
|
}
|
|
|
|
void SyncAndMessageDispatch::subscribeAndDispatch()
|
|
{
|
|
if (_pPeerObserver != NULL)
|
|
_pPeerObserver->onSyncAndDispatchStart(_devURN.c_str());
|
|
|
|
if (_pWebSvc == NULL)
|
|
{
|
|
if (_pPeerObserver != NULL)
|
|
_pPeerObserver->onFatalError(PeerObserver::ERROR_CODES::ERR_NO_NETWORK_SVC, "No Web service interface provided for subscribe and dispatch");
|
|
}
|
|
else
|
|
{
|
|
#ifdef WIN32
|
|
// Limit access to this on a per thread basis...
|
|
static CRITICAL_SECTION sCS;
|
|
static bool bCSInited = false;
|
|
|
|
if (!bCSInited)
|
|
bCSInited = (::InitializeCriticalSectionAndSpinCount(&sCS, 0x00000400) == TRUE);
|
|
|
|
CriticalSectionLocker csLock(sCS);
|
|
#endif
|
|
|
|
// Invokes '/pubsub/subscribe' web service request
|
|
JSONObject msgsObj(_pWebSvc->getPubSubMessages());
|
|
if (msgsObj.gettype() == JSONObject::J_ARRAY)
|
|
{
|
|
#ifdef WIN32
|
|
logK2SamdMsg("SAMD got %d pubsub messages from Cloudguard.", msgsObj.getnumelements());
|
|
#endif
|
|
EyeTime lastMsgSync, lastGroupSync, lastCloudSync;
|
|
bool bDisMsgSync = false, bDidGroupSync = false, bDidCloudSync = false;
|
|
for (int i = 0; i < msgsObj.getnumelements(); ++i)
|
|
{
|
|
const JSONObject &msg = reinterpret_cast<const JSONObject &>(msgsObj[i]);
|
|
#ifdef WIN32
|
|
logK2SamdMsg("%s", msg.toString().c_str());
|
|
#endif
|
|
PubSubMessage pubSub (msg);
|
|
|
|
// Check to see if we can ignore this message. The parameters are:
|
|
// 1) Is this a SHARE?
|
|
// 2) Did we sync this mount yet?
|
|
// 3) If we did sync, is the time on this SHARE newer then the last sync time?
|
|
// We will process the SHARE if all of the above are true. We can ignore it, if (1) and (2) are true, but (3) is false.
|
|
EyeTime *pLastSync = NULL;
|
|
EyeTime msgTime;
|
|
bool bProcessMsg = true;
|
|
if (pubSub.getAction() == PubSubMessage::Action::SHARE)
|
|
{
|
|
// Check!
|
|
JSONObjectPtr pMsgData = pubSub.getActionData();
|
|
msgTime = pubSub.getIssueDate();
|
|
WebSvcs::MOUNT_TYPE msgMount = getMountType(pMsgData->getJSONString("mount"));
|
|
if ((msgMount == WebSvcs::MOUNT_TYPE::eMessages) && bDisMsgSync)
|
|
pLastSync = &lastMsgSync;
|
|
else if ((msgMount == WebSvcs::MOUNT_TYPE::eGroupShared) && bDidGroupSync)
|
|
pLastSync = &lastGroupSync;
|
|
else if ((msgMount == WebSvcs::MOUNT_TYPE::eSynchronized) && bDidCloudSync)
|
|
pLastSync = &lastCloudSync;
|
|
|
|
if (pLastSync != NULL)
|
|
{
|
|
pLastSync->SetTime(_pWebSvc->getLastSyncTime(msgMount).c_str());
|
|
if (msgTime.Compare(pLastSync) < 0)
|
|
bProcessMsg = false;
|
|
}
|
|
|
|
// At this point, we are either going to process the SHARE, because we haven't processed one for this
|
|
// mount yet. Or, we went through our checklist, and don't need to process (which means we had an initial
|
|
// SHARE processed already).
|
|
switch (msgMount)
|
|
{
|
|
case WebSvcs::MOUNT_TYPE::eMessages:
|
|
bDisMsgSync = true;
|
|
break;
|
|
case WebSvcs::MOUNT_TYPE::eGroupShared:
|
|
bDidGroupSync = true;
|
|
break;
|
|
case WebSvcs::MOUNT_TYPE::eSynchronized:
|
|
bDidCloudSync = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bProcessMsg)
|
|
{
|
|
doRequest(pubSub);
|
|
if (_pPeerObserver != NULL)
|
|
_pPeerObserver->onSubscribedMessageReceived(pubSub.toString().c_str());
|
|
}
|
|
disposeOf(pubSub);
|
|
}
|
|
}
|
|
if (_pPeerObserver != NULL)
|
|
_pPeerObserver->onSyncAndDispatchStop(_devURN.c_str());
|
|
}
|
|
}
|
|
|
|
void SyncAndMessageDispatch::disposeOf(const PubSubMessage &msg)
|
|
{
|
|
if (_pWebSvc == NULL)
|
|
{
|
|
std::cout << ERROR_MSG << __FILE__ << "(" << __LINE__ << "): No Web service provided!" << std::endl;
|
|
if (_pPeerObserver != NULL)
|
|
_pPeerObserver->onFatalError(PeerObserver::ERROR_CODES::ERR_NO_NETWORK_SVC, "No Web service interface provided for disposing of message");
|
|
}
|
|
else
|
|
{
|
|
_pWebSvc->disposePubSubMessage(msg.toString().c_str());
|
|
if (_pPeerObserver != NULL)
|
|
_pPeerObserver->onMessageDisposed(msg.toString().c_str());
|
|
}
|
|
}
|
|
|
|
WebSvcs::MOUNT_TYPE SyncAndMessageDispatch::getMountType (const std::string &mount)
|
|
{
|
|
WebSvcs::MOUNT_TYPE mountType = WebSvcs::eNone;
|
|
if (mount == _pWebSvc->getMount(WebSvcs::eMessages))
|
|
mountType = WebSvcs::eMessages;
|
|
else if (mount == _pWebSvc->getMount(WebSvcs::eOffice))
|
|
mountType = WebSvcs::eOffice;
|
|
else if (mount == _pWebSvc->getMount(WebSvcs::eSynchronized))
|
|
mountType = WebSvcs::eSynchronized;
|
|
else if (mount == _pWebSvc->getMount(WebSvcs::eGroupShared))
|
|
mountType = WebSvcs::eGroupShared;
|
|
return mountType;
|
|
}
|
|
|
|
void SyncAndMessageDispatch::doRequest(const PubSubMessage &msg)
|
|
{
|
|
if (_pWebSvc == NULL)
|
|
{
|
|
std::cout << ERROR_MSG << __FILE__ << "(" << __LINE__ << "): No Web service provided!" << std::endl;
|
|
if (_pPeerObserver != NULL)
|
|
_pPeerObserver->onFatalError(PeerObserver::ERROR_CODES::ERR_NO_NETWORK_SVC, "No Web service interface provided for message request");
|
|
}
|
|
else
|
|
{
|
|
PubSubMessagePtr pReply;
|
|
PubSubMessage::Action replyAction = msg.getReplyToAction();
|
|
|
|
if ((msg.getInReplyToMessageIdentifier() == "") && !msg.isExpired() && (replyAction != PubSubMessage::Action::NONE))
|
|
{
|
|
if (msg.isManualUserReplyRequired())
|
|
{
|
|
// This is to allow automatic responses.
|
|
if (_pPeerObserver != NULL)
|
|
{
|
|
std::string replyStr;
|
|
if (pReply != NULL)
|
|
replyStr = _pPeerObserver->automaticReplyToManualMessage(msg.toString().c_str(), pReply->toString().c_str());
|
|
else
|
|
replyStr = _pPeerObserver->automaticReplyToManualMessage(msg.toString().c_str(), "");
|
|
if (replyStr != "")
|
|
pReply.reset(new PubSubMessage(replyStr));
|
|
else
|
|
pReply.reset();
|
|
}
|
|
|
|
if (pReply == NULL)
|
|
{
|
|
// Cannot reply automatically. Perisist locally.
|
|
switch (msg.getAction())
|
|
{
|
|
case PubSubMessage::Action::SHARE:
|
|
{
|
|
JSONObjectPtr pMsgData = msg.getActionData();
|
|
if (pMsgData == NULL)
|
|
pMsgData.reset(new JSONObject());
|
|
|
|
std::string sig = pMsgData->getJSONString(Permissions::SIGNATURE_KEY, "unknown");
|
|
File someFile;
|
|
|
|
std::string nodeStr = _pWebSvc->createNode(pMsgData->toString());
|
|
_pWebSvc->syncNode(nodeStr);
|
|
someFile = _pWebSvc->getNodeAbsolutePath(nodeStr);
|
|
|
|
// Do a delta sync too.
|
|
StorageNode node(nodeStr);
|
|
node.setAbsPath("");
|
|
node.setPath("/");
|
|
node.setURN("");
|
|
node.setType(StorageNodeType::SNT_DEVICE_ROOT);
|
|
_pWebSvc->syncNode(node.toString());
|
|
|
|
if (_pPeerObserver != NULL)
|
|
_pPeerObserver->onMessagePersisted(msg.toString().c_str());
|
|
pReply.reset();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ((pReply != NULL) && pReply->isValid())
|
|
{
|
|
std::string resStr = _pWebSvc->sendPubSubMessage(pReply->toString().c_str());
|
|
if (_pPeerObserver != NULL)
|
|
_pPeerObserver->onResponseSent(resStr);
|
|
}
|
|
}
|
|
}
|
|
}
|