/*++ 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, const CString& ModuleName, const CString& SectionName): CCustomModule(AProcess, ModuleName, SectionName) { m_NexServerTime = 0; CWebService::InitMethods(); InitServerList(); } //-------------------------------------------------------------------------------------------------------------- void CWebService::InitMethods() { CCustomModule::InitMethods(); } //-------------------------------------------------------------------------------------------------------------- void CWebService::InitServerList() { m_DefaultServer.Value().PGP().Add(CKeyContext(CString("PUBLIC"), CString())); m_DefaultServer.Value().PGP().Add(CKeyContext(m_DefaultServer.Name(), CString())); if (m_Servers.Count() == 0) { #ifdef _DEBUG int index = m_Servers.AddPair(BPS_BM_DEBUG_ADDRESS, CContext(BPS_BM_DEBUG_ADDRESS, CLocation(BPS_SERVER_URL))); auto &Keys = m_Servers[index].Value().PGP(); Keys.Add(CKeyContext(CString("PUBLIC"), CString())); Keys.Add(CKeyContext(CString(BPS_BM_DEBUG_ADDRESS), CString())); #else m_Servers.Add(m_DefaultServer); #endif } } //-------------------------------------------------------------------------------------------------------------- int CWebService::CurrentContextIndex(const CString &Params) { int index = 0; while (index < m_Servers.Count()) { const auto &caContext = m_Servers[index].Value(); if (caContext.Status() == Context::csRunning) { if (Params == caContext.Name() || Params == caContext.URL().Origin()) { return index; } } index++; } return -1; } //-------------------------------------------------------------------------------------------------------------- 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(); const auto &caServerRequest = pProxyConnection->Request(); auto &ServerReply = pProxyConnection->Reply(); auto &ProxyReply = pConnection->Reply(); const auto &caFormat = caServerRequest.Params["payload"]; if (!caFormat.IsEmpty()) { if (caFormat == "html") { ServerReply.ContentType = CHTTPReply::html; } else if (caFormat == "json") { ServerReply.ContentType = CHTTPReply::json; } else if (caFormat == "xml") { ServerReply.ContentType = CHTTPReply::xml; } else { ServerReply.ContentType = CHTTPReply::text; } if (ProxyReply.Status == CHTTPReply::ok) { if (!ProxyReply.Content.IsEmpty()) { const CJSON json(ProxyReply.Content); ServerReply.Content = base64_decode(json["payload"].AsString()); } pProxyConnection->SendReply(ProxyReply.Status, nullptr, true); } else { if (ProxyReply.Status == CHTTPReply::forbidden) { const auto &caServerParam = caServerRequest.Params["server"]; const auto index = CurrentContextIndex(caServerParam); auto &Context = index == -1 ? m_CurrentServer : m_Servers[index].Value(); Context.SetCheckDate(0); Context.SetStatus(Context::csInitialized); } ServerReply.Content = ProxyReply.Content; pProxyConnection->SendStockReply(ProxyReply.Status, true); } } else { ServerReply.Content = ProxyReply.Content; if (ProxyReply.Status == CHTTPReply::ok) { pProxyConnection->SendReply(ProxyReply.Status, nullptr, true); } else { pProxyConnection->SendStockReply(ProxyReply.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(); const auto &caServerRequest = pProxyConnection->Request(); auto &ServerReply = pProxyConnection->Reply(); const auto &Format = caServerRequest.Params["format"]; if (Format == "html") { ServerReply.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 &Reply = pProxyConnection->Reply(); ExceptionToJson(0, E, Reply.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); const auto &caServerRequest = AConnection->Request(); auto &ProxyRequest = pProxy->Request(); const auto &caModuleAddress = m_Module["address"]; const auto &caHost = caServerRequest.Headers["host"]; const auto &caOrigin = caServerRequest.Headers["origin"]; const auto &caUserAddress = GetAddress(caServerRequest); const auto &pgpValue = caServerRequest.Params["pgp"]; const auto &caServerParam = caServerRequest.Params["server"]; const auto index = CurrentContextIndex(caServerParam); const auto &caContext = index == -1 ? m_CurrentServer : m_Servers[index].Value(); const auto &caServer = caContext.URL().Origin(); CLocation Location(caServer); pProxy->Host() = Location.hostname; pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port); CStringList caClearText; CString sPayload; if (!caServerRequest.Content.IsEmpty()) { const auto &ContentType = caServerRequest.Headers.Values(_T("content-type")); if (ContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = caServerRequest.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(caServerRequest, 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(caServerRequest.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 = caServerRequest.Content; } if (pgpValue == "off" || pgpValue == "false") { sPayload = caClearText.Text(); } else { Apostol::PGP::CleartextSignature( m_pgpModuleKey, m_pgpPassphrase, BPS_PGP_HASH, caClearText.Text(), sPayload); } } CJSON Json(jvtObject); CJSONValue Module(jvtObject); Module.Object().AddPair("address", caModuleAddress); Json.Object().AddPair("id", GetUID(16).Lower()); Json.Object().AddPair("host", caHost); if (!caOrigin.IsEmpty()) { Json.Object().AddPair("origin", caOrigin); } Json.Object().AddPair("module", Module); Json.Object().AddPair("address", caUserAddress.IsEmpty() ? caModuleAddress : caUserAddress); if (!sPayload.IsEmpty()) Json.Object().AddPair("payload", base64_encode(sPayload)); ProxyRequest.Clear(); ProxyRequest.Location = caServerRequest.Location; ProxyRequest.CloseConnection = false; ProxyRequest.ContentType = CHTTPRequest::json; ProxyRequest.Content << Json; CHTTPRequest::Prepare(ProxyRequest, "POST", URI.c_str()); ProxyRequest.AddHeader("Authorization", "Bearer " + caContext.Tokens()["access_token"]); if (!caModuleAddress.IsEmpty()) ProxyRequest.AddHeader("Module-Address", caModuleAddress); if (!caOrigin.IsEmpty()) ProxyRequest.AddHeader("Origin", caOrigin); AConnection->CloseConnection(false); pProxy->AutoFree(true); pProxy->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoDeal(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI, const CString &Action) { auto pProxy = GetProxy(AConnection); const auto &caServerRequest = AConnection->Request(); auto &ProxyRequest = 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 &caHost = caServerRequest.Headers["host"]; const auto &caOrigin = caServerRequest.Headers["origin"]; const auto &address = GetAddress(caServerRequest); const auto &code = caServerRequest.Params["code"]; const auto &caUserAddress = address.length() == 40 ? CString() : address; const auto &caDealCode = !code.empty() ? code : address.length() == 40 ? address : CString(); const auto &pgpValue = caServerRequest.Params["pgp"]; const auto &caServerParam = caServerRequest.Params["server"]; const auto index = CurrentContextIndex(caServerParam); const auto &caContext = index == -1 ? m_CurrentServer : m_Servers[index].Value(); const auto &caServer = caContext.URL().Origin(); CLocation Location(caServer); pProxy->Host() = Location.hostname; pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port); YAML::Node Node; CString sPayload; if (!caServerRequest.Content.IsEmpty() && Action != "status") { const auto &ContentType = caServerRequest.Headers.Values(_T("content-type")); if (ContentType.Find("application/x-www-form-urlencoded") == 0) { const CStringList &FormData = caServerRequest.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"]; const auto &formFeedbackRefund = FormData["feedback_refund"]; 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.IsEmpty() ? UTCFormat(DateToString(UTC())).c_str() : 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(); if (!formFeedbackRefund.IsEmpty()) Feedback["refund"] = formFeedbackRefund.c_str(); } } else if (ContentType.Find("multipart/form-data") == 0) { CFormData FormData; CHTTPRequestParser::ParseFormData(caServerRequest, 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"); const auto &formFeedbackRefund = FormData.Data("feedback_refund"); 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.IsEmpty() ? UTCFormat(DateToString(UTC())).c_str() : 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(); if (!formFeedbackRefund.IsEmpty()) Feedback["refund"] = formFeedbackRefund.c_str(); } } else if (ContentType.Find("application/json") == 0) { const CJSON jsonData(caServerRequest.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 &formFeedbackRefund = jsonFeedback["refund"].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.IsEmpty() ? UTCFormat(DateToString(UTC())).c_str() : 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(); if (!formFeedbackRefund.IsEmpty()) Feedback["refund"] = formFeedbackRefund.c_str(); } } else { Node = YAML::Load(caServerRequest.Content.c_str()); } const auto &BTCKeys = caContext.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) { const auto isSegWit = IsSegWitAddress(Data.Seller.Address) && IsSegWitAddress(Data.Customer.Address); Data.Payment.Address = isSegWit ? Deal.GetPaymentSW(BTCKeys.Names(0), BTCKeys.Names(1), Deal.Data().Transaction.Key, BitcoinConfig.version_hd) : 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); CJSONValue Module(jvtObject); Module.Object().AddPair("address", caModuleAddress); Module.Object().AddPair("fee", caModuleFee); Json.Object().AddPair("id", GetUID(16).Lower()); Json.Object().AddPair("host", caHost); if (!caOrigin.IsEmpty()) { Json.Object().AddPair("origin", caOrigin); } Json.Object().AddPair("module", Module); Json.Object().AddPair("address", caUserAddress.IsEmpty() ? caModuleAddress : caUserAddress); if (!caDealCode.IsEmpty()) { CJSONValue Deal(jvtObject); Deal.Object().AddPair("code", caDealCode); Json.Object().AddPair("deal", Deal); } if (!sPayload.IsEmpty()) Json.Object().AddPair("payload", base64_encode(sPayload)); ProxyRequest.Clear(); ProxyRequest.Location = caServerRequest.Location; ProxyRequest.CloseConnection = true; ProxyRequest.ContentType = CHTTPRequest::json; ProxyRequest.Content << Json; CHTTPRequest::Prepare(ProxyRequest, "POST", URI.c_str()); ProxyRequest.AddHeader("Authorization", "Bearer " + caContext.Tokens()["access_token"]); if (!caModuleAddress.IsEmpty()) ProxyRequest.AddHeader("Module-Address", caModuleAddress); if (!caModuleFee.IsEmpty()) ProxyRequest.AddHeader("Module-Fee", caModuleFee); if (!caOrigin.IsEmpty()) ProxyRequest.AddHeader("Origin", caOrigin); AConnection->TimeOutInterval(30 * 1000); AConnection->UpdateTimeOut(Now()); AConnection->CloseConnection(false); pProxy->AutoFree(true); pProxy->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoProxy(CHTTPServerConnection *AConnection, const CString &Method, const CString &URI) { auto pProxy = GetProxy(AConnection); const auto &caServerRequest = AConnection->Request(); auto &ProxyRequest = 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 &caHost = caServerRequest.Headers["host"]; const auto &caOrigin = caServerRequest.Headers["origin"]; const auto &caContentType = caServerRequest.Headers["content-type"].Lower(); const auto &address = GetAddress(caServerRequest); const auto &code = caServerRequest.Params["code"]; const auto &caUserAddress = address.length() == 40 ? CString() : address; const auto &caDealCode = !code.empty() ? code : address.length() == 40 ? address : CString(); const auto &pgpValue = caServerRequest.Params["pgp"]; const auto &caServerParam = caServerRequest.Params["server"]; const auto index = CurrentContextIndex(caServerParam); const auto &caContext = index == -1 ? m_CurrentServer : m_Servers[index].Value(); const auto &caServer = caContext.URL().Origin(); CLocation Location(caServer); pProxy->Host() = Location.hostname; pProxy->Port(Location.port == 0 ? BPS_SERVER_PORT : Location.port); ProxyRequest.Clear(); ProxyRequest.Location = caServerRequest.Location; if (Method == "POST") { ProxyRequest.ContentType = StringToContentType(caContentType); ProxyRequest.Content = caServerRequest.Content; } ProxyRequest.CloseConnection = true; CHTTPRequest::Prepare(ProxyRequest, Method.c_str(), URI.c_str(), caContentType.c_str()); ProxyRequest.AddHeader("Authorization", "Bearer " + caContext.Tokens()["access_token"]); if (!caModuleAddress.IsEmpty()) ProxyRequest.AddHeader("Module-Address", caModuleAddress); if (!caModuleFee.IsEmpty()) ProxyRequest.AddHeader("Module-Fee", caModuleFee); if (!caOrigin.IsEmpty()) ProxyRequest.AddHeader("Origin", caOrigin); AConnection->TimeOutInterval(15 * 1000); AConnection->UpdateTimeOut(Now()); AConnection->CloseConnection(false); pProxy->AutoFree(true); pProxy->Active(true); } //-------------------------------------------------------------------------------------------------------------- void CWebService::DoSignature(CHTTPServerConnection *AConnection) { const auto &caRequest = AConnection->Request(); auto &Reply = AConnection->Reply(); if (caRequest.Content.IsEmpty()) { AConnection->SendStockReply(CHTTPReply::no_content); return; } const auto &caServerParam = caRequest.Params["server"]; int index = CurrentContextIndex(caServerParam); const auto &caContext = index == -1 ? m_CurrentServer : m_Servers[index].Value(); auto &Keys = caContext.PGP(); index = 0; while (index < Keys.Count() && Keys[index].Name != caContext.Name()) { index++; } if (index == Keys.Count()) throw ExceptionFrm("PGP key not found."); const auto &caServerKey = Keys[index].Key; if (caServerKey.IsEmpty()) throw ExceptionFrm("PGP key not added."); CString message; CJSON Json(jvtObject); const auto &caContentType = caRequest.Headers["content-type"]; if (caContentType.Find("application/x-www-form-urlencoded") == 0) { const auto &FormData = caRequest.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(caRequest, 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(caRequest.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 = caRequest.Content; const auto bVerified = CheckVerifyPGPSignature(VerifyPGPSignature(caClearText, caServerKey, message), message); Json.Object().AddPair("verified", bVerified); } Json.Object().AddPair("message", message); AConnection->CloseConnection(true); Reply.Content << Json; AConnection->SendReply(CHTTPReply::ok); } //-------------------------------------------------------------------------------------------------------------- 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 &Request) { const auto &name = Sender->Data()["name"]; auto &Keys = Context.PGP(); int index = 0; while (index < Keys.Count() && Keys[index].Name != name) { index++; } Request.ContentType = CHTTPRequest::json; if (index < Keys.Count()) { auto &Key = Keys[index]; Key.Status = CKeyContext::ksFetching; Key.StatusTime = Now(); } Request.Content = CString().Format(R"({"type": "pgp", "account": "%s", "code": "%s", "fields": ["code", "data"]})", Context.Name().c_str(), name.c_str()); CHTTPRequest::Prepare(Request, "POST", "/api/v1/key/public"); const auto &access_token = Context.Tokens()["access_token"]; if (!access_token.empty()) { Request.AddHeader("Authorization", "Bearer " + access_token); } DebugRequest(Request); }; auto OnExecute = [this, &Context](CTCPConnection *AConnection) { auto pConnection = dynamic_cast (AConnection); if (pConnection != nullptr) { auto pClient = dynamic_cast (pConnection->Client()); const auto &Reply = pConnection->Reply(); DebugReply(Reply); CString Code; CString Key; try { JsonStringToKey(Reply.Content, Code, Key); if (Key.IsEmpty()) throw Delphi::Exception::Exception("Not found."); const auto &name = pClient->Data()["name"]; auto &Keys = Context.PGP(); int index = 0; while (index < Keys.Count() && Keys[index].Name != name) { index++; } if (index < Keys.Count()) { auto &PGP = Keys[index]; PGP.Status = CKeyContext::ksSuccess; PGP.StatusTime = Now(); PGP.Key = Key; if (Code == "PUBLIC") { UpdateServerList(m_Servers, Key); Context.SetStatus(csRunning); } } } catch (Delphi::Exception::Exception &e) { Context.SetStatus(Context::csAuthorized); 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) { const auto &name = pClient->Data()["name"]; auto &Keys = Context.PGP(); int index = 0; while (index < Keys.Count() && Keys[index].Name != name) { index++; } if (index < Keys.Count()) { auto &Key = Keys[index]; Key.Status = CKeyContext::ksError; Key.StatusTime = Now(); } Log()->Error(APP_LOG_EMERG, 0, "[%s:%d] %s", pClient->Host().c_str(), pClient->Port(), E.what()); } DebugReply(pConnection->Reply()); } Context.SetStatus(Context::csAuthorized); }; auto &Keys = Context.PGP(); for (int i = 0; i < Keys.Count(); i++ ) { auto &Key = Keys[i]; Key.Status = CKeyContext::ksUnknown; Key.StatusTime = Now(); Key.RunTime = Key.StatusTime; auto pClient = GetClient(Context.URL().hostname, Context.URL().port == 0 ? BPS_SERVER_PORT : Context.URL().port); pClient->Data().AddPair("name", Key.Name); 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_Servers, 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(); #ifndef _DEBUG if (Context.Name() == BPS_BM_DEBUG_ADDRESS) continue; #endif 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(Context, Now); } } 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; } } } } //-------------------------------------------------------------------------------------------------------------- } } }