/*++ Program name: dm Module Name: WebService.cpp Notices: WebService - Deal Module Web Service Author: Copyright (c) Prepodobny Alen mailto: alienufo@inbox.ru mailto: ufocomp@gmail.com --*/ //---------------------------------------------------------------------------------------------------------------------- #include "Core.hpp" #include "WebService.hpp" //---------------------------------------------------------------------------------------------------------------------- #define BPS_PGP_HASH "SHA512" #define BM_PREFIX "BM-" //---------------------------------------------------------------------------------------------------------------------- #define SYSTEM_PROVIDER_NAME "system" #define SERVICE_APPLICATION_NAME "service" #define CONFIG_SECTION_NAME "module" //---------------------------------------------------------------------------------------------------------------------- extern "C++" { namespace Apostol { namespace Module { //-------------------------------------------------------------------------------------------------------------- //-- CWebService ----------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------------- CWebService::CWebService(CModuleProcess *AProcess): CApostolModule(AProcess, "web service", "module/WebService") { m_NexServerTime = 0; m_SyncPeriod = BPS_DEFAULT_SYNC_PERIOD; m_Status = psStopped; CWebService::InitMethods(); } //-------------------------------------------------------------------------------------------------------------- void CWebService::InitMethods() { #if defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 9) m_pMethods->AddObject(_T("GET") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoGet(Connection); })); m_pMethods->AddObject(_T("POST") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoPost(Connection); })); m_pMethods->AddObject(_T("HEAD") , (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoHead(Connection); })); m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true , [this](auto && Connection) { DoOptions(Connection); })); m_pMethods->AddObject(_T("PUT") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); m_pMethods->AddObject(_T("DELETE") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); m_pMethods->AddObject(_T("TRACE") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); m_pMethods->AddObject(_T("PATCH") , (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, [this](auto && Connection) { MethodNotAllowed(Connection); })); #else m_pMethods->AddObject(_T("GET"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoGet, this, _1))); m_pMethods->AddObject(_T("POST"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoPost, this, _1))); m_pMethods->AddObject(_T("HEAD"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoHead, this, _1))); m_pMethods->AddObject(_T("OPTIONS"), (CObject *) new CMethodHandler(true, std::bind(&CWebService::DoOptions, this, _1))); m_pMethods->AddObject(_T("PUT"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); m_pMethods->AddObject(_T("DELETE"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); m_pMethods->AddObject(_T("TRACE"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); m_pMethods->AddObject(_T("PATCH"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); m_pMethods->AddObject(_T("CONNECT"), (CObject *) new CMethodHandler(false, std::bind(&CWebService::MethodNotAllowed, this, _1))); #endif } //-------------------------------------------------------------------------------------------------------------- void CWebService::InitServerList() { m_DefaultServer.Value().Name() = m_DefaultServer.Name(); m_DefaultServer.Value().PGP().Name = "PUBLIC"; if (m_Servers.Count() == 0) { #ifdef _DEBUG int index = m_Servers.AddPair(BPS_BM_DEBUG_ADDRESS, CContext(CLocation(BPS_SERVER_URL))); m_Servers[index].Value().Name() = m_Servers[index].Name(); m_Servers[index].Value().PGP().Name = "PUBLIC"; #else m_Servers.Add(m_DefaultServer); #endif } } //-------------------------------------------------------------------------------------------------------------- void CWebService::UpdateServerList(const CString &Key) { CStringPairs ServerList; CStringList Keys; ParsePGPKey(Key, ServerList, Keys); if (ServerList.Count() != 0) { CStringPairs::ConstEnumerator em(ServerList); while (em.MoveNext()) { const auto ¤t = em.Current(); if (m_Servers.IndexOfName(current.Name()) == -1 && !current.Value().IsEmpty()) { m_Servers.AddPair(current.Name(), CContext(CLocation(current.Value()))); auto &Context = m_Servers.Last().Value(); Context.Name() = current.Name(); Context.PGP().Name = "PUBLIC"; Context.PGP().Key = Key; Context.BTCKeys() = Keys; } } } } //-------------------------------------------------------------------------------------------------------------- void CWebService::CheckKeyForNull(LPCTSTR Key, LPCTSTR Value) { if (Value == nullptr) throw ExceptionFrm("Invalid format: key \"%s\" cannot be empty.", Key); } //-------------------------------------------------------------------------------------------------------------- void CWebService::UpdateOAuth2(CContext &Context, const CJSONObject &OAuth2) { if (OAuth2["type"].AsString() == "service_account") { UpdateProviders(Context.Providers(), OAuth2); Context.SetCheckDate(0); Context.SetStatus(Context::csInitialized); } } //-------------------------------------------------------------------------------------------------------------- void CWebService::UpdateProviders(CProviders &Providers, const CJSONObject &Data) { const auto &caProviderName = CString(SYSTEM_PROVIDER_NAME); const auto &caApplicationName = CString(SERVICE_APPLICATION_NAME); int index = Providers.IndexOfName(caProviderName); if (index == -1) index = Providers.AddPair(caProviderName, CProvider(caProviderName)); auto& Provider = Providers[index].Value(); Provider.Applications().AddPair(caApplicationName, Data); } //-------------------------------------------------------------------------------------------------------------- bool CWebService::FindURLInLine(const CString &Line, CStringList &List) { CString URL; TCHAR ch; int length = 0; size_t startPos, pos; pos = 0; while ((startPos = Line.Find(HTTP_PREFIX, pos)) != CString::npos) { URL.Clear(); pos = startPos + HTTP_PREFIX_SIZE; if (Line.Length() < 5) return false; URL.Append(HTTP_PREFIX); ch = Line.at(pos); if (ch == 's') { URL.Append(ch); pos++; } if (Line.Length() < 7 || Line.at(pos++) != ':' || Line.at(pos++) != '/' || Line.at(pos++) != '/') return false; URL.Append("://"); length = 0; ch = Line.at(pos); while (ch != 0 && (IsChar(ch) || IsNumeral(ch) || ch == ':' || ch == '.' || ch == '-')) { URL.Append(ch); length++; ch = Line.at(++pos); } if (length < 3) { return false; } if (startPos == 0) { List.Add(URL); } else { ch = Line.at(startPos - 1); switch (ch) { case ' ': case ',': case ';': List.Add(URL); break; default: return false; } } } return true; } //-------------------------------------------------------------------------------------------------------------- int CWebService::CheckFee(const CString &Fee) { if (!Fee.IsEmpty()) { if (Fee.Length() >= 10) return -1; size_t numbers = 0; size_t delimiter = 0; size_t percent = 0; size_t pos = 0; TCHAR ch; ch = Fee.at(pos); while (ch != 0) { if (IsNumeral(ch)) numbers++; if (ch == '.') delimiter++; if (ch == '%') percent++; ch = Fee.at(++pos); } if (numbers == 0 || delimiter > 1 || percent > 1 || ((numbers + percent + delimiter) != Fee.Length())) return -1; return 1; } return 0; } //-------------------------------------------------------------------------------------------------------------- void CWebService::CreateAccessToken(const CProvider &Provider, const CString &Application, CContext &Context) { auto OnDone = [&Context](CTCPConnection *Sender) { auto pConnection = dynamic_cast (Sender); auto pReply = pConnection->Reply(); DebugReply(pReply); if (pReply->Status == CHTTPReply::ok) { const CJSON Json(pReply->Content); Context.SetStatus(csAuthorized); Context.Session() = Json["session"].AsString(); Context.Secret() = Json["secret"].AsString(); Context.Tokens().Values("access_token", Json["access_token"].AsString()); Context.SetFixedDate(0); Context.SetCheckDate(Now() + (CDateTime) 50 / MinsPerDay); // 50 min } return true; }; auto OnFail = [&Context](CTCPConnection *Sender, const Delphi::Exception::Exception &E) { Context.SetStatus(csInitialized); auto pConnection = dynamic_cast (Sender); auto pClient = dynamic_cast (pConnection->Client()); DebugReply(pConnection->Reply()); Log()->Error(APP_LOG_ERR, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); }; auto OnHTTPClient = [this](const CLocation &URI) { return GetClient(URI.hostname, URI.port); }; CString server_uri(Context.URL().Origin()); const auto &token_uri = Provider.TokenURI(Application); const auto &service_token = CToken::CreateToken(Provider, Application); Context.Tokens().Values("service_token", service_token); if (!token_uri.IsEmpty()) { CToken::FetchAccessToken(token_uri.front() == '/' ? server_uri + token_uri : token_uri, service_token, OnHTTPClient, OnDone, OnFail); } } //-------------------------------------------------------------------------------------------------------------- void CWebService::FetchProviders(CDateTime Now, CContext &Context) { for (int i = 0; i < Context.Providers().Count(); i++) { auto &Provider = Context.Providers()[i].Value(); for (int j = 0; j < Provider.Applications().Count(); ++j) { const auto &app = Provider.Applications().Members(j); if (app["type"].AsString() == "service_account") { if (Provider.KeyStatus() == ksUnknown) { CreateAccessToken(Provider, app.String(), Context); Provider.KeyStatusTime(Now); Provider.KeyStatus(ksSuccess); } } } } } //-------------------------------------------------------------------------------------------------------------- void CWebService::CheckProviders(CDateTime Now, CContext &Context) { for (int i = 0; i < Context.Providers().Count(); i++) { auto& Provider = Context.Providers()[i].Value(); if (Provider.KeyStatus() != ksUnknown) { Provider.KeyStatusTime(Now); Provider.KeyStatus(ksUnknown); } } } //-------------------------------------------------------------------------------------------------------------- bool CWebService::DoProxyExecute(CTCPConnection *AConnection) { auto pConnection = dynamic_cast (AConnection); auto pProxy = dynamic_cast (pConnection->Client()); if (Assigned(pProxy) && (pProxy->Connection() != nullptr)) { auto pProxyConnection = pProxy->Connection(); auto pServerRequest = pProxyConnection->Request(); auto pServerReply = pProxyConnection->Reply(); auto pProxyReply = pConnection->Reply(); const auto &caFormat = pServerRequest->Params["payload"]; if (!caFormat.IsEmpty()) { if (caFormat == "html") { pServerReply->ContentType = CHTTPReply::html; } else if (caFormat == "json") { pServerReply->ContentType = CHTTPReply::json; } else if (caFormat == "xml") { pServerReply->ContentType = CHTTPReply::xml; } else { pServerReply->ContentType = CHTTPReply::text; } if (pProxyReply->Status == CHTTPReply::ok) { if (!pProxyReply->Content.IsEmpty()) { const CJSON json(pProxyReply->Content); pServerReply->Content = base64_decode(json["payload"].AsString()); } pProxyConnection->SendReply(pProxyReply->Status, nullptr, true); } else { pProxyConnection->SendStockReply(pProxyReply->Status, true); } } else { if (pProxyReply->Status == CHTTPReply::ok) { pServerReply->Content = pProxyReply->Content; pProxyConnection->SendReply(pProxyReply->Status, nullptr, true); } else { pProxyConnection->SendStockReply(pProxyReply->Status, true); } } } pConnection->CloseConnection(true); return true; } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoProxyException(CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { auto pConnection = dynamic_cast (AConnection); auto pProxy = dynamic_cast (pConnection->Client()); if (Assigned(pProxy) && (pProxy->Connection() != nullptr)) { auto pProxyConnection = pProxy->Connection(); auto pServerRequest = pProxyConnection->Request(); auto pServerReply = pProxyConnection->Reply(); const auto &Format = pServerRequest->Params["format"]; if (Format == "html") { pServerReply->ContentType = CHTTPReply::html; } try { pProxyConnection->SendStockReply(CHTTPReply::bad_gateway, true); } catch (...) { } } Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pProxy->Host().c_str(), pProxy->Port(), E.what()); } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoEventHandlerException(CPollEventHandler *AHandler, const Delphi::Exception::Exception &E) { auto pConnection = dynamic_cast (AHandler->Binding()); auto pProxy = dynamic_cast (pConnection->Client()); if (Assigned(pProxy) && (pProxy->Connection() != nullptr)) { auto pProxyConnection = pProxy->Connection(); auto pReply = pProxyConnection->Reply(); ExceptionToJson(0, E, pReply->Content); pProxyConnection->SendReply(CHTTPReply::internal_server_error, nullptr, true); } Log()->Error(APP_LOG_EMERG, 0, E.what()); } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoProxyConnected(CObject *Sender) { auto pConnection = dynamic_cast (Sender); if (pConnection != nullptr) { Log()->Message(_T("[%s:%d] Client connected."), pConnection->Socket()->Binding()->PeerIP(), pConnection->Socket()->Binding()->PeerPort()); } } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoProxyDisconnected(CObject *Sender) { auto pConnection = dynamic_cast (Sender); if (pConnection != nullptr) { Log()->Message(_T("[%s:%d] Client disconnected."), pConnection->Socket()->Binding()->PeerIP(), pConnection->Socket()->Binding()->PeerPort()); } } //-------------------------------------------------------------------------------------------------------------- CHTTPProxy *CWebService::GetProxy(CHTTPServerConnection *AConnection) { auto pProxy = m_ProxyManager.Add(AConnection); #if defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 9) pProxy->OnVerbose([this](auto && Sender, auto && AConnection, auto && AFormat, auto && args) { DoVerbose(Sender, AConnection, AFormat, args); }); pProxy->OnExecute([this](auto && AConnection) { return DoProxyExecute(AConnection); }); pProxy->OnException([this](auto && AConnection, auto && E) { DoProxyException(AConnection, E); }); pProxy->OnEventHandlerException([this](auto && AHandler, auto && E) { DoEventHandlerException(AHandler, E); }); pProxy->OnConnected([this](auto && Sender) { DoProxyConnected(Sender); }); pProxy->OnDisconnected([this](auto && Sender) { DoProxyDisconnected(Sender); }); #else pProxy->OnVerbose(std::bind(&CWebService::DoVerbose, this, _1, _2, _3, _4)); pProxy->OnExecute(std::bind(&CWebService::DoProxyExecute, this, _1)); pProxy->OnException(std::bind(&CWebService::DoProxyException, this, _1, _2)); pProxy->OnEventHandlerException(std::bind(&CWebService::DoEventHandlerException, this, _1, _2)); pProxy->OnConnected(std::bind(&CWebService::DoProxyConnected, this, _1)); pProxy->OnDisconnected(std::bind(&CWebService::DoProxyDisconnected, this, _1)); #endif return pProxy; } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoAccount(CHTTPServerConnection *AConnection, const CString& Method, const CString& URI) { auto pProxy = GetProxy(AConnection); auto pServerRequest = AConnection->Request(); auto pProxyRequest = pProxy->Request(); const auto& caModuleAddress = m_Module["address"]; const auto& caOrigin = pServerRequest->Headers["origin"]; const auto& caUserAddress = pServerRequest->Params["address"]; const auto& pgpValue = pServerRequest->Params["pgp"]; const auto& caServerParam = pServerRequest->Params["server"]; const auto& caServer = caServerParam.IsEmpty() ? m_CurrentServer.URL().Origin() : caServerParam; CLocation Location(caServer); pProxy->Host() = Location.hostname; pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port); CStringList caClearText; CString sPayload; if (!pServerRequest->Content.IsEmpty()) { const auto& ContentType = pServerRequest->Headers.Values(_T("content-type")); if (ContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = pServerRequest->FormData; const auto& formDate = FormData["date"]; const auto& formAddress = FormData["address"]; const auto& formBitmessage = FormData["bitmessage"]; const auto& formKey = FormData["key"]; const auto& formPGP = FormData["pgp"]; const auto& formURL = FormData["url"]; const auto& formFlags = FormData["flags"]; const auto& formSign = FormData["sign"]; if (!formDate.IsEmpty()) { caClearText << formDate; } if (!formAddress.IsEmpty()) { caClearText << formAddress; } if (!formFlags.IsEmpty()) { caClearText << formFlags; } if (!formBitmessage.IsEmpty()) { caClearText << formBitmessage; } if (!formKey.IsEmpty()) { caClearText << formKey; } if (!formPGP.IsEmpty()) { caClearText << formPGP; } if (!formURL.IsEmpty()) { caClearText << formURL; } if (!formSign.IsEmpty()) { caClearText << formSign; } } else if (ContentType.Find("multipart/form-data") == 0) { CFormData FormData; CHTTPRequestParser::ParseFormData(pServerRequest, FormData); const auto& formDate = FormData.Data("date"); const auto& formAddress = FormData.Data("address"); const auto& formBitmessage = FormData.Data("bitmessage"); const auto& formKey = FormData.Data("key"); const auto& formPGP = FormData.Data("pgp"); const auto& formURL = FormData.Data("url"); const auto& formFlags = FormData.Data("flags"); const auto& formSign = FormData.Data("sign"); if (!formDate.IsEmpty()) { caClearText << formDate; } if (!formAddress.IsEmpty()) { caClearText << formAddress; } if (!formFlags.IsEmpty()) { caClearText << formFlags; } if (!formBitmessage.IsEmpty()) { caClearText << formBitmessage; } if (!formKey.IsEmpty()) { caClearText << formKey; } if (!formPGP.IsEmpty()) { caClearText << formPGP; } if (!formURL.IsEmpty()) { caClearText << formURL; } if (!formSign.IsEmpty()) { caClearText << formSign; } } else if (ContentType.Find("application/json") == 0) { const CJSON contextJson(pServerRequest->Content); const auto& jsonDate = contextJson["date"].AsString(); const auto& jsonAddress = contextJson["address"].AsString(); const auto& jsonBitmessage = contextJson["bitmessage"].AsString(); const auto& jsonKey = contextJson["key"].AsString(); const auto& jsonPGP = contextJson["pgp"].AsString(); const auto& jsonFlags = contextJson["flags"].AsString(); const auto& jsonSign = contextJson["sign"].AsString(); if (!jsonDate.IsEmpty()) { caClearText << jsonDate; } if (!jsonAddress.IsEmpty()) { caClearText << jsonAddress; } if (!jsonFlags.IsEmpty()) { caClearText << jsonFlags; } if (!jsonBitmessage.IsEmpty()) { caClearText << jsonBitmessage; } if (!jsonKey.IsEmpty()) { caClearText << jsonKey; } if (!jsonPGP.IsEmpty()) { caClearText << jsonPGP; } const CJSONValue &jsonURL = contextJson["url"]; if (jsonURL.IsArray()) { const CJSONArray &arrayURL = jsonURL.Array(); for (int i = 0; i < arrayURL.Count(); i++) { caClearText << arrayURL[i].AsString(); } } if (!jsonSign.IsEmpty()) { caClearText << jsonSign; } } else { caClearText = pServerRequest->Content; } if (pgpValue == "off" || pgpValue == "false") { sPayload = caClearText.Text(); } else { Apostol::PGP::CleartextSignature( m_pgpModuleKey, m_pgpPassphrase, BPS_PGP_HASH, caClearText.Text(), sPayload); } } //DebugMessage("[DoAccount] Server request:\n%s\n", pServerRequest->Content.c_str()); //DebugMessage("[DoAccount] sPayload:\n%s\n", sPayload.c_str()); CJSON Json(jvtObject); Json.Object().AddPair("id", ApostolUID()); Json.Object().AddPair("address", caUserAddress.IsEmpty() ? caModuleAddress : caUserAddress); if (!sPayload.IsEmpty()) Json.Object().AddPair("payload", base64_encode(sPayload)); pProxyRequest->Clear(); pProxyRequest->Location = pServerRequest->Location; pProxyRequest->CloseConnection = false; pProxyRequest->ContentType = CHTTPRequest::json; pProxyRequest->Content << Json; CHTTPRequest::Prepare(pProxyRequest, Method.c_str(), URI.c_str()); pProxyRequest->AddHeader("Authorization", "Bearer " + m_CurrentServer.Tokens()["access_token"]); if (!caModuleAddress.IsEmpty()) pProxyRequest->AddHeader("Module-Address", caModuleAddress); if (!caOrigin.IsEmpty()) pProxyRequest->AddHeader("Origin", caOrigin); AConnection->CloseConnection(false); pProxy->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::CheckDeal(const CDeal &Deal) { const auto& Data = Deal.Data(); const auto DateTime = UTC(); const auto Date = StringToDate(Data.Date); const auto Sum = BTCToDouble(Data.Payment.Sum); if (Data.Order == doCreate) { if (DateTime < Date) throw ExceptionFrm("Invalid deal date."); if ((DateTime - Date) > (CDateTime) 180 / SecsPerDay) throw ExceptionFrm("Deal date expired."); } if (Data.Order == doComplete) { const CDateTime LeaveBefore = StringToDate(Data.FeedBack.LeaveBefore); if (DateTime > LeaveBefore) throw ExceptionFrm("Date feedback expired."); } if (Odd(int(Data.Order)) || Data.Order == doExecute || Data.Order == doDelete) throw ExceptionFrm("Invalid \"order\" value for deal module."); if (Data.Order == doCancel) { const CDateTime Until = StringToDate(Data.Payment.Until); if (DateTime > Until) throw ExceptionFrm("Deal cancellation expired."); CString message(Data.Payment.Address); if (!Data.FeedBack.Comments.IsEmpty()) { message += LINEFEED; message += Data.FeedBack.Comments; } if (Data.Seller.Signature.IsEmpty() || !VerifyMessage(message, Data.Seller.Address, Data.Seller.Signature)) throw ExceptionFrm("The deal is not signed by the seller."); } if (Data.Order == doFeedback) { CString message(Data.Payment.Address); if (!Data.FeedBack.Comments.IsEmpty()) { message += LINEFEED; message += Data.FeedBack.Comments; } if (Data.Customer.Signature.IsEmpty() || !VerifyMessage(message, Data.Customer.Address, Data.Customer.Signature)) throw ExceptionFrm("The deal is not signed by the customer."); } if (!valid_address(Data.Seller.Address)) throw ExceptionFrm("Invalid Seller address: %s.", Data.Seller.Address.c_str()); if (!valid_address(Data.Customer.Address)) throw ExceptionFrm("Invalid Customer address: %s.", Data.Customer.Address.c_str()); if (!valid_address(Data.Payment.Address)) throw ExceptionFrm("Invalid Payment address: %s.", Data.Payment.Address.c_str()); } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) { auto pProxy = GetProxy(AConnection); auto pServerRequest = AConnection->Request(); auto pProxyRequest = pProxy->Request(); const auto& caModuleAddress = m_Module["address"]; const auto& caModuleFee = m_Module["fee"]; const auto checkFee = CheckFee(caModuleFee); if (checkFee == -1) throw ExceptionFrm("Invalid module fee value: %s", caModuleFee.c_str()); const auto& caOrigin = pServerRequest->Headers["origin"]; const auto& caUserAddress = pServerRequest->Params["address"]; const auto& pgpValue = pServerRequest->Params["pgp"]; const auto& caServerParam = pServerRequest->Params["server"]; const auto& caServer = caServerParam.IsEmpty() ? m_CurrentServer.URL().Origin() : caServerParam; CLocation Location(caServer); pProxy->Host() = Location.hostname; pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port); YAML::Node Node; CString sPayload; if (!pServerRequest->Content.IsEmpty()) { const auto& ContentType = pServerRequest->Headers.Values(_T("content-type")); if (ContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = pServerRequest->FormData; const auto& formType = FormData["type"]; const auto& formAt = FormData["at"]; const auto& formDate = FormData["date"]; const auto& formSellerAddress = FormData["seller_address"]; const auto& formSellerRating = FormData["seller_rating"]; const auto& formSellerSignature = FormData["seller_signature"]; const auto& formCustomerAddress = FormData["customer_address"]; const auto& formCustomerRating = FormData["customer_rating"]; const auto& formCustomerSignature = FormData["customer_signature"]; const auto& formPaymentAddress = FormData["payment_address"]; const auto& formPaymentUntil = FormData["payment_until"]; const auto& formPaymentSum = FormData["payment_sum"]; const auto& formFeedbackLeaveBefore = FormData["feedback_leave_before"]; const auto& formFeedbackStatus = FormData["feedback_status"]; const auto& formFeedbackComments = FormData["feedback_comments"]; CheckKeyForNull("order", Action.c_str()); CheckKeyForNull("type", formType.c_str()); CheckKeyForNull("at", formAt.c_str()); CheckKeyForNull("date", formDate.c_str()); CheckKeyForNull("seller_address", formSellerAddress.c_str()); CheckKeyForNull("customer_address", formCustomerAddress.c_str()); CheckKeyForNull("payment_sum", formPaymentSum.c_str()); if (Action == "cancel") { CheckKeyForNull("seller_signature", formSellerSignature.c_str()); } if (Action == "feedback") { CheckKeyForNull("customer_signature", formCustomerSignature.c_str()); } YAML::Node Deal = Node["deal"]; Deal["order"] = Action.c_str(); Deal["type"] = formType.c_str(); Deal["at"] = formAt.c_str(); Deal["date"] = formDate.c_str(); YAML::Node Seller = Deal["seller"]; Seller["address"] = formSellerAddress.c_str(); if (!formSellerRating.IsEmpty()) Seller["rating"] = formSellerRating.c_str(); if (!formSellerSignature.IsEmpty()) Seller["signature"] = formSellerSignature.c_str(); YAML::Node Customer = Deal["customer"]; Customer["address"] = formCustomerAddress.c_str(); if (!formCustomerRating.IsEmpty()) Customer["rating"] = formCustomerRating.c_str(); if (!formCustomerSignature.IsEmpty()) Customer["signature"] = formCustomerSignature.c_str(); YAML::Node Payment = Deal["payment"]; if (!formPaymentAddress.IsEmpty()) Payment["address"] = formPaymentAddress.c_str(); if (!formPaymentUntil.IsEmpty()) Payment["until"] = formPaymentUntil.c_str(); Payment["sum"] = formPaymentSum.c_str(); if (!formFeedbackLeaveBefore.IsEmpty()) { YAML::Node Feedback = Deal["feedback"]; Feedback["leave-before"] = formFeedbackLeaveBefore.c_str(); if (!formFeedbackStatus.IsEmpty()) Feedback["status"] = formFeedbackStatus.c_str(); if (!formFeedbackComments.IsEmpty()) Feedback["comments"] = formFeedbackComments.c_str(); } } else if (ContentType.Find("multipart/form-data") == 0) { CFormData FormData; CHTTPRequestParser::ParseFormData(pServerRequest, FormData); const auto& formType = FormData.Data("type"); const auto& formAt = FormData.Data("at"); const auto& formDate = FormData.Data("date"); const auto& formSellerAddress = FormData.Data("seller_address"); const auto& formSellerRating = FormData.Data("seller_rating"); const auto& formSellerSignature = FormData.Data("seller_signature"); const auto& formCustomerAddress = FormData.Data("customer_address"); const auto& formCustomerRating = FormData.Data("customer_rating"); const auto& formCustomerSignature = FormData.Data("customer_signature"); const auto& formPaymentAddress = FormData.Data("payment_address"); const auto& formPaymentUntil = FormData.Data("payment_until"); const auto& formPaymentSum = FormData.Data("payment_sum"); const auto& formFeedbackLeaveBefore = FormData.Data("feedback_leave_before"); const auto& formFeedbackStatus = FormData.Data("feedback_status"); const auto& formFeedbackComments = FormData.Data("feedback_comments"); CheckKeyForNull("order", Action.c_str()); CheckKeyForNull("type", formType.c_str()); CheckKeyForNull("at", formAt.c_str()); CheckKeyForNull("date", formDate.c_str()); CheckKeyForNull("seller_address", formSellerAddress.c_str()); CheckKeyForNull("customer_address", formCustomerAddress.c_str()); CheckKeyForNull("payment_sum", formPaymentSum.c_str()); if (Action == "cancel") { CheckKeyForNull("seller_signature", formSellerSignature.c_str()); } if (Action == "feedback") { CheckKeyForNull("customer_signature", formCustomerSignature.c_str()); } YAML::Node Deal = Node["deal"]; Deal["order"] = Action.c_str(); Deal["type"] = formType.c_str(); Deal["at"] = formAt.c_str(); Deal["date"] = formDate.c_str(); YAML::Node Seller = Deal["seller"]; Seller["address"] = formSellerAddress.c_str(); if (!formSellerRating.IsEmpty()) Seller["rating"] = formSellerRating.c_str(); if (!formSellerSignature.IsEmpty()) Seller["signature"] = formSellerSignature.c_str(); YAML::Node Customer = Deal["customer"]; Customer["address"] = formCustomerAddress.c_str(); if (!formSellerRating.IsEmpty()) Customer["rating"] = formCustomerRating.c_str(); if (!formCustomerSignature.IsEmpty()) Customer["signature"] = formCustomerSignature.c_str(); YAML::Node Payment = Deal["payment"]; if (!formPaymentAddress.IsEmpty()) Payment["address"] = formPaymentAddress.c_str(); if (!formPaymentUntil.IsEmpty()) Payment["until"] = formPaymentUntil.c_str(); Payment["sum"] = formPaymentSum.c_str(); if (!formFeedbackLeaveBefore.IsEmpty()) { YAML::Node Feedback = Deal["feedback"]; Feedback["leave-before"] = formFeedbackLeaveBefore.c_str(); if (!formFeedbackStatus.IsEmpty()) Feedback["status"] = formFeedbackStatus.c_str(); if (!formFeedbackComments.IsEmpty()) Feedback["comments"] = formFeedbackComments.c_str(); } } else if (ContentType.Find("application/json") == 0) { const CJSON jsonData(pServerRequest->Content); const auto& formOrder = jsonData["order"].AsString().Lower(); const auto& formType = jsonData["type"].AsString(); const auto& formAt = jsonData["at"].AsString(); const auto& formDate = jsonData["date"].AsString(); const CJSONValue &jsonSeller = jsonData["seller"]; const auto& formSellerAddress = jsonSeller["address"].AsString(); const auto& formSellerRating = jsonSeller["rating"].AsString(); const auto& formSellerSignature = jsonSeller["signature"].AsString(); const CJSONValue &jsonCustomer = jsonData["customer"]; const auto& formCustomerAddress = jsonCustomer["address"].AsString(); const auto& formCustomerRating = jsonCustomer["rating"].AsString(); const auto& formCustomerSignature = jsonCustomer["signature"].AsString(); const CJSONValue &jsonPayment = jsonData["payment"]; const auto& formPaymentAddress = jsonPayment["address"].AsString(); const auto& formPaymentUntil = jsonPayment["until"].AsString(); const auto& formPaymentSum = jsonPayment["sum"].AsString(); const CJSONValue &jsonFeedback = jsonData["feedback"]; const auto& formFeedbackLeaveBefore = jsonFeedback["leave-before"].AsString(); const auto& formFeedbackStatus = jsonFeedback["status"].AsString(); const auto& formFeedbackComments = jsonFeedback["comments"].AsString(); const auto &action = Action.IsEmpty() ? formOrder : Action; CheckKeyForNull("order", action.c_str()); CheckKeyForNull("type", formType.c_str()); CheckKeyForNull("at", formAt.c_str()); CheckKeyForNull("date", formDate.c_str()); CheckKeyForNull("seller.address", formSellerAddress.c_str()); CheckKeyForNull("customer.address", formCustomerAddress.c_str()); CheckKeyForNull("payment.sum", formPaymentSum.c_str()); if (action == "cancel") { CheckKeyForNull("seller.signature", formSellerSignature.c_str()); } if (action == "feedback") { CheckKeyForNull("customer.signature", formCustomerSignature.c_str()); } YAML::Node Deal = Node["deal"]; Deal["order"] = action.c_str(); Deal["type"] = formType.c_str(); Deal["at"] = formAt.c_str(); Deal["date"] = formDate.c_str(); YAML::Node Seller = Deal["seller"]; Seller["address"] = formSellerAddress.c_str(); if (!formSellerRating.IsEmpty()) Seller["rating"] = formSellerRating.c_str(); YAML::Node Customer = Deal["customer"]; Customer["address"] = formCustomerAddress.c_str(); if (!formSellerRating.IsEmpty()) Customer["rating"] = formCustomerRating.c_str(); YAML::Node Payment = Deal["payment"]; if (!formPaymentAddress.IsEmpty()) Payment["address"] = formPaymentAddress.c_str(); if (!formPaymentUntil.IsEmpty()) Payment["until"] = formPaymentUntil.c_str(); Payment["sum"] = formPaymentSum.c_str(); if (!formFeedbackLeaveBefore.IsEmpty()) { YAML::Node Feedback = Deal["feedback"]; Feedback["leave-before"] = formFeedbackLeaveBefore.c_str(); if (!formFeedbackStatus.IsEmpty()) Feedback["status"] = formFeedbackStatus.c_str(); if (!formFeedbackComments.IsEmpty()) Feedback["comments"] = formFeedbackComments.c_str(); } } else { Node = YAML::Load(pServerRequest->Content.c_str()); } const auto &BTCKeys = m_CurrentServer.BTCKeys(); if (BTCKeys.Count() < 2) throw ExceptionFrm("Bitcoin keys cannot be empty."); for (int i = 0; i < BTCKeys.Count(); ++i) { if (BTCKeys[i].IsEmpty()) throw ExceptionFrm("Bitcoin KEY%d cannot be empty.", i); } CDeal Deal(Node); auto& Data = Deal.Data(); if (Data.Order == doCreate) { Data.Payment.Address = Deal.GetPaymentHD(BTCKeys.Names(0), BTCKeys.Names(1), Deal.Data().Transaction.Key, BitcoinConfig.version_hd, BitcoinConfig.version_script); Node["deal"]["date"] = Data.Date.c_str(); YAML::Node Payment = Node["deal"]["payment"]; Payment.remove("sum"); Payment["address"] = Data.Payment.Address.c_str(); Payment["until"] = Data.Payment.Until.c_str(); Payment["sum"] = Data.Payment.Sum.c_str(); Node["deal"]["feedback"]["leave-before"] = Data.FeedBack.LeaveBefore.c_str(); } CheckDeal(Deal); const CString caClearText(YAML::Dump(Node)); if (pgpValue == "off" || pgpValue == "false") { sPayload = caClearText; } else { Apostol::PGP::CleartextSignature( m_pgpModuleKey, m_pgpPassphrase, BPS_PGP_HASH, caClearText, sPayload); } } CJSON Json(jvtObject); Json.Object().AddPair("id", ApostolUID()); if (!caUserAddress.IsEmpty()) Json.Object().AddPair("address", caUserAddress.IsEmpty() ? caModuleAddress : caUserAddress); if (!sPayload.IsEmpty()) Json.Object().AddPair("payload", base64_encode(sPayload)); pProxyRequest->Clear(); pProxyRequest->Location = pServerRequest->Location; pProxyRequest->CloseConnection = true; pProxyRequest->ContentType = CHTTPRequest::json; pProxyRequest->Content << Json; CHTTPRequest::Prepare(pProxyRequest, Method.c_str(), URI.c_str()); pProxyRequest->AddHeader("Authorization", "Bearer " + m_CurrentServer.Tokens()["access_token"]); if (!caModuleAddress.IsEmpty()) pProxyRequest->AddHeader("Module-Address", caModuleAddress); if (!caModuleFee.IsEmpty()) pProxyRequest->AddHeader("Module-Fee", caModuleFee); if (!caOrigin.IsEmpty()) pProxyRequest->AddHeader("Origin", caOrigin); AConnection->TimeOutInterval(30 * 1000); AConnection->UpdateTimeOut(Now()); AConnection->CloseConnection(false); pProxy->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoSignature(CHTTPServerConnection *AConnection) { auto pRequest = AConnection->Request(); auto pReply = AConnection->Reply(); if (pRequest->Content.IsEmpty()) { AConnection->SendStockReply(CHTTPReply::no_content); return; } const auto& caServerKey = m_CurrentServer.PGP().Key; if (caServerKey.IsEmpty()) throw ExceptionFrm("Server PGP key not added."); CString message; CJSON Json(jvtObject); const auto& caContentType = pRequest->Headers["content-type"]; if (caContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = pRequest->FormData; const auto& caClearText = FormData["message"]; CheckKeyForNull("message", caClearText.c_str()); const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); Json.Object().AddPair("verified", bVerified); } else if (caContentType.Find("multipart/form-data") == 0) { CFormData FormData; CHTTPRequestParser::ParseFormData(pRequest, FormData); const auto& caClearText = FormData.Data("message"); CheckKeyForNull("message", caClearText.c_str()); const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); Json.Object().AddPair("verified", bVerified); } else if (caContentType.Find("application/json") == 0) { const CJSON jsonData(pRequest->Content); const auto& caClearText = jsonData["message"].AsString(); CheckKeyForNull("message", caClearText.c_str()); const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); Json.Object().AddPair("verified", bVerified); } else { const auto& caClearText = pRequest->Content; const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); Json.Object().AddPair("verified", bVerified); } Json.Object().AddPair("message", message); AConnection->CloseConnection(true); pReply->Content << Json; AConnection->SendReply(CHTTPReply::ok); } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoAPI(CHTTPServerConnection *AConnection) { auto pRequest = AConnection->Request(); auto pReply = AConnection->Reply(); pReply->ContentType = CHTTPReply::json; CStringList slRouts; SplitColumns(pRequest->Location.pathname, slRouts, '/'); if (slRouts.Count() < 3) { AConnection->SendStockReply(CHTTPReply::not_found); return; } const auto& caService = slRouts[0].Lower(); const auto& caVersion = slRouts[1].Lower(); const auto& caCommand = slRouts[2].Lower(); const auto& caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : ""; if (caService != "api") { AConnection->SendStockReply(CHTTPReply::not_found); return; } if (caVersion != "v1") { AConnection->SendStockReply(CHTTPReply::not_found); return; } CString sRoute; for (int i = 0; i < slRouts.Count(); ++i) { sRoute.Append('/'); sRoute.Append(slRouts[i]); } try { if (caCommand == "ping") { AConnection->SendStockReply(CHTTPReply::ok); } else if (caCommand == "time") { pReply->Content << "{\"serverTime\": " << ToString(MsEpoch()) << "}"; AConnection->SendReply(CHTTPReply::ok); } else if (caCommand == "help") { pRequest->Content.Clear(); DoAccount(AConnection, "GET", sRoute); } else if (caCommand == "account" && caAction == "status") { pRequest->Content.Clear(); DoAccount(AConnection, "GET", sRoute); } else if (caCommand == "deal" && caAction == "status") { pRequest->Content.Clear(); DoDeal(AConnection, "GET", sRoute, caAction); } else if (caCommand == "bc" && caAction == "history") { const auto& caAccount = pRequest->Params["account"]; if (caAccount.IsEmpty()) { AConnection->SendStockReply(CHTTPReply::bad_request); return; } try { const wallet::payment_address address(std::string(caAccount.c_str())); CJSON history; fetch_history(address, history); pReply->Content = history.ToString(); } catch (Delphi::Exception::Exception &E) { ExceptionToJson(CHTTPReply::bad_request, E, pReply->Content); Log()->Error(APP_LOG_EMERG, 0, E.what()); } AConnection->SendReply(CHTTPReply::ok, nullptr, true); } else if (caCommand == "bc" && caAction == "header") { const auto& caHeight = pRequest->Params["height"]; const auto& caHash = pRequest->Params["hash"]; if (caHeight.IsEmpty() && caHash.IsEmpty()) { AConnection->SendStockReply(CHTTPReply::bad_request); return; } try { CJSON header; if (!caHash.IsEmpty()) { fetch_header(hash256(std::string(caHash.c_str())), header); } else { uint32_t height = StrToInt(caHeight.c_str()); fetch_header(height, header); } pReply->Content = header.ToString(); } catch (Delphi::Exception::Exception &E) { ExceptionToJson(CHTTPReply::bad_request, E, pReply->Content); Log()->Error(APP_LOG_EMERG, 0, E.what()); } AConnection->SendReply(CHTTPReply::ok, nullptr, true); } else { AConnection->SendStockReply(CHTTPReply::not_found); } } catch (std::exception &e) { CHTTPReply::CStatusType status = CHTTPReply::internal_server_error; ExceptionToJson(0, e, pReply->Content); AConnection->SendReply(status); Log()->Error(APP_LOG_EMERG, 0, e.what()); } } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoGet(CHTTPServerConnection *AConnection) { auto pRequest = AConnection->Request(); CString sPath(pRequest->Location.pathname); // Request sPath must be absolute and not contain "..". if (sPath.empty() || sPath.front() != '/' || sPath.find(_T("..")) != CString::npos) { AConnection->SendStockReply(CHTTPReply::bad_request); return; } if (sPath.SubString(0, 5) == "/api/") { DoAPI(AConnection); return; } SendResource(AConnection, sPath); } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoPost(CHTTPServerConnection *AConnection) { auto pRequest = AConnection->Request(); auto pReply = AConnection->Reply(); pReply->ContentType = CHTTPReply::json; CStringList slRouts; SplitColumns(pRequest->Location.pathname, slRouts, '/'); if (slRouts.Count() < 3) { AConnection->SendStockReply(CHTTPReply::not_found); return; } const auto& caService = slRouts[0].Lower(); const auto& caVersion = slRouts[1].Lower(); const auto& caCommand = slRouts[2].Lower(); const auto& caAction = slRouts.Count() == 4 ? slRouts[3].Lower() : ""; if (caService != "api") { AConnection->SendStockReply(CHTTPReply::not_found); return; } if (caVersion != "v1") { AConnection->SendStockReply(CHTTPReply::not_found); return; } CString sRoute; for (int i = 0; i < slRouts.Count(); ++i) { sRoute.Append('/'); sRoute.Append(slRouts[i]); } try { if (caCommand == "account") { DoAccount(AConnection, "POST", sRoute); } else if (caCommand == "deal") { DoDeal(AConnection, "POST", sRoute, caAction); } else if (caCommand == "signature") { DoSignature(AConnection); } else { AConnection->SendStockReply(CHTTPReply::not_found); } } catch (std::exception &e) { ExceptionToJson(0, e, pReply->Content); AConnection->SendReply(CHTTPReply::internal_server_error); Log()->Error(APP_LOG_EMERG, 0, e.what()); } } //-------------------------------------------------------------------------------------------------------------- const CContext &CWebService::CurrentServer() const { return m_CurrentServer; } //-------------------------------------------------------------------------------------------------------------- bool CWebService::CheckVerifyPGPSignature(int VerifyPGPSignature, CString &Message) { if (VerifyPGPSignature == -2) { Message = "PGP public key is not meaningful."; } else if (VerifyPGPSignature == -1) { Message = "PGP signature not valid."; } else if (VerifyPGPSignature == 0) { Message = "PGP signature not found."; } else if (VerifyPGPSignature > 1) { Message = "PGP signature status: Unknown."; } return VerifyPGPSignature == 1; } //-------------------------------------------------------------------------------------------------------------- int CWebService::VerifyPGPSignature(const CString &caClearText, const CString &Key, CString &Message) { const OpenPGP::Key signer(Key.c_str()); const OpenPGP::CleartextSignature cleartext(caClearText.c_str()); if (!cleartext.meaningful()) return -2; const int verified = OpenPGP::Verify::cleartext_signature(signer, cleartext); if (verified) { Message = cleartext.get_message().c_str(); } return verified; } //-------------------------------------------------------------------------------------------------------------- void CWebService::ParsePGPKey(const CString &Key, CStringPairs& ServerList, CStringList& BTCKeys) { if (Key.IsEmpty()) return; const Apostol::PGP::Key pgp(Key.c_str()); if (!pgp.meaningful()) return; CStringList Data; CPGPUserIdList List; CStringList KeyList; pgp.ExportUID(List); for (int i = 0; i < List.Count(); i++) { const auto& uid = List[i]; DebugMessage("%s (%s)\n", uid.Name.c_str(), uid.Desc.c_str()); const auto& name = uid.Name.Lower(); const auto& data = uid.Desc.Lower(); if (name == "technical_data") { SplitColumns(data, Data, ';'); if (Data.Count() == 3) { m_SyncPeriod = StrToIntDef(Data[1].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD); } else if (Data.Count() == 2) { m_SyncPeriod = StrToIntDef(Data[0].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD); } else if (Data.Count() == 1) { m_SyncPeriod = StrToIntDef(Data[0].Trim().c_str(), BPS_DEFAULT_SYNC_PERIOD); } } if (uid.Name.Length() >= 35 && uid.Name.SubString(0, 3) == BM_PREFIX) { CStringList urlList; if (FindURLInLine(uid.Desc, urlList)) { for (int l = 0; l < urlList.Count(); l++) { ServerList.AddPair(uid.Name, urlList[l]); } } } else if (name.Find("bitcoin_key") != CString::npos) { const auto& key = wallet::ec_public(data.c_str()); if (verify(key)) KeyList.AddPair(name, key.encoded()); } } CString Name; for (int i = 1; i <= KeyList.Count(); i++) { Name = "bitcoin_key"; Name << i; const auto& key = KeyList[Name]; if (!key.IsEmpty()) { BTCKeys.Add(key); } } } //-------------------------------------------------------------------------------------------------------------- void CWebService::JsonStringToKey(const CString &jsonString, CString& Key) { CJSON Json; Json << jsonString; if (Json.HasOwnProperty("error")) { const auto &error = Json["error"]; if (error.ValueType() == jvtObject) throw Delphi::Exception::Exception(error["message"].AsString().c_str()); } if (Json.HasOwnProperty("data")) { Key = Json["data"].AsString(); } } //-------------------------------------------------------------------------------------------------------------- void CWebService::FetchPGP(CContext &Context) { Log()->Notice("[%s] [%s] Trying to fetch a PGP key.", Context.Name().c_str(), Context.URL().Origin().c_str()); auto OnRequest = [&Context](CHTTPClient *Sender, CHTTPRequest *ARequest) { Context.PGP().Status = CKeyContext::ksFetching; Context.PGP().StatusTime = Now(); ARequest->ContentType = CHTTPRequest::json; ARequest->Content = CString().Format(R"({"type": "pgp", "account": "%s", "code": "%s", "fields": ["data"]})", Context.Name().c_str(), Context.PGP().Name.c_str()); CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/key/public"); const auto &access_token = Context.Tokens()["access_token"]; if (!access_token.empty()) { ARequest->AddHeader("Authorization", "Bearer " + access_token); } DebugRequest(ARequest); }; auto OnExecute = [this, &Context](CTCPConnection *AConnection) { auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pReply = pConnection->Reply(); DebugReply(pReply); CString Key; try { JsonStringToKey(pReply->Content, Key); if (Key.IsEmpty()) throw Delphi::Exception::Exception("Not found."); Context.PGP().Status = CKeyContext::ksSuccess; Context.PGP().StatusTime = Now(); Context.PGP().Key = Key; UpdateServerList(Key); Context.SetStatus(csRunning); } catch (Delphi::Exception::Exception &e) { Log()->Error(APP_LOG_INFO, 0, "[FetchPGP] %s", e.what()); } pConnection->CloseConnection(true); } return true; }; auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pClient = dynamic_cast (pConnection->Client()); if (pClient != nullptr) { Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); } DebugReply(pConnection->Reply()); } Context.SetStatus(Context::csAuthorized); Context.PGP().Status = CKeyContext::ksError; Context.PGP().StatusTime = Now(); }; Context.PGP().Status = CKeyContext::ksUnknown; Context.PGP().StatusTime = Now(); Context.PGP().RunTime = Context.PGP().StatusTime; auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port); pClient->OnRequest(OnRequest); pClient->OnExecute(OnExecute); pClient->OnException(OnException); pClient->AutoFree(true); pClient->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::ModuleService(CContext &Context) { Log()->Notice("[%s] [%s] Trying to fetch a OAuth2 configuration file.", Context.Name().c_str(), Context.URL().Origin().c_str()); auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) { Context.SetStatus(Context::csPreparing); ARequest->ContentType = CHTTPRequest::text; Apostol::PGP::CleartextSignature( m_pgpModuleKey, m_pgpPassphrase, BPS_PGP_HASH, Context.Name(), ARequest->Content); CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/service"); const auto &caModuleAddress = m_Module["address"]; if (!caModuleAddress.IsEmpty()) ARequest->AddHeader("Module-Address", caModuleAddress); DebugRequest(ARequest); }; auto OnExecute = [&Context](CTCPConnection *AConnection) { Context.SetStatus(Context::csInitialization); auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pReply = pConnection->Reply(); DebugReply(pReply); if (pReply->Status == CHTTPReply::ok) { const CJSON OAuth2(pReply->Content); UpdateOAuth2(Context, OAuth2.Object()); } pConnection->CloseConnection(true); } return true; }; auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pClient = dynamic_cast (pConnection->Client()); if (pClient != nullptr) { Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); } DebugReply(pConnection->Reply()); } Context.SetStatus(Context::csInitialization); }; auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port); pClient->OnRequest(OnRequest); pClient->OnExecute(OnExecute); pClient->OnException(OnException); pClient->AutoFree(true); pClient->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::ModuleStatus(CContext &Context) { Log()->Notice("[%s] [%s] Trying get module status.", Context.Name().c_str(), Context.URL().Origin().c_str()); auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) { Context.SetStatus(Context::csInProgress); const auto &caModuleAddress = m_Module["address"]; ARequest->ContentType = CHTTPRequest::text; ARequest->Content.Format(R"({"address": "%s"})", caModuleAddress.c_str()); CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/status"); ARequest->AddHeader("Authorization", "Bearer " + Context.Tokens()["access_token"]); if (!caModuleAddress.IsEmpty()) ARequest->AddHeader("Module-Address", caModuleAddress); DebugRequest(ARequest); }; auto OnExecute = [this, &Context](CTCPConnection *AConnection) { auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pReply = pConnection->Reply(); DebugReply(pReply); if (pReply->Status == CHTTPReply::ok) { ModuleAuthorize(Context); } else { ModuleNew(Context); } pConnection->CloseConnection(true); } return true; }; auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pClient = dynamic_cast (pConnection->Client()); if (pClient != nullptr) { Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); } DebugReply(pConnection->Reply()); } Context.SetStatus(Context::csInitialized); }; auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port); pClient->OnRequest(OnRequest); pClient->OnExecute(OnExecute); pClient->OnException(OnException); pClient->AutoFree(true); pClient->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::ModuleNew(CContext &Context) { Log()->Notice("[%s] [%s] Trying create new module.", Context.Name().c_str(), Context.URL().Origin().c_str()); auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) { Context.SetStatus(Context::csInProgress); ARequest->ContentType = CHTTPRequest::json; const auto &caModuleAddress = m_Module["address"]; CJSON Json(jvtObject); Json.Object().AddPair("address", caModuleAddress); Json.Object().AddPair("bitmessage", Context.Name()); const OpenPGP::SecretKey pgpSecret(m_pgpModuleKey.c_str()); const auto &public_key = pgpSecret.get_public(); Json.Object().AddPair("pgp", public_key.write(OpenPGP::PGP::Armored::YES)); ARequest->Content = Json.ToString(); CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/client/new"); ARequest->AddHeader("Authorization", "Bearer " + Context.Tokens()["access_token"]); if (!caModuleAddress.IsEmpty()) ARequest->AddHeader("Module-Address", caModuleAddress); DebugRequest(ARequest); }; auto OnExecute = [this, &Context](CTCPConnection *AConnection) { Context.SetStatus(Context::csInitialized); auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pReply = pConnection->Reply(); DebugReply(pReply); if (pReply->Status == CHTTPReply::ok) { const CJSON Json(pReply->Content); if (Json.HasOwnProperty("result")) { const auto &caResult = Json["result"]; if (caResult.HasOwnProperty("success")) { if (caResult["success"].AsBoolean()) { ModuleAuthorize(Context); } else { if (caResult.HasOwnProperty("message")) { Log()->Error(APP_LOG_EMERG, 0, "[%s] [%s] %s", Context.Name().c_str(), Context.URL().Origin().c_str(), caResult["message"].AsString().c_str()); } } } } } pConnection->CloseConnection(true); } return true; }; auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pClient = dynamic_cast (pConnection->Client()); if (pClient != nullptr) { Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); } DebugReply(pConnection->Reply()); } Context.SetStatus(Context::csInitialized); }; auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port); pClient->OnRequest(OnRequest); pClient->OnExecute(OnExecute); pClient->OnException(OnException); pClient->AutoFree(true); pClient->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::ModuleAuthorize(CContext &Context) { Log()->Notice("[%s] [%s] Trying authorize module.", Context.Name().c_str(), Context.URL().Origin().c_str()); auto OnRequest = [this, &Context](CHTTPClient *Sender, CHTTPRequest *ARequest) { Context.SetStatus(Context::csAuthorization); ARequest->ContentType = CHTTPRequest::text; Apostol::PGP::CleartextSignature( m_pgpModuleKey, m_pgpPassphrase, BPS_PGP_HASH, Context.Name(), ARequest->Content); CHTTPRequest::Prepare(ARequest, "POST", "/api/v1/dm/authorize"); ARequest->AddHeader("Authorization", "Bearer " + Context.Tokens()["access_token"]); const auto &caModuleAddress = m_Module["address"]; if (!caModuleAddress.IsEmpty()) ARequest->AddHeader("Module-Address", caModuleAddress); DebugRequest(ARequest); }; auto OnExecute = [&Context](CTCPConnection *AConnection) { Context.SetStatus(Context::csInitialized); auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pReply = pConnection->Reply(); DebugReply(pReply); if (pReply->Status == CHTTPReply::ok) { const CJSON Json(pReply->Content); Context.Session() = Json["session"].AsString(); Context.Secret() = Json["secret"].AsString(); Context.Tokens().Values("access_token", Json["access_token"].AsString()); Context.SetFixedDate(0); Context.SetCheckDate(Now() + (CDateTime) 55 / MinsPerDay); // 55 min Context.SetStatus(csAuthorized); } pConnection->CloseConnection(true); } return true; }; auto OnException = [&Context](CTCPConnection *AConnection, const Delphi::Exception::Exception &E) { auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pClient = dynamic_cast (pConnection->Client()); if (pClient != nullptr) { Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); } DebugReply(pConnection->Reply()); } Context.SetStatus(Context::csInitialized); }; auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port); pClient->OnRequest(OnRequest); pClient->OnExecute(OnExecute); pClient->OnException(OnException); pClient->AutoFree(true); pClient->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::Reload() { Config()->IniFile().ReadSectionValues("module", &m_Module); CString FileName; FileName = Config()->IniFile().ReadString(CONFIG_SECTION_NAME, "oauth2", "oauth2/service.json"); if (!path_separator(FileName.front())) { FileName = Config()->Prefix() + FileName; } if (FileExists(FileName.c_str())) { m_OAuth2.LoadFromFile(FileName.c_str()); } const auto &caPrivateKey = Config()->IniFile().ReadString("pgp", "private", "module.sec"); const auto &caPublicKey = Config()->IniFile().ReadString("pgp", "public", "dm.pub"); m_pgpPassphrase = Config()->IniFile().ReadString("pgp", "passphrase", ""); if (FileExists(caPrivateKey.c_str())) { m_pgpModuleKey.LoadFromFile(caPrivateKey.c_str()); if (FileExists(caPublicKey.c_str())) { m_pgpPublicKey.LoadFromFile(caPublicKey.c_str()); UpdateServerList(m_pgpPublicKey); } else { Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPublicKey.c_str()); } } else { Log()->Error(APP_LOG_WARN, 0, APP_FILE_NOT_FOUND, caPrivateKey.c_str()); } } //-------------------------------------------------------------------------------------------------------------- void CWebService::Heartbeat(CDateTime Now) { for (int i = 0; i < m_Servers.Count(); i++) { auto &Context = m_Servers[i].Value(); if (Now >= Context.CheckDate()) { Context.SetCheckDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec if (Context.Status() == Context::csInitialization) { ModuleService(Context); } if (Context.Status() >= Context::csInitialized) { CheckProviders(Now, Context); FetchProviders(Now, Context); } } if (Now >= Context.FixedDate()) { if (Context.Status() == Context::csAuthorized) { Context.SetFixedDate(Now + (CDateTime) 30 / SecsPerDay); // 30 sec Context.SetStatus(Context::csInProgress); FetchPGP(Context); } } if (Context.Status() == Context::csRunning) { if (Now >= m_NexServerTime) { m_NexServerTime = GetRandomDate(10 * 60, m_SyncPeriod * 60, Now); m_CurrentServer = Context; } } } } //-------------------------------------------------------------------------------------------------------------- void CWebService::Initialization(CModuleProcess *AProcess) { CApostolModule::Initialization(AProcess); if (Enabled()) { Reload(); } } //-------------------------------------------------------------------------------------------------------------- bool CWebService::Enabled() { if (m_ModuleStatus == msUnknown) m_ModuleStatus = Config()->IniFile().ReadBool(SectionName().c_str(), "enable", true) ? msEnabled : msDisabled; return m_ModuleStatus == msEnabled; } //-------------------------------------------------------------------------------------------------------------- CString CWebService::ToString(unsigned long Value) { TCHAR szString[_INT_T_LEN + 1] = {0}; sprintf(szString, "%lu", Value); return { szString }; } //-------------------------------------------------------------------------------------------------------------- } } }