diff -U2 -r /var/lib/copr-rpmbuild/results/i2pd-git/upstream-unpacked/Source0/i2pd-openssl/daemon/HTTPServer.cpp /var/lib/copr-rpmbuild/results/i2pd-git/srpm-unpacked/i2pd-openssl.tar.gz-extract/i2pd-openssl/daemon/HTTPServer.cpp --- /var/lib/copr-rpmbuild/results/i2pd-git/upstream-unpacked/Source0/i2pd-openssl/daemon/HTTPServer.cpp 2026-02-09 02:40:12.000000000 +0000 +++ /var/lib/copr-rpmbuild/results/i2pd-git/srpm-unpacked/i2pd-openssl.tar.gz-extract/i2pd-openssl/daemon/HTTPServer.cpp 2026-02-09 02:20:48.000000000 +0000 @@ -1,1699 +1,1699 @@ -/* -* Copyright (c) 2013-2026, The PurpleI2P Project -* -* This file is part of Purple i2pd project and licensed under BSD3 -* -* See full license text in LICENSE file at top of project tree -*/ - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "Base.h" -#include "FS.h" -#include "Log.h" -#include "Config.h" -#include "Tunnel.h" -#include "Transports.h" -#include "NetDb.hpp" -#include "HTTP.h" -#include "LeaseSet.h" -#include "Destination.h" -#include "RouterContext.h" -#include "ClientContext.h" -#include "HTTPServer.h" -#include "Daemon.h" -#include "util.h" -#include "ECIESX25519AEADRatchetSession.h" -#include "I18N.h" - -#ifdef WIN32_APP -#include "Win32App.h" -#endif - -// For image, style and info -#include "version.h" -#include "HTTPServerResources.h" - -namespace i2p { -namespace http { - - static void LoadExtCSS (std::string fileName = "style") - { - std::stringstream s; - std::string styleFile = i2p::fs::DataDirPath ("webconsole/"+fileName+".css"); - if (i2p::fs::Exists(styleFile)) { - std::ifstream f(styleFile, std::ifstream::binary); - s << f.rdbuf(); - externalCSS = s.str(); - } else if (externalCSS.length() != 0) { // clean up external style if file was removed - externalCSS = ""; - } - } - - static void GetStyles (std::stringstream& s) - { - if (externalCSS.length() != 0) - s << "\r\n"; - else - s << internalCSS; - } - - const char HTTP_PAGE_TUNNELS[] = "tunnels"; - const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; - const char HTTP_PAGE_TRANSPORTS[] = "transports"; - const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations"; - const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination"; - const char HTTP_PAGE_I2CP_LOCAL_DESTINATION[] = "i2cp_local_destination"; - const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; - const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; - const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; - const char HTTP_PAGE_COMMANDS[] = "commands"; - const char HTTP_PAGE_LEASESETS[] = "leasesets"; - const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; - const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; - const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; - const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; - const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; - const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; - const char HTTP_COMMAND_RELOAD_TUNNELS_CONFIG[] = "reload_tunnels_config"; - const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; - const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; - const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; - const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; - const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; - const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; - const char HTTP_COMMAND_EXPIRELEASE[] = "expirelease"; - - static std::string ConvertTime (uint64_t time) - { - struct tm caltime; - lldiv_t divTime = lldiv(time, 1000); - time_t t = divTime.quot; -#ifdef _WIN32 - localtime_s(&caltime, &t); -#else - localtime_r(&t, &caltime); -#endif - char date[128]; - snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", caltime.tm_mday, caltime.tm_mon + 1, caltime.tm_year + 1900, caltime.tm_hour, caltime.tm_min, caltime.tm_sec, divTime.rem); - return date; - } - - static void ShowUptime (std::stringstream& s, int seconds) - { - int num; - - if ((num = seconds / 86400) > 0) { - s << ntr("%d day", "%d days", num, num) << ", "; - seconds -= num * 86400; - } - if ((num = seconds / 3600) > 0) { - s << ntr("%d hour", "%d hours", num, num) << ", "; - seconds -= num * 3600; - } - if ((num = seconds / 60) > 0) { - s << ntr("%d minute", "%d minutes", num, num) << ", "; - seconds -= num * 60; - } - s << ntr("%d second", "%d seconds", seconds, seconds); - } - - static void ShowTraffic (std::stringstream& s, uint64_t bytes) - { - s << std::fixed << std::setprecision(2); - auto numKBytes = (double) bytes / 1024; - if (numKBytes < 1024) - /* Kibibyte */ - s << numKBytes << " KiB"; - else if (numKBytes < 1024 * 1024) - /* Mebibyte */ - s << (numKBytes / 1024) << " MiB" ; - else - /* Gibibyte */ - s << (numKBytes / 1024 / 1024) << " GiB"; - } - - static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) - { - std::string state; - std::string_view stateText; - switch (eState) - { - case i2p::tunnel::eTunnelStateBuildReplyReceived : - case i2p::tunnel::eTunnelStatePending : state = "building"; break; - case i2p::tunnel::eTunnelStateBuildFailed : state = "failed"; stateText = "declined"; break; - case i2p::tunnel::eTunnelStateTestFailed : state = "failed"; stateText = "test failed"; break; - case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; - case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; - case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; - default: state = "unknown"; break; - } - if (stateText.empty ()) stateText = tr(state); - - s << " " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << ", "; // TODO: - ShowTraffic(s, bytes); - s << "\r\n"; - } - - static void SetLogLevel (const std::string& level) - { - if (level == "none" || level == "critical" || level == "error" || level == "warn" || level == "info" || level == "debug") - i2p::log::Logger().SetLogLevel(level); - else { - LogPrint(eLogError, "HTTPServer: Unknown loglevel set attempted"); - return; - } - i2p::log::Logger().Reopen (); - } - - static void ShowPageHead (std::stringstream& s) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - std::string theme; i2p::config::GetOption("http.theme", theme); - - const auto isThemeRegex = std::regex("^(white|black|light)"); - if (!std::regex_search(theme, isThemeRegex)) - { - LoadExtCSS(theme); - } - else - { - LoadExtCSS(); - } - - // Page language - std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language - auto it = i2p::i18n::languages.find(currLang); - // fallback to default English, if language is not supported - if (it == i2p::i18n::languages.end()) { - it = i2p::i18n::languages.find("english"); - } - std::string langCode = it->second.ShortCode; - - // Right to Left language option - bool rtl = i2p::client::context.GetLanguage ()->GetRTL(); - - s << - "\r\n" - "\r\n" - "\r\n" /* TODO: Find something to parse html/template system. This is horrible. */ - " \r\n" - " \r\n" - " \r\n" - " " << tr(/* tr: Webconsole page title */ "Purple I2P Webconsole") << "\r\n"; - GetStyles(s); - if (theme == "black") - { - s << - ""; - - } - s << - "\r\n" - "\r\n" - "
" << tr("i2pd webconsole") << "
\r\n" - "
\r\n" - "
\r\n" - " " << tr("Main page") << "

\r\n" - " " << tr("Router commands") << "
\r\n" - " " << tr("Local Destinations") << "
\r\n"; - if (i2p::context.IsFloodfill ()) - s << " " << tr("LeaseSets") << "
\r\n"; - s << - " " << tr("Tunnels") << "
\r\n"; - if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels()) - s << " " << tr("Transit Tunnels") << "
\r\n"; - s << - " " << tr("Transports") << "
\r\n" - " " << tr("I2P tunnels") << "
\r\n"; - if (i2p::client::context.GetSAMBridge ()) - s << " " << tr("SAM sessions") << "
\r\n"; - s << - "
\r\n\r\n" - "
\r\n\r\n"; - } - - static void ShowPageTail (std::stringstream& s) - { - s << - "
\r\n
\r\n" - "\r\n" - "\r\n"; - } - - static void ShowError(std::stringstream& s, std::string_view string) - { - s << "" << tr("ERROR") << ": " << string << "
\r\n"; - } - - static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) - { - switch (status) - { - case eRouterStatusOK: s << tr("OK"); break; - case eRouterStatusFirewalled: s << tr("Firewalled"); break; - case eRouterStatusUnknown: s << tr("Unknown"); break; - case eRouterStatusProxy: s << tr("Proxy"); break; - case eRouterStatusMesh: s << tr("Mesh"); break; - case eRouterStatusStan: s << tr("Stan"); break; - default: s << tr("Unknown"); - } - if (testing) - s << " (" << tr("Testing") << ")"; - if (error != eRouterErrorNone) - { - switch (error) - { - case eRouterErrorClockSkew: - s << " - " << tr("Clock skew"); - break; - case eRouterErrorOffline: - s << " - " << tr("Offline"); - break; - case eRouterErrorSymmetricNAT: - s << " - " << tr("Symmetric NAT"); - break; - case eRouterErrorFullConeNAT: - s << " - " << tr("Full cone NAT"); - break; - case eRouterErrorNoDescriptors: - s << " - " << tr("No Descriptors"); - break; - default: ; - } - } - } - - void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) - { - s << "" << tr("Uptime") << ": "; - ShowUptime(s, i2p::context.GetUptime ()); - s << "
\r\n"; - if (i2p::context.SupportsV4 () || i2p::context.GetStatus () != eRouterStatusUnknown) // don't show Unknown for ipv6-only - { - s << "" << tr("Network status") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ()); - s << "
\r\n"; - } - if (i2p::context.SupportsV6 ()) - { - s << "" << tr("Network status v6") << ": "; - ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); - s << "
\r\n"; - } - auto remains = Daemon.GetGracefulShutdownInterval (); - if (remains > 0) - { - s << "" << tr("Stopping in") << ": "; - ShowUptime(s, remains); - s << "
\r\n"; - } - auto family = i2p::context.GetFamily (); - if (family.length () > 0) - s << ""<< tr("Family") << ": " << family << "
\r\n"; - s << "" << tr("Tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; - bool isTotalTCSR; - i2p::config::GetOption("http.showTotalTCSR", isTotalTCSR); - if (isTotalTCSR) { - s << "" << tr("Total tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTotalTunnelCreationSuccessRate() << "%
\r\n"; - } - s << std::fixed << std::setprecision(2); - /* Kibibyte/s */ - s << "" << tr("Received") << ": "; - ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); - s << " (" << ((double) i2p::transport::transports.GetInBandwidth15s () / 1024) << " KiB/s)" << "
\r\n"; - s << "" << tr("Sent") << ": "; - ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); - s << " (" << ((double) i2p::transport::transports.GetOutBandwidth15s () / 1024) << " KiB/s)" << "
\r\n"; - s << "" << tr("Transit") << ": "; - ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); - s << " (" << ((double) i2p::transport::transports.GetTransitBandwidth15s () / 1024) << " KiB/s)" << "
\r\n"; - s << "" << tr("Data path") << ": " << i2p::fs::GetUTF8DataDir() << "
\r\n"; - s << "
"; - if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { - s << "\r\n"; - s << "\r\n"; - s << "
\r\n"; - } - if (includeHiddenContent) - { - s << "" << tr("Router Ident") << ": " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; - if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) - s << "" << tr("Router Family") << ": " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; - s << "" << tr("Router Caps") << ": " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; - s << "" << tr("Version") << ": " VERSION "
\r\n"; - s << ""<< tr("Our external address") << ":" << "
\r\n"; - s << "\r\n\r\n"; - auto addresses = i2p::context.GetRouterInfo().GetAddresses (); - if (addresses) - { - for (const auto& address : *addresses) - { - if (!address) continue; - s << "\r\n\r\n"; - if (address->published) { - s << "\r\n"; - } - else - { - /* tr: Shown when router doesn't publish itself and have "Firewalled" state */ - s << "\r\n"; - } - s << "\r\n"; - } - } - s << "\r\n
"; - switch (address->transportStyle) - { - case i2p::data::RouterInfo::eTransportNTCP2: - s << "NTCP2"; - break; - case i2p::data::RouterInfo::eTransportSSU2: - s << "SSU2"; - break; - default: - s << tr("Unknown"); - } - bool v6 = address->IsV6 (); - if (v6) - { - if (address->IsV4 ()) s << "v4"; - s << "v6"; - } - s << ""; - s << (v6 ? "[" : "") << address->host.to_string() << (v6 ? "]:" : ":"); - s << address->port << "" << tr("supported"); - if (address->port) - s << " :" << address->port; - s << "
\r\n"; - } - if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { - s << "
\r\n"; // class slidecontent - } - s << "
\r\n"; // class slide - if (outputFormat == OutputFormatEnum::forQtUi) { - s << "
"; - } - s << "" << tr("Routers") << ": " << i2p::data::netdb.GetNumRouters () << "   "; - s << "" << tr("Floodfills") << ": " << i2p::data::netdb.GetNumFloodfills () << "   "; - s << "" << tr("LeaseSets") << ": " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; - - size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); - clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); - size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); - - s << "" << tr("Client Tunnels") << ": " << std::to_string(clientTunnelCount) << "   "; - s << "" << tr("Transit Tunnels") << ": " << std::to_string(transitTunnelCount) << "
\r\n
\r\n"; - - if (outputFormat==OutputFormatEnum::forWebConsole) { - bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false; - bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false; - bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false; - bool sam = i2p::client::context.GetSAMBridge () ? true : false; - bool i2cp = i2p::client::context.GetI2CPServer () ? true : false; - bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - s << "\r\n\r\n\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n
" << tr("Services") << "
" << "HTTP " << tr("Proxy") << "" << (httpproxy ? tr("Enabled") : tr("Disabled")) << "
" << "SOCKS " << tr("Proxy") << "" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "
" << "BOB" << "" << (bob ? tr("Enabled") : tr("Disabled")) << "
" << "SAM" << "" << (sam ? tr("Enabled") : tr("Disabled")) << "
" << "I2CP" << "" << (i2cp ? tr("Enabled") : tr("Disabled")) << "
" << "I2PControl" << "" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "
\r\n
\r\n"; - } - } - - void ShowLocalDestinations (std::stringstream& s) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "" << tr("Local Destinations") << ":
\r\n
\r\n"; - for (auto& it: i2p::client::context.GetDestinations ()) - { - auto ident = it.second->GetIdentHash (); - s << "\r\n" << std::endl; - } - s << "
\r\n
\r\n"; - - auto i2cpServer = i2p::client::context.GetI2CPServer (); - if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) - { - s << "
I2CP "<< tr("Local Destinations") << ":
\r\n
\r\n"; - for (auto& it: i2cpServer->GetSessions ()) - { - auto dest = it.second->GetDestination (); - if (dest) - { - auto ident = dest->GetIdentHash (); - auto& name = dest->GetNickname (); - s << "
[ "; - s << name << " ] ⇔ " << i2p::client::context.GetAddressBook ().ToAddress(ident) <<"
\r\n" << std::endl; - } - } - s << "
\r\n
\r\n"; - } - } - - static void ShowHop(std::stringstream& s, const i2p::data::IdentityEx& ident) - { - auto identHash = ident.GetIdentHash(); - auto router = i2p::data::netdb.FindRouter(identHash); - s << i2p::data::GetIdentHashAbbreviation(identHash); - if (router) - s << " " << router->GetBandwidthCap() << ""; - } - - static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest, uint32_t token) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "Base32:
\r\n
\r\n
\r\n"; - - s << "Base64:
\r\n
\r\n
\r\n"; - - if (dest->IsEncryptedLeaseSet ()) - { - i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); - s << "
\r\n"; - s << "\r\n"; - s << "
\r\n"; - s << blinded.ToB33 () << ".b32.i2p
\r\n"; - s << "
\r\n
\r\n"; - } - - if (dest->IsPublic() && token && !dest->IsEncryptedLeaseSet ()) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "
\r\n" - "\r\n" - "
\r\n" - "
\r\n" - " \r\n" - " \r\n" - " GetIdentHash ().ToBase32 () << "\">\r\n" - " " << tr("Domain") << ":\r\n\r\n" - " \r\n" - "
\r\n" - "" << tr("Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.") << "\r\n" - "
\r\n
\r\n
\r\n"; - } - - if (dest->GetNumRemoteLeaseSets()) - { - s << "
\r\n\r\n" - << "
\r\n" - << "\r\n" - << "" - << "" // LeaseSet expiration button column - << "" - << "" - << "\r\n"; - for(auto& it: dest->GetLeaseSets ()) - { - s << "" - << "" - << "" - << "" - << "" - << "\r\n"; - } - s << "\r\n
" << tr("Address") << " " << tr("Type") << "" << tr("EncType") << "
" << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
\r\n"; - s << "
\r\n
\r\n
\r\n"; - } else - s << "" << tr("LeaseSets") << ": 0
\r\n
\r\n"; - - auto pool = dest->GetTunnelPool (); - if (pool) - { - s << "" << tr("Inbound tunnels") << ":
\r\n
\r\n"; - for (auto & it : pool->GetInboundTunnels ()) { - s << "
"; - // for each tunnel hop if not zero-hop - if (it->GetNumHops ()) - { - it->VisitTunnelHops( - [&s](std::shared_ptr hopIdent) - { - s << "⇒ "; - ShowHop(s, *hopIdent); - s << " "; - } - ); - } - s << "⇒ " << it->GetTunnelID () << ":me"; - if (it->LatencyIsKnown()) - s << " ( " << tr(/* tr: Milliseconds */ "%dms", it->GetMeanLatency()) << " )"; - ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); - s << "
\r\n"; - } - s << "
\r\n
\r\n"; - s << "" << tr("Outbound tunnels") << ":
\r\n
\r\n"; - for (auto & it : pool->GetOutboundTunnels ()) { - s << "
"; - s << it->GetTunnelID () << ":me ⇒"; - // for each tunnel hop if not zero-hop - if (it->GetNumHops ()) - { - it->VisitTunnelHops( - [&s](std::shared_ptr hopIdent) - { - s << " "; - ShowHop(s, *hopIdent); - s << " ⇒"; - } - ); - } - if (it->LatencyIsKnown()) - s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; - ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); - s << "
\r\n"; - } - s << "
\r\n
\r\n"; - } - s << "
\r\n"; - - s << "" << tr("Tags") << "
\r\n" - << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
\r\n"; - if (!dest->GetSessions ().empty ()) { - std::stringstream tmp_s; uint32_t out_tags = 0; - for (const auto& it: dest->GetSessions ()) { - tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; - out_tags += it.second->GetNumOutgoingTags (); - } - s << "
\r\n" - << "\r\n" - << "
\r\n" - << "\r\n\r\n" - << "\r\n" << tmp_s.str () << "\r\n
" << tr("Destination") << "" << tr("Amount") << "
\r\n" - << "
\r\n
\r\n"; - } else - s << tr("Outgoing") << ": 0
\r\n"; - s << "
\r\n"; - - auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); - if (numECIESx25519Tags > 0) { - s << "ECIESx25519
\r\n" << tr("Incoming Tags") << ": " << numECIESx25519Tags << "
\r\n"; - if (!dest->GetECIESx25519Sessions ().empty ()) - { - std::stringstream tmp_s; uint32_t ecies_sessions = 0; - for (const auto& it: dest->GetECIESx25519Sessions ()) { - tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "" << it.second->GetState () << "\r\n"; - ecies_sessions++; - } - s << "
\r\n" - << "\r\n" - << "
\r\n" - << "\r\n\r\n" - << "\r\n" << tmp_s.str () << "\r\n
" << tr("Destination") << "" << tr("Status") << "
\r\n" - << "
\r\n
\r\n"; - } else - s << tr("Tags sessions") << ": 0
\r\n"; - s << "
\r\n"; - } - } - - void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "" << tr("Local Destination") << ":
\r\n
\r\n"; - i2p::data::IdentHash ident; - ident.FromBase32 (b32); - auto dest = i2p::client::context.FindLocalDestination (ident); - - if (dest) - { - ShowLeaseSetDestination (s, dest, token); - - // Print table with streams information - s << "\r\n\r\n" - << "" - << "" - << "" // Stream closing button column - << "" - << "" - << "" - << "" - << "" - << "" - << "" - << "" - << "" - << "\r\n" - << "\r\n"; - - for (const auto& it: dest->GetAllStreams ()) - { - auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()); - std::string streamDestShort = streamDest.substr(0,12) + "….b32.i2p"; - s << ""; - s << ""; - if (it->GetRecvStreamID ()) { - s << ""; - } - else { - s << ""; // TODO: FIXME: Undefined HTML code - } - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << ""; - s << "\r\n"; - } - s << "\r\n
" << tr("Streams") << "
StreamID DestinationSentReceivedOutInBufRTTWindowStatus
" << it->GetRecvStreamID () << " !! FIXME !! " << streamDestShort << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << it->GetReceiveQueueSize () << "" << it->GetSendBufferSize () << "" << it->GetRTT () << "" << it->GetWindowSize () << "" << (int)it->GetStatus () << "
\r\n
\r\n"; - } - else - ShowError(s, tr("Such destination is not found")); - } - - void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) - { - auto i2cpServer = i2p::client::context.GetI2CPServer (); - if (i2cpServer) - { - s << "I2CP " << tr("Local Destination") << ":
\r\n
\r\n"; - auto it = i2cpServer->GetSessions ().find (std::stoi (id)); - if (it != i2cpServer->GetSessions ().end ()) - ShowLeaseSetDestination (s, it->second->GetDestination (), 0); - else - ShowError(s, tr("I2CP session not found")); - } - else - ShowError(s, tr("I2CP is not enabled")); - } - - void ShowLeasesSets(std::stringstream& s) - { - if (i2p::data::netdb.GetNumLeaseSets ()) - { - s << "" << tr("LeaseSets") << ":
\r\n
\r\n"; - int counter = 1; - // for each lease set - i2p::data::netdb.VisitLeaseSets( - [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) - { - // create copy of lease set so we extract leases - auto storeType = leaseSet->GetStoreType (); - std::unique_ptr ls; - if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) - ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); - else - { - ls.reset (new i2p::data::LeaseSet2 (storeType)); - ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), nullptr, false); - } - if (!ls) return; - s << "
IsExpired()) - s << " expired"; // additional css class for expired - s << "\">\r\n"; - if (!ls->IsValid()) - s << "
!! " << tr("Invalid") << " !!
\r\n"; - s << "
\r\n"; - s << "\r\n"; - s << "
\r\n"; - s << "" << tr("Store type") << ": " << (int)storeType << "
\r\n"; - s << "" << tr("Expires") << ": " << ConvertTime(ls->GetExpirationTime()) << "
\r\n"; - if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) - { - // leases information is available - auto leases = ls->GetNonExpiredLeases(); - s << "" << tr("Non Expired Leases") << ": " << leases.size() << "
\r\n"; - for ( auto & l : leases ) - { - s << "" << tr("Gateway") << ": " << l->tunnelGateway.ToBase64() << "
\r\n"; - s << "" << tr("TunnelID") << ": " << l->tunnelID << "
\r\n"; - s << "" << tr("EndDate") << ": " << ConvertTime(l->endDate) << "
\r\n"; - } - } - s << "
\r\n
\r\n"; // class slide class slidecontent - s << "
\r\n"; // class leaseset listitem - } - ); - // end for each lease set - s << "
\r\n
\r\n"; - } - else if (!i2p::context.IsFloodfill ()) - { - s << "" << tr("LeaseSets") << ": " << tr(/* Message on LeaseSets page */ "floodfill mode is disabled") << ".
\r\n"; - } - else - { - s << "" << tr("LeaseSets") << ": 0
\r\n"; - } - } - - void ShowTunnels (std::stringstream& s) - { - s << "" << tr("Tunnels") << ":
\r\n"; - s << "" << tr("Queue size") << ": " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n
\r\n"; - s << "" << tr("TBM Queue size") << ": " << i2p::tunnel::tunnels.GetTBMQueueSize () << "
\r\n
\r\n"; - - auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); - - s << "" << tr("Inbound tunnels") << ":
\r\n
\r\n"; - for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { - s << "
"; - if (it->GetNumHops ()) - { - it->VisitTunnelHops( - [&s](std::shared_ptr hopIdent) - { - s << "⇒ "; - ShowHop(s, *hopIdent); - s << " "; - } - ); - } - s << "⇒ " << it->GetTunnelID () << ":me"; - if (it->LatencyIsKnown()) - s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; - ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); - s << "
\r\n"; - } - s << "
\r\n
\r\n"; - s << "" << tr("Outbound tunnels") << ":
\r\n
\r\n"; - for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { - s << "
"; - s << it->GetTunnelID () << ":me ⇒"; - // for each tunnel hop if not zero-hop - if (it->GetNumHops ()) - { - it->VisitTunnelHops( - [&s](std::shared_ptr hopIdent) - { - s << " "; - ShowHop(s, *hopIdent); - s << " ⇒"; - } - ); - } - if (it->LatencyIsKnown()) - s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; - ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); - s << "
\r\n"; - } - s << "
\r\n
\r\n"; - } - - static void ShowCommands (std::stringstream& s, uint32_t token) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - - s << "" << tr("Router commands") << "
\r\n
\r\n
\r\n"; - s << " " << tr("Run peer test") << "
\r\n"; - s << " " << tr("Reload tunnels configuration") << "
\r\n"; - - if (i2p::context.AcceptsTunnels ()) - s << " " << tr("Decline transit tunnels") << "
\r\n"; - else - s << " " << tr("Accept transit tunnels") << "
\r\n"; - -#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) - if (Daemon.gracefulShutdownInterval) - s << " " << tr("Cancel graceful shutdown") << "
\r\n"; - else - s << " " << tr("Start graceful shutdown") << "
\r\n"; -#elif defined(WIN32_APP) - if (i2p::util::DaemonWin32::Instance().isGraceful) - s << " " << tr("Cancel graceful shutdown") << "
\r\n"; - else - s << " " << tr("Start graceful shutdown") << "
\r\n"; -#endif - - s << " " << tr("Force shutdown") << "

\r\n"; - s << " " << tr("Reload external CSS styles") << "\r\n"; - s << "
"; - - s << "
\r\n" << tr("Note: any action done here are not persistent and not changes your config files.") << "\r\n
\r\n"; - - auto loglevel = i2p::log::Logger().GetLogLevel(); - s << "" << tr("Logging level") << "
\r\n"; - s << " none \r\n"; - s << " critical \r\n"; - s << " error \r\n"; - s << " warn \r\n"; - s << " info \r\n"; - s << " debug
\r\n
\r\n"; - - uint32_t maxTunnels = i2p::tunnel::tunnels.GetMaxNumTransitTunnels (); - s << "" << tr("Transit tunnels limit") << "
\r\n"; - s << "
\r\n"; - s << " \r\n"; - s << " \r\n"; - s << " \r\n"; - s << " \r\n"; - s << "
\r\n
\r\n"; - - // get current used language - std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); - - s << "" - << tr("Change language") - << "
\r\n" - << "
\r\n" - << " \r\n" - << " \r\n" - << " \r\n" - << " \r\n" - << "
\r\n
\r\n"; - - } - - void ShowTransitTunnels (std::stringstream& s) - { - if (i2p::tunnel::tunnels.CountTransitTunnels()) - { - s << "" << tr("Transit Tunnels") << ":
\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) - { - s << ""; - if (std::dynamic_pointer_cast(it)) - s << ""; - else if (std::dynamic_pointer_cast(it)) - s << ""; - else - s << ""; - s << ""; - s << ""; - s << "\r\n"; - } - s << "\r\n
ID" << tr("Amount") << "" << tr("Next") << "
 " << std::setw(10) << it->GetTunnelID () << "" << std::setw(10) << it->GetTunnelID () << "  " << std::setw(10) << it->GetTunnelID () << ""; - ShowTraffic(s, it->GetNumTransmittedBytes ()); - s << " " << it->GetNextPeerName () << "
\r\n
\r\n"; - } - else - { - s << "" << tr("Transit Tunnels") << ": "; - /* Message on transit tunnels page */ - s << tr("no transit tunnels currently built") << ".
\r\n"; - } - } - - template - static void ShowTransportSessions (std::stringstream& s, const Sessions& sessions, const std::string name) - { - auto comp = [](typename Sessions::mapped_type a, typename Sessions::mapped_type b) - { return a->GetRemoteEndpoint() < b->GetRemoteEndpoint(); }; - std::set sortedSessions(comp); - for (const auto& it : sessions) - { - auto ret = sortedSessions.insert(it.second); - if (!ret.second) - LogPrint(eLogError, "HTTPServer: Duplicate remote endpoint detected: ", (*ret.first)->GetRemoteEndpoint()); - } - std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; - for (const auto& it: sortedSessions) - { - auto endpoint = it->GetRemoteEndpoint (); - if (it && it->IsEstablished () && endpoint.address ().is_v4 ()) - { - tmp_s << "
\r\n"; - if (it->IsOutgoing ()) tmp_s << " ⇒ "; - else tmp_s << "   "; - tmp_s << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << endpoint.address ().to_string () << ":" << endpoint.port (); - if (!it->IsOutgoing ()) tmp_s << " ⇒ "; - else tmp_s << "   "; - tmp_s << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; - if (it->GetRelayTag ()) - tmp_s << " [itag:" << it->GetRelayTag () << "]"; - if (it->GetSendQueueSize () > 0) - tmp_s << " [queue:" << it->GetSendQueueSize () << "]"; - if (it->IsSlow ()) tmp_s << " [slow]"; - tmp_s << "
\r\n" << std::endl; - cnt++; - } - if (it && it->IsEstablished () && endpoint.address ().is_v6 ()) - { - tmp_s6 << "
\r\n"; - if (it->IsOutgoing ()) tmp_s6 << " ⇒ "; - else tmp_s6 << "   "; - tmp_s6 << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " - << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); - if (!it->IsOutgoing ()) tmp_s6 << " ⇒ "; - else tmp_s6 << "   "; - tmp_s6 << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; - if (it->GetRelayTag ()) - tmp_s6 << " [itag:" << it->GetRelayTag () << "]"; - if (it->GetSendQueueSize () > 0) - tmp_s6 << " [queue:" << it->GetSendQueueSize () << "]"; - tmp_s6 << "
\r\n" << std::endl; - cnt6++; - } - } - if (!tmp_s.str ().empty ()) - { - s << "
\r\n" - << "\r\n" - << "
\r\n\r\n" - << tmp_s.str () << "
\r\n
\r\n"; - } - if (!tmp_s6.str ().empty ()) - { - s << "
\r\n" - << "\r\n" - << "
\r\n\r\n" - << tmp_s6.str () << "
\r\n
\r\n"; - } - } - - void ShowTransports (std::stringstream& s) - { - s << "" << tr("Transports") << ":
\r\n"; - auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); - if (ntcp2Server) - { - auto sessions = ntcp2Server->GetNTCP2Sessions (); - if (!sessions.empty ()) - ShowTransportSessions (s, sessions, "NTCP2"); - } - auto ssu2Server = i2p::transport::transports.GetSSU2Server (); - if (ssu2Server) - { - auto sessions = ssu2Server->GetSSU2Sessions (); - if (!sessions.empty ()) - ShowTransportSessions (s, sessions, "SSU2"); - } - } - - void ShowSAMSessions (std::stringstream& s) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - auto sam = i2p::client::context.GetSAMBridge (); - if (!sam) - { - ShowError(s, tr("SAM disabled")); - return; - } - - if (sam->GetSessions ().size ()) - { - s << "" << tr("SAM sessions") << ":
\r\n
\r\n"; - for (auto& it: sam->GetSessions ()) - { - auto& name = it.second->GetLocalDestination ()->GetNickname (); - auto sam_id = i2p::data::ByteStreamToBase64 ((const uint8_t *)it.first.data (), it.first.length ()); // base64, becuase session name might be UTF-8 - s << "\r\n" << std::endl; - } - s << "
\r\n
\r\n"; - } - else - { - s << "" << tr("SAM sessions") << ": "; - /* Message on SAM sessions page */ - s << tr("no sessions currently running") << ".
\r\n"; - } - } - - void ShowSAMSession (std::stringstream& s, const std::string& id) - { - auto sam = i2p::client::context.GetSAMBridge (); - if (!sam) - { - ShowError(s, tr("SAM disabled")); - return; - } - if (id.empty ()) - { - ShowError(s, tr("No sam_id")); - return; - } - std::vector sam_id(id.length ()); // id is in base64 - size_t l = i2p::data::Base64ToByteStream (id, sam_id.data (), sam_id.size ()); - if (!l) - { - ShowError(s, tr("Invalid sam_id")); - return; - } - auto session = sam->FindSession ( { (const char *)sam_id.data (), l }); - if (!session) - { - ShowError(s, tr("SAM session not found")); - return; - } - - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - s << "" << tr("SAM Session") << ":
\r\n
\r\n"; - auto& ident = session->GetLocalDestination ()->GetIdentHash(); - s << "\r\n"; - s << "
\r\n
\r\n"; - s << "" << tr("Streams") << ":
\r\n
\r\n"; - for (const auto& it: sam->ListSockets({ (const char *)sam_id.data (), l })) - { - s << "
"; - switch (it->GetSocketType ()) - { - case i2p::client::SAMSocketType::eSAMSocketTypeSession : s << "session"; break; - case i2p::client::SAMSocketType::eSAMSocketTypeStream : s << "stream"; break; - case i2p::client::SAMSocketType::eSAMSocketTypeAcceptor : s << "acceptor"; break; - case i2p::client::SAMSocketType::eSAMSocketTypeForward : s << "forward"; break; - default: s << "unknown"; break; - } - if (it->GetSocketType () != i2p::client::SAMSocketType::eSAMSocketTypeTerminated && it->GetSocket ().is_open ()) - s << " [" << it->GetSocket ().remote_endpoint() << "]"; - s << "
\r\n"; - } - s << "
\r\n"; - } - - void ShowI2PTunnels (std::stringstream& s) - { - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - - auto& clientTunnels = i2p::client::context.GetClientTunnels (); - auto httpProxy = i2p::client::context.GetHttpProxy (); - auto socksProxy = i2p::client::context.GetSocksProxy (); - if (!clientTunnels.empty () || httpProxy || socksProxy) - { - s << "" << tr("Client Tunnels") << ":
\r\n
\r\n"; - if (!clientTunnels.empty ()) - { - for (auto& it: clientTunnels) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << it.second->GetName () << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } - } - if (httpProxy) - { - auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << "HTTP " << tr("Proxy") << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } - if (socksProxy) - { - auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << "SOCKS " << tr("Proxy") << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } - s << "
\r\n
\r\n"; - } - - auto& serverTunnels = i2p::client::context.GetServerTunnels (); - if (!serverTunnels.empty ()) { - s << "
\r\n" << tr("Server Tunnels") << ":
\r\n
\r\n"; - for (auto& it: serverTunnels) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << it.second->GetName () << " ⇒ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << ":" << it.second->GetLocalPort (); - s << "
\r\n"<< std::endl; - } - s << "
\r\n
\r\n"; - } - - auto& clientForwards = i2p::client::context.GetClientForwards (); - if (!clientForwards.empty ()) - { - s << "
\r\n" << tr("Client Forwards") << ":
\r\n
\r\n"; - for (auto& it: clientForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << it.second->GetName () << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } - s << "
\r\n
\r\n"; - } - auto& serverForwards = i2p::client::context.GetServerForwards (); - if (!serverForwards.empty ()) - { - s << "
\r\n" << tr("Server Forwards") << ":
\r\n
\r\n"; - for (auto& it: serverForwards) - { - auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << "
"; - s << it.second->GetName () << " ⇐ "; - s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; - } - s << "
\r\n
\r\n"; - } - } - - HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket): - m_Socket (socket), m_BufferLen (0), expected_host(hostname) - { - /* cache options */ - i2p::config::GetOption("http.auth", needAuth); - i2p::config::GetOption("http.user", user); - i2p::config::GetOption("http.pass", pass); - } - - void HTTPConnection::Receive () - { - m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), - std::bind(&HTTPConnection::HandleReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2)); - } - - void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (ecode) { - if (ecode != boost::asio::error::operation_aborted) - Terminate (ecode); - return; - } - if (bytes_transferred <= HTTP_CONNECTION_BUFFER_SIZE) { - m_Buffer[bytes_transferred] = '\0'; - m_BufferLen = bytes_transferred; - } - else { - m_Buffer[HTTP_CONNECTION_BUFFER_SIZE] = '\0'; - m_BufferLen = HTTP_CONNECTION_BUFFER_SIZE; - } - RunRequest(); - Receive (); - } - - void HTTPConnection::RunRequest () - { - HTTPReq request; - int ret = request.parse(m_Buffer); - if (ret < 0) { - m_Buffer[0] = '\0'; - m_BufferLen = 0; - return; /* error */ - } - if (ret == 0) - return; /* need more data */ - - HandleRequest (request); - } - - void HTTPConnection::Terminate (const boost::system::error_code& ecode) - { - if (ecode == boost::asio::error::operation_aborted) - return; - boost::system::error_code ignored_ec; - m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); - m_Socket->close (); - } - - bool HTTPConnection::CheckAuth (const HTTPReq & req) - { - /* method #1: http://user:pass@127.0.0.1:7070/ */ - if (req.uri.find('@') != std::string::npos) { - URL url; - if (url.parse(req.uri) && url.user == user && url.pass == pass) - return true; - } - /* method #2: 'Authorization' header sent */ - auto provided = req.GetHeader ("Authorization"); - if (provided.length () > 0) - { - std::string expected = "Basic " + i2p::data::ToBase64Standard (user + ":" + pass); - if (expected == provided) return true; - } - - LogPrint(eLogWarning, "HTTPServer: Auth failure from ", m_Socket->remote_endpoint().address ()); - return false; - } - - void HTTPConnection::HandleRequest (const HTTPReq & req) - { - std::stringstream s; - std::string content; - HTTPRes res; - - LogPrint(eLogDebug, "HTTPServer: Request: ", req.uri); - - if (needAuth && !CheckAuth(req)) { - res.code = 401; - res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\""); - SendReply(res, content); - return; - } - - bool strictheaders; - i2p::config::GetOption("http.strictheaders", strictheaders); - if (strictheaders) - { - std::string http_hostname; - i2p::config::GetOption("http.hostname", http_hostname); - std::string host = req.GetHeader("Host"); - auto idx = host.find(':'); - /* strip out port so it's just host */ - if (idx != std::string::npos && idx > 0) - { - host = host.substr(0, idx); - } - if (!(host == expected_host || host == http_hostname)) - { - /* deny request as it's from a non whitelisted hostname */ - res.code = 403; - content = "host mismatch"; - SendReply(res, content); - return; - } - } - - // HTML head start - ShowPageHead (s); - if (req.uri.find("page=") != std::string::npos) { - HandlePage (req, res, s); - } else if (req.uri.find("cmd=") != std::string::npos) { - HandleCommand (req, res, s); - } else { - ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); - res.add_header("Refresh", "10"); - } - ShowPageTail (s); - - res.code = 200; - content = s.str (); - SendReply (res, content); - } - - std::map HTTPConnection::m_Tokens; - - uint32_t HTTPConnection::CreateToken () - { - uint32_t token; - RAND_bytes ((uint8_t *)&token, 4); - token &= 0x7FFFFFFF; // clear first bit - auto ts = i2p::util::GetSecondsSinceEpoch (); - for (auto it = m_Tokens.begin (); it != m_Tokens.end (); ) - { - if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT) - it = m_Tokens.erase (it); - else - ++it; - } - m_Tokens[token] = ts; - return token; - } - - void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s) - { - std::map params; - std::string page(""); - URL url; - - url.parse(req.uri); - url.parse_query(params); - page = params["page"]; - - if (page == HTTP_PAGE_TRANSPORTS) - ShowTransports (s); - else if (page == HTTP_PAGE_TUNNELS) - ShowTunnels (s); - else if (page == HTTP_PAGE_COMMANDS) - { - uint32_t token = CreateToken (); - ShowCommands (s, token); - } - else if (page == HTTP_PAGE_TRANSIT_TUNNELS) - ShowTransitTunnels (s); - else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) - ShowLocalDestinations (s); - else if (page == HTTP_PAGE_LOCAL_DESTINATION) - { - uint32_t token = CreateToken (); - ShowLocalDestination (s, params["b32"], token); - } - else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION) - ShowI2CPLocalDestination (s, params["i2cp_id"]); - else if (page == HTTP_PAGE_SAM_SESSIONS) - ShowSAMSessions (s); - else if (page == HTTP_PAGE_SAM_SESSION) - ShowSAMSession (s, params["sam_id"]); - else if (page == HTTP_PAGE_I2P_TUNNELS) - ShowI2PTunnels (s); - else if (page == HTTP_PAGE_LEASESETS) - ShowLeasesSets(s); - else { - res.code = 400; - ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO - return; - } - } - - void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s) - { - std::map params; - URL url; - - url.parse(req.uri); - url.parse_query(params); - - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - std::string redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=commands"; - std::string token = params["token"]; - - if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) - { - ShowError(s, tr("Invalid token")); - return; - } - - std::string cmd = params["cmd"]; - if (cmd == HTTP_COMMAND_RUN_PEER_TEST) - i2p::transport::transports.PeerTest (); - else if (cmd == HTTP_COMMAND_RELOAD_TUNNELS_CONFIG) - i2p::client::context.ReloadConfig (); - else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) - i2p::context.SetAcceptsTunnels (true); - else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) - i2p::context.SetAcceptsTunnels (false); - else if (cmd == HTTP_COMMAND_SHUTDOWN_START) - { - i2p::context.SetAcceptsTunnels (false); -#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) - Daemon.gracefulShutdownInterval = 10*60; -#elif defined(WIN32_APP) - i2p::win32::GracefulShutdown (); -#endif - } - else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) - { - i2p::context.SetAcceptsTunnels (true); -#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) - Daemon.gracefulShutdownInterval = 0; -#elif defined(WIN32_APP) - i2p::win32::StopGracefulShutdown (); -#endif - } - else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) - { -#ifndef WIN32_APP - Daemon.running = false; -#else - i2p::win32::StopWin32App (); -#endif - } - else if (cmd == HTTP_COMMAND_LOGLEVEL) - { - std::string level = params["level"]; - SetLogLevel (level); - } - else if (cmd == HTTP_COMMAND_KILLSTREAM) - { - std::string b32 = params["b32"]; - uint32_t streamID = std::stoul(params["streamID"], nullptr); - - i2p::data::IdentHash ident; - ident.FromBase32 (b32); - auto dest = i2p::client::context.FindLocalDestination (ident); - - if (streamID) - { - if (dest) - { - if (dest->DeleteStream (streamID)) - s << "" << tr("SUCCESS") << ": " << tr("Stream closed") << "
\r\n
\r\n"; - else - s << "" << tr("ERROR") << ": " << tr("Stream not found or already was closed") << "
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
\r\n
\r\n"; - - s << "" << tr("Return to destination page") << "
\r\n"; - s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; - redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32; - res.add_header("Refresh", redirect.c_str()); - return; - } - else if (cmd == HTTP_COMMAND_EXPIRELEASE) - { - std::string b32 = params["b32"]; - std::string lease = params["lease"]; - - i2p::data::IdentHash ident, leaseident; - ident.FromBase32 (b32); - leaseident.FromBase32 (lease); - auto dest = i2p::client::context.FindLocalDestination (ident); - - if (dest) - { - auto leaseset = dest->FindLeaseSet (leaseident); - if (leaseset) - { - leaseset->ExpireLease (); - s << "" << tr("SUCCESS") << ": " << tr("LeaseSet expiration time updated") << "
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("LeaseSet is not found or already expired") << "
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
\r\n
\r\n"; - - s << "" << tr("Return to destination page") << "
\r\n"; - s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; - redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32; - res.add_header("Refresh", redirect.c_str()); - return; - } - else if (cmd == HTTP_COMMAND_LIMITTRANSIT) - { - uint32_t limit = std::stoul(params["limit"], nullptr); - if (limit >= 2 && limit <= TRANSIT_TUNNELS_LIMIT) - i2p::tunnel::tunnels.SetMaxNumTransitTunnels (limit); - else { - s << "" << tr("ERROR") << ": "; - s << tr("Transit tunnels count must be at least 2 and not exceed %d", TRANSIT_TUNNELS_LIMIT) << "\r\n

\r\n"; - s << "" << tr("Back to commands list") << "\r\n
\r\n"; - s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; - res.add_header("Refresh", redirect.c_str()); - return; - } - } - else if (cmd == HTTP_COMMAND_GET_REG_STRING) - { - std::string b32 = params["b32"]; - std::string name = i2p::http::UrlDecode(params["name"]); - - i2p::data::IdentHash ident; - ident.FromBase32 (b32); - auto dest = i2p::client::context.FindLocalDestination (ident); - - if (dest) - { - std::size_t pos; - pos = name.find (".i2p"); - if (pos == (name.length () - 4)) - { - pos = name.find (".b32.i2p"); - if (pos == std::string::npos) - { - auto signatureLen = dest->GetIdentity ()->GetSignatureLen (); - uint8_t * signature = new uint8_t[signatureLen]; - std::stringstream out; - - out << name << "=" << dest->GetIdentity ()->ToBase64 (); - dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature); - auto sig = i2p::data::ByteStreamToBase64 (signature, signatureLen); - out << "#!sig=" << sig; - s << "" << tr("SUCCESS") << ":
\r\n

\r\n" - "\r\n
\r\n
\r\n" - "" << tr("Register at reg.i2p") << ":\r\n
\r\n" - "" << tr("Description") << ":\r\n\r\n" - "\r\n" - "
\r\n
\r\n"; - delete[] signature; - } - else - s << "" << tr("ERROR") << ": " << tr("Domain can't end with .b32.i2p") << "\r\n
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("Domain must end with .i2p") << "\r\n
\r\n
\r\n"; - } - else - s << "" << tr("ERROR") << ": " << tr("Such destination is not found") << "\r\n
\r\n
\r\n"; - - s << "" << tr("Return to destination page") << "\r\n"; - return; - } - else if (cmd == HTTP_COMMAND_SETLANGUAGE) - { - std::string lang = params["lang"]; - std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); - - if (currLang.compare(lang) != 0) - i2p::i18n::SetLanguage(lang); - } - else if (cmd == HTTP_COMMAND_RELOAD_CSS) - { - std::string theme; i2p::config::GetOption("http.theme", theme); - - if (theme != "light" && theme != "black" && theme !="white") LoadExtCSS(theme); - else LoadExtCSS(); - } - else - { - res.code = 400; - ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO - return; - } - - s << "" << tr("SUCCESS") << ": " << tr("Command accepted") << "

\r\n"; - s << "" << tr("Back to commands list") << "
\r\n"; - s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; - res.add_header("Refresh", redirect.c_str()); - } - - void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) - { - reply.add_header("X-Frame-Options", "SAMEORIGIN"); - reply.add_header("X-Content-Type-Options", "nosniff"); - reply.add_header("X-XSS-Protection", "1; mode=block"); - reply.add_header("Content-Type", "text/html"); - reply.body = content; - - m_SendBuffer = reply.to_string(); - boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (), - std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); - } - - HTTPServer::HTTPServer (const std::string& address, int port): - m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service.get_executor ()), - m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port)), - m_Hostname(address) - { - } - - HTTPServer::~HTTPServer () - { - Stop (); - } - - void HTTPServer::Start () - { - bool needAuth; i2p::config::GetOption("http.auth", needAuth); - std::string user; i2p::config::GetOption("http.user", user); - std::string pass; i2p::config::GetOption("http.pass", pass); - /* generate pass if needed */ - if (needAuth && pass == "") { - uint8_t random[16]; - char alnum[] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - pass.resize(sizeof(random)); - RAND_bytes(random, sizeof(random)); - for (size_t i = 0; i < sizeof(random); i++) { - pass[i] = alnum[random[i] % (sizeof(alnum) - 1)]; - } - i2p::config::SetOption("http.pass", pass); - LogPrint(eLogInfo, "HTTPServer: Password set to ", pass); - } - - m_IsRunning = true; - m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); - m_Acceptor.listen (); - Accept (); - - LoadExtCSS(); - } - - void HTTPServer::Stop () - { - m_IsRunning = false; - - boost::system::error_code ec; - m_Acceptor.cancel(ec); - if (ec) - LogPrint (eLogDebug, "HTTPServer: Error while cancelling operations on acceptor: ", ec.message ()); - m_Acceptor.close(); - - m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - m_Thread = nullptr; - } - } - - void HTTPServer::Run () - { - i2p::util::SetThreadName("Webconsole"); - - while (m_IsRunning) - { - try - { - m_Service.run (); - } - catch (std::exception& ex) - { - LogPrint (eLogError, "HTTPServer: Runtime exception: ", ex.what ()); - } - } - } - - void HTTPServer::Accept () - { - auto newSocket = std::make_shared (m_Service); - m_Acceptor.async_accept (*newSocket, std::bind (&HTTPServer::HandleAccept, this, - std::placeholders::_1, newSocket)); - } - - void HTTPServer::HandleAccept(const boost::system::error_code& ecode, - std::shared_ptr newSocket) - { - if (!ecode) - CreateConnection(newSocket); - else - { - if (newSocket) newSocket->close(); - LogPrint(eLogError, "HTTP Server: Error handling accept: ", ecode.message()); - } - if (m_IsRunning) - Accept (); - } - - void HTTPServer::CreateConnection(std::shared_ptr newSocket) - { - auto conn = std::make_shared (m_Hostname, newSocket); - conn->Receive (); - } -} // http -} // i2p +/* +* Copyright (c) 2013-2026, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Base.h" +#include "FS.h" +#include "Log.h" +#include "Config.h" +#include "Tunnel.h" +#include "Transports.h" +#include "NetDb.hpp" +#include "HTTP.h" +#include "LeaseSet.h" +#include "Destination.h" +#include "RouterContext.h" +#include "ClientContext.h" +#include "HTTPServer.h" +#include "Daemon.h" +#include "util.h" +#include "ECIESX25519AEADRatchetSession.h" +#include "I18N.h" + +#ifdef WIN32_APP +#include "Win32App.h" +#endif + +// For image, style and info +#include "version.h" +#include "HTTPServerResources.h" + +namespace i2p { +namespace http { + + static void LoadExtCSS (std::string fileName = "style") + { + std::stringstream s; + std::string styleFile = i2p::fs::DataDirPath ("webconsole/"+fileName+".css"); + if (i2p::fs::Exists(styleFile)) { + std::ifstream f(styleFile, std::ifstream::binary); + s << f.rdbuf(); + externalCSS = s.str(); + } else if (externalCSS.length() != 0) { // clean up external style if file was removed + externalCSS = ""; + } + } + + static void GetStyles (std::stringstream& s) + { + if (externalCSS.length() != 0) + s << "\r\n"; + else + s << internalCSS; + } + + const char HTTP_PAGE_TUNNELS[] = "tunnels"; + const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; + const char HTTP_PAGE_TRANSPORTS[] = "transports"; + const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations"; + const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination"; + const char HTTP_PAGE_I2CP_LOCAL_DESTINATION[] = "i2cp_local_destination"; + const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; + const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; + const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; + const char HTTP_PAGE_COMMANDS[] = "commands"; + const char HTTP_PAGE_LEASESETS[] = "leasesets"; + const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; + const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; + const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; + const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; + const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; + const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; + const char HTTP_COMMAND_RELOAD_TUNNELS_CONFIG[] = "reload_tunnels_config"; + const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; + const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; + const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; + const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; + const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; + const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; + const char HTTP_COMMAND_EXPIRELEASE[] = "expirelease"; + + static std::string ConvertTime (uint64_t time) + { + struct tm caltime; + lldiv_t divTime = lldiv(time, 1000); + time_t t = divTime.quot; +#ifdef _WIN32 + localtime_s(&caltime, &t); +#else + localtime_r(&t, &caltime); +#endif + char date[128]; + snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", caltime.tm_mday, caltime.tm_mon + 1, caltime.tm_year + 1900, caltime.tm_hour, caltime.tm_min, caltime.tm_sec, divTime.rem); + return date; + } + + static void ShowUptime (std::stringstream& s, int seconds) + { + int num; + + if ((num = seconds / 86400) > 0) { + s << ntr("%d day", "%d days", num, num) << ", "; + seconds -= num * 86400; + } + if ((num = seconds / 3600) > 0) { + s << ntr("%d hour", "%d hours", num, num) << ", "; + seconds -= num * 3600; + } + if ((num = seconds / 60) > 0) { + s << ntr("%d minute", "%d minutes", num, num) << ", "; + seconds -= num * 60; + } + s << ntr("%d second", "%d seconds", seconds, seconds); + } + + static void ShowTraffic (std::stringstream& s, uint64_t bytes) + { + s << std::fixed << std::setprecision(2); + auto numKBytes = (double) bytes / 1024; + if (numKBytes < 1024) + /* Kibibyte */ + s << numKBytes << " KiB"; + else if (numKBytes < 1024 * 1024) + /* Mebibyte */ + s << (numKBytes / 1024) << " MiB" ; + else + /* Gibibyte */ + s << (numKBytes / 1024 / 1024) << " GiB"; + } + + static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) + { + std::string state; + std::string_view stateText; + switch (eState) + { + case i2p::tunnel::eTunnelStateBuildReplyReceived : + case i2p::tunnel::eTunnelStatePending : state = "building"; break; + case i2p::tunnel::eTunnelStateBuildFailed : state = "failed"; stateText = "declined"; break; + case i2p::tunnel::eTunnelStateTestFailed : state = "failed"; stateText = "test failed"; break; + case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; + case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; + case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; + default: state = "unknown"; break; + } + if (stateText.empty ()) stateText = tr(state); + + s << " " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << ", "; // TODO: + ShowTraffic(s, bytes); + s << "\r\n"; + } + + static void SetLogLevel (const std::string& level) + { + if (level == "none" || level == "critical" || level == "error" || level == "warn" || level == "info" || level == "debug") + i2p::log::Logger().SetLogLevel(level); + else { + LogPrint(eLogError, "HTTPServer: Unknown loglevel set attempted"); + return; + } + i2p::log::Logger().Reopen (); + } + + static void ShowPageHead (std::stringstream& s) + { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + std::string theme; i2p::config::GetOption("http.theme", theme); + + const auto isThemeRegex = std::regex("^(white|black|light)"); + if (!std::regex_search(theme, isThemeRegex)) + { + LoadExtCSS(theme); + } + else + { + LoadExtCSS(); + } + + // Page language + std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language + auto it = i2p::i18n::languages.find(currLang); + // fallback to default English, if language is not supported + if (it == i2p::i18n::languages.end()) { + it = i2p::i18n::languages.find("english"); + } + std::string langCode = it->second.ShortCode; + + // Right to Left language option + bool rtl = i2p::client::context.GetLanguage ()->GetRTL(); + + s << + "\r\n" + "\r\n" + "\r\n" /* TODO: Find something to parse html/template system. This is horrible. */ + " \r\n" + " \r\n" + " \r\n" + " " << tr(/* tr: Webconsole page title */ "Purple I2P Webconsole") << "\r\n"; + GetStyles(s); + if (theme == "black") + { + s << + ""; + + } + s << + "\r\n" + "\r\n" + "

" << tr("i2pd webconsole") << "
\r\n" + "
\r\n" + "
\r\n" + " " << tr("Main page") << "

\r\n" + " " << tr("Router commands") << "
\r\n" + " " << tr("Local Destinations") << "
\r\n"; + if (i2p::context.IsFloodfill ()) + s << " " << tr("LeaseSets") << "
\r\n"; + s << + " " << tr("Tunnels") << "
\r\n"; + if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels()) + s << " " << tr("Transit Tunnels") << "
\r\n"; + s << + " " << tr("Transports") << "
\r\n" + " " << tr("I2P tunnels") << "
\r\n"; + if (i2p::client::context.GetSAMBridge ()) + s << " " << tr("SAM sessions") << "
\r\n"; + s << + "
\r\n\r\n" + "
\r\n\r\n"; + } + + static void ShowPageTail (std::stringstream& s) + { + s << + "
\r\n
\r\n" + "\r\n" + "\r\n"; + } + + static void ShowError(std::stringstream& s, std::string_view string) + { + s << "" << tr("ERROR") << ": " << string << "
\r\n"; + } + + static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) + { + switch (status) + { + case eRouterStatusOK: s << tr("OK"); break; + case eRouterStatusFirewalled: s << tr("Firewalled"); break; + case eRouterStatusUnknown: s << tr("Unknown"); break; + case eRouterStatusProxy: s << tr("Proxy"); break; + case eRouterStatusMesh: s << tr("Mesh"); break; + case eRouterStatusStan: s << tr("Stan"); break; + default: s << tr("Unknown"); + } + if (testing) + s << " (" << tr("Testing") << ")"; + if (error != eRouterErrorNone) + { + switch (error) + { + case eRouterErrorClockSkew: + s << " - " << tr("Clock skew"); + break; + case eRouterErrorOffline: + s << " - " << tr("Offline"); + break; + case eRouterErrorSymmetricNAT: + s << " - " << tr("Symmetric NAT"); + break; + case eRouterErrorFullConeNAT: + s << " - " << tr("Full cone NAT"); + break; + case eRouterErrorNoDescriptors: + s << " - " << tr("No Descriptors"); + break; + default: ; + } + } + } + + void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) + { + s << "" << tr("Uptime") << ": "; + ShowUptime(s, i2p::context.GetUptime ()); + s << "
\r\n"; + if (i2p::context.SupportsV4 () || i2p::context.GetStatus () != eRouterStatusUnknown) // don't show Unknown for ipv6-only + { + s << "" << tr("Network status") << ": "; + ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ()); + s << "
\r\n"; + } + if (i2p::context.SupportsV6 ()) + { + s << "" << tr("Network status v6") << ": "; + ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); + s << "
\r\n"; + } + auto remains = Daemon.GetGracefulShutdownInterval (); + if (remains > 0) + { + s << "" << tr("Stopping in") << ": "; + ShowUptime(s, remains); + s << "
\r\n"; + } + auto family = i2p::context.GetFamily (); + if (family.length () > 0) + s << ""<< tr("Family") << ": " << family << "
\r\n"; + s << "" << tr("Tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; + bool isTotalTCSR; + i2p::config::GetOption("http.showTotalTCSR", isTotalTCSR); + if (isTotalTCSR) { + s << "" << tr("Total tunnel creation success rate") << ": " << i2p::tunnel::tunnels.GetTotalTunnelCreationSuccessRate() << "%
\r\n"; + } + s << std::fixed << std::setprecision(2); + /* Kibibyte/s */ + s << "" << tr("Received") << ": "; + ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); + s << " (" << ((double) i2p::transport::transports.GetInBandwidth15s () / 1024) << " KiB/s)" << "
\r\n"; + s << "" << tr("Sent") << ": "; + ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); + s << " (" << ((double) i2p::transport::transports.GetOutBandwidth15s () / 1024) << " KiB/s)" << "
\r\n"; + s << "" << tr("Transit") << ": "; + ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); + s << " (" << ((double) i2p::transport::transports.GetTransitBandwidth15s () / 1024) << " KiB/s)" << "
\r\n"; + s << "" << tr("Data path") << ": " << i2p::fs::GetUTF8DataDir() << "
\r\n"; + s << "
"; + if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { + s << "\r\n"; + s << "\r\n"; + s << "
\r\n"; + } + if (includeHiddenContent) + { + s << "" << tr("Router Ident") << ": " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; + if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) + s << "" << tr("Router Family") << ": " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; + s << "" << tr("Router Caps") << ": " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; + s << "" << tr("Version") << ": " VERSION "
\r\n"; + s << ""<< tr("Our external address") << ":" << "
\r\n"; + s << "\r\n\r\n"; + auto addresses = i2p::context.GetRouterInfo().GetAddresses (); + if (addresses) + { + for (const auto& address : *addresses) + { + if (!address) continue; + s << "\r\n\r\n"; + if (address->published) { + s << "\r\n"; + } + else + { + /* tr: Shown when router doesn't publish itself and have "Firewalled" state */ + s << "\r\n"; + } + s << "\r\n"; + } + } + s << "\r\n
"; + switch (address->transportStyle) + { + case i2p::data::RouterInfo::eTransportNTCP2: + s << "NTCP2"; + break; + case i2p::data::RouterInfo::eTransportSSU2: + s << "SSU2"; + break; + default: + s << tr("Unknown"); + } + bool v6 = address->IsV6 (); + if (v6) + { + if (address->IsV4 ()) s << "v4"; + s << "v6"; + } + s << ""; + s << (v6 ? "[" : "") << address->host.to_string() << (v6 ? "]:" : ":"); + s << address->port << "" << tr("supported"); + if (address->port) + s << " :" << address->port; + s << "
\r\n"; + } + if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { + s << "
\r\n"; // class slidecontent + } + s << "
\r\n"; // class slide + if (outputFormat == OutputFormatEnum::forQtUi) { + s << "
"; + } + s << "" << tr("Routers") << ": " << i2p::data::netdb.GetNumRouters () << "   "; + s << "" << tr("Floodfills") << ": " << i2p::data::netdb.GetNumFloodfills () << "   "; + s << "" << tr("LeaseSets") << ": " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; + + size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); + clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); + size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); + + s << "" << tr("Client Tunnels") << ": " << std::to_string(clientTunnelCount) << "   "; + s << "" << tr("Transit Tunnels") << ": " << std::to_string(transitTunnelCount) << "
\r\n
\r\n"; + + if (outputFormat==OutputFormatEnum::forWebConsole) { + bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false; + bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false; + bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false; + bool sam = i2p::client::context.GetSAMBridge () ? true : false; + bool i2cp = i2p::client::context.GetI2CPServer () ? true : false; + bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); + s << "\r\n\r\n\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n
" << tr("Services") << "
" << "HTTP " << tr("Proxy") << "" << (httpproxy ? tr("Enabled") : tr("Disabled")) << "
" << "SOCKS " << tr("Proxy") << "" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "
" << "BOB" << "" << (bob ? tr("Enabled") : tr("Disabled")) << "
" << "SAM" << "" << (sam ? tr("Enabled") : tr("Disabled")) << "
" << "I2CP" << "" << (i2cp ? tr("Enabled") : tr("Disabled")) << "
" << "I2PControl" << "" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "
\r\n
\r\n"; + } + } + + void ShowLocalDestinations (std::stringstream& s) + { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + s << "" << tr("Local Destinations") << ":
\r\n
\r\n"; + for (auto& it: i2p::client::context.GetDestinations ()) + { + auto ident = it.second->GetIdentHash (); + s << "\r\n" << std::endl; + } + s << "
\r\n
\r\n"; + + auto i2cpServer = i2p::client::context.GetI2CPServer (); + if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) + { + s << "
I2CP "<< tr("Local Destinations") << ":
\r\n
\r\n"; + for (auto& it: i2cpServer->GetSessions ()) + { + auto dest = it.second->GetDestination (); + if (dest) + { + auto ident = dest->GetIdentHash (); + auto& name = dest->GetNickname (); + s << "
[ "; + s << name << " ] ⇔ " << i2p::client::context.GetAddressBook ().ToAddress(ident) <<"
\r\n" << std::endl; + } + } + s << "
\r\n
\r\n"; + } + } + + static void ShowHop(std::stringstream& s, const i2p::data::IdentityEx& ident) + { + auto identHash = ident.GetIdentHash(); + auto router = i2p::data::netdb.FindRouter(identHash); + s << i2p::data::GetIdentHashAbbreviation(identHash); + if (router) + s << " " << router->GetBandwidthCap() << ""; + } + + static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest, uint32_t token) + { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + s << "Base32:
\r\n
\r\n
\r\n"; + + s << "Base64:
\r\n
\r\n
\r\n"; + + if (dest->IsEncryptedLeaseSet ()) + { + i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); + s << "
\r\n"; + s << "\r\n"; + s << "
\r\n"; + s << blinded.ToB33 () << ".b32.i2p
\r\n"; + s << "
\r\n
\r\n"; + } + + if (dest->IsPublic() && token && !dest->IsEncryptedLeaseSet ()) + { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + s << "
\r\n" + "\r\n" + "
\r\n" + "
\r\n" + " \r\n" + " \r\n" + " GetIdentHash ().ToBase32 () << "\">\r\n" + " " << tr("Domain") << ":\r\n\r\n" + " \r\n" + "
\r\n" + "" << tr("Note: result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.") << "\r\n" + "
\r\n
\r\n
\r\n"; + } + + if (dest->GetNumRemoteLeaseSets()) + { + s << "
\r\n\r\n" + << "
\r\n" + << "\r\n" + << "" + << "" // LeaseSet expiration button column + << "" + << "" + << "\r\n"; + for(auto& it: dest->GetLeaseSets ()) + { + s << "" + << "" + << "" + << "" + << "" + << "\r\n"; + } + s << "\r\n
" << tr("Address") << " " << tr("Type") << "" << tr("EncType") << "
" << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
\r\n"; + s << "
\r\n
\r\n
\r\n"; + } else + s << "" << tr("LeaseSets") << ": 0
\r\n
\r\n"; + + auto pool = dest->GetTunnelPool (); + if (pool) + { + s << "" << tr("Inbound tunnels") << ":
\r\n
\r\n"; + for (auto & it : pool->GetInboundTunnels ()) { + s << "
"; + // for each tunnel hop if not zero-hop + if (it->GetNumHops ()) + { + it->VisitTunnelHops( + [&s](std::shared_ptr hopIdent) + { + s << "⇒ "; + ShowHop(s, *hopIdent); + s << " "; + } + ); + } + s << "⇒ " << it->GetTunnelID () << ":me"; + if (it->LatencyIsKnown()) + s << " ( " << tr(/* tr: Milliseconds */ "%dms", it->GetMeanLatency()) << " )"; + ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); + s << "
\r\n"; + } + s << "
\r\n
\r\n"; + s << "" << tr("Outbound tunnels") << ":
\r\n
\r\n"; + for (auto & it : pool->GetOutboundTunnels ()) { + s << "
"; + s << it->GetTunnelID () << ":me ⇒"; + // for each tunnel hop if not zero-hop + if (it->GetNumHops ()) + { + it->VisitTunnelHops( + [&s](std::shared_ptr hopIdent) + { + s << " "; + ShowHop(s, *hopIdent); + s << " ⇒"; + } + ); + } + if (it->LatencyIsKnown()) + s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; + ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); + s << "
\r\n"; + } + s << "
\r\n
\r\n"; + } + s << "
\r\n"; + + s << "" << tr("Tags") << "
\r\n" + << tr("Incoming") << ": " << dest->GetNumIncomingTags () << "
\r\n"; + if (!dest->GetSessions ().empty ()) { + std::stringstream tmp_s; uint32_t out_tags = 0; + for (const auto& it: dest->GetSessions ()) { + tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "" << it.second->GetNumOutgoingTags () << "\r\n"; + out_tags += it.second->GetNumOutgoingTags (); + } + s << "
\r\n" + << "\r\n" + << "
\r\n" + << "\r\n\r\n" + << "\r\n" << tmp_s.str () << "\r\n
" << tr("Destination") << "" << tr("Amount") << "
\r\n" + << "
\r\n
\r\n"; + } else + s << tr("Outgoing") << ": 0
\r\n"; + s << "
\r\n"; + + auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); + if (numECIESx25519Tags > 0) { + s << "ECIESx25519
\r\n" << tr("Incoming Tags") << ": " << numECIESx25519Tags << "
\r\n"; + if (!dest->GetECIESx25519Sessions ().empty ()) + { + std::stringstream tmp_s; uint32_t ecies_sessions = 0; + for (const auto& it: dest->GetECIESx25519Sessions ()) { + tmp_s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "" << it.second->GetState () << "\r\n"; + ecies_sessions++; + } + s << "
\r\n" + << "\r\n" + << "
\r\n" + << "\r\n\r\n" + << "\r\n" << tmp_s.str () << "\r\n
" << tr("Destination") << "" << tr("Status") << "
\r\n" + << "
\r\n
\r\n"; + } else + s << tr("Tags sessions") << ": 0
\r\n"; + s << "
\r\n"; + } + } + + void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) + { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + s << "" << tr("Local Destination") << ":
\r\n
\r\n"; + i2p::data::IdentHash ident; + ident.FromBase32 (b32); + auto dest = i2p::client::context.FindLocalDestination (ident); + + if (dest) + { + ShowLeaseSetDestination (s, dest, token); + + // Print table with streams information + s << "\r\n\r\n" + << "" + << "" + << "" // Stream closing button column + << "" + << "" + << "" + << "" + << "" + << "" + << "" + << "" + << "" + << "\r\n" + << "\r\n"; + + for (const auto& it: dest->GetAllStreams ()) + { + auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()); + std::string streamDestShort = streamDest.substr(0,12) + "….b32.i2p"; + s << ""; + s << ""; + if (it->GetRecvStreamID ()) { + s << ""; + } + else { + s << ""; // TODO: FIXME: Undefined HTML code + } + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << ""; + s << "\r\n"; + } + s << "\r\n
" << tr("Streams") << "
StreamID DestinationSentReceivedOutInBufRTTWindowStatus
" << it->GetRecvStreamID () << " !! FIXME !! " << streamDestShort << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << it->GetReceiveQueueSize () << "" << it->GetSendBufferSize () << "" << it->GetRTT () << "" << it->GetWindowSize () << "" << (int)it->GetStatus () << "
\r\n
\r\n"; + } + else + ShowError(s, tr("Such destination is not found")); + } + + void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) + { + auto i2cpServer = i2p::client::context.GetI2CPServer (); + if (i2cpServer) + { + s << "I2CP " << tr("Local Destination") << ":
\r\n
\r\n"; + auto it = i2cpServer->GetSessions ().find (std::stoi (id)); + if (it != i2cpServer->GetSessions ().end ()) + ShowLeaseSetDestination (s, it->second->GetDestination (), 0); + else + ShowError(s, tr("I2CP session not found")); + } + else + ShowError(s, tr("I2CP is not enabled")); + } + + void ShowLeasesSets(std::stringstream& s) + { + if (i2p::data::netdb.GetNumLeaseSets ()) + { + s << "" << tr("LeaseSets") << ":
\r\n
\r\n"; + int counter = 1; + // for each lease set + i2p::data::netdb.VisitLeaseSets( + [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr leaseSet) + { + // create copy of lease set so we extract leases + auto storeType = leaseSet->GetStoreType (); + std::unique_ptr ls; + if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) + ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); + else + { + ls.reset (new i2p::data::LeaseSet2 (storeType)); + ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), nullptr, false); + } + if (!ls) return; + s << "
IsExpired()) + s << " expired"; // additional css class for expired + s << "\">\r\n"; + if (!ls->IsValid()) + s << "
!! " << tr("Invalid") << " !!
\r\n"; + s << "
\r\n"; + s << "\r\n"; + s << "
\r\n"; + s << "" << tr("Store type") << ": " << (int)storeType << "
\r\n"; + s << "" << tr("Expires") << ": " << ConvertTime(ls->GetExpirationTime()) << "
\r\n"; + if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) + { + // leases information is available + auto leases = ls->GetNonExpiredLeases(); + s << "" << tr("Non Expired Leases") << ": " << leases.size() << "
\r\n"; + for ( auto & l : leases ) + { + s << "" << tr("Gateway") << ": " << l->tunnelGateway.ToBase64() << "
\r\n"; + s << "" << tr("TunnelID") << ": " << l->tunnelID << "
\r\n"; + s << "" << tr("EndDate") << ": " << ConvertTime(l->endDate) << "
\r\n"; + } + } + s << "
\r\n
\r\n"; // class slide class slidecontent + s << "
\r\n"; // class leaseset listitem + } + ); + // end for each lease set + s << "
\r\n
\r\n"; + } + else if (!i2p::context.IsFloodfill ()) + { + s << "" << tr("LeaseSets") << ": " << tr(/* Message on LeaseSets page */ "floodfill mode is disabled") << ".
\r\n"; + } + else + { + s << "" << tr("LeaseSets") << ": 0
\r\n"; + } + } + + void ShowTunnels (std::stringstream& s) + { + s << "" << tr("Tunnels") << ":
\r\n"; + s << "" << tr("Queue size") << ": " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n
\r\n"; + s << "" << tr("TBM Queue size") << ": " << i2p::tunnel::tunnels.GetTBMQueueSize () << "
\r\n
\r\n"; + + auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); + + s << "" << tr("Inbound tunnels") << ":
\r\n
\r\n"; + for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { + s << "
"; + if (it->GetNumHops ()) + { + it->VisitTunnelHops( + [&s](std::shared_ptr hopIdent) + { + s << "⇒ "; + ShowHop(s, *hopIdent); + s << " "; + } + ); + } + s << "⇒ " << it->GetTunnelID () << ":me"; + if (it->LatencyIsKnown()) + s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; + ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); + s << "
\r\n"; + } + s << "
\r\n
\r\n"; + s << "" << tr("Outbound tunnels") << ":
\r\n
\r\n"; + for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { + s << "
"; + s << it->GetTunnelID () << ":me ⇒"; + // for each tunnel hop if not zero-hop + if (it->GetNumHops ()) + { + it->VisitTunnelHops( + [&s](std::shared_ptr hopIdent) + { + s << " "; + ShowHop(s, *hopIdent); + s << " ⇒"; + } + ); + } + if (it->LatencyIsKnown()) + s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; + ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); + s << "
\r\n"; + } + s << "
\r\n
\r\n"; + } + + static void ShowCommands (std::stringstream& s, uint32_t token) + { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + + s << "" << tr("Router commands") << "
\r\n
\r\n
\r\n"; + s << " " << tr("Run peer test") << "
\r\n"; + s << " " << tr("Reload tunnels configuration") << "
\r\n"; + + if (i2p::context.AcceptsTunnels ()) + s << " " << tr("Decline transit tunnels") << "
\r\n"; + else + s << " " << tr("Accept transit tunnels") << "
\r\n"; + +#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) + if (Daemon.gracefulShutdownInterval) + s << " " << tr("Cancel graceful shutdown") << "
\r\n"; + else + s << " " << tr("Start graceful shutdown") << "
\r\n"; +#elif defined(WIN32_APP) + if (i2p::util::DaemonWin32::Instance().isGraceful) + s << " " << tr("Cancel graceful shutdown") << "
\r\n"; + else + s << " " << tr("Start graceful shutdown") << "
\r\n"; +#endif + + s << " " << tr("Force shutdown") << "

\r\n"; + s << " " << tr("Reload external CSS styles") << "\r\n"; + s << "
"; + + s << "
\r\n" << tr("Note: any action done here are not persistent and not changes your config files.") << "\r\n
\r\n"; + + auto loglevel = i2p::log::Logger().GetLogLevel(); + s << "" << tr("Logging level") << "
\r\n"; + s << " none \r\n"; + s << " critical \r\n"; + s << " error \r\n"; + s << " warn \r\n"; + s << " info \r\n"; + s << " debug
\r\n
\r\n"; + + uint32_t maxTunnels = i2p::tunnel::tunnels.GetMaxNumTransitTunnels (); + s << "" << tr("Transit tunnels limit") << "
\r\n"; + s << "
\r\n"; + s << " \r\n"; + s << " \r\n"; + s << " \r\n"; + s << " \r\n"; + s << "
\r\n
\r\n"; + + // get current used language + std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); + + s << "" + << tr("Change language") + << "
\r\n" + << "
\r\n" + << " \r\n" + << " \r\n" + << " \r\n" + << " \r\n" + << "
\r\n
\r\n"; + + } + + void ShowTransitTunnels (std::stringstream& s) + { + if (i2p::tunnel::tunnels.CountTransitTunnels()) + { + s << "" << tr("Transit Tunnels") << ":
\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) + { + s << ""; + if (std::dynamic_pointer_cast(it)) + s << ""; + else if (std::dynamic_pointer_cast(it)) + s << ""; + else + s << ""; + s << ""; + s << ""; + s << "\r\n"; + } + s << "\r\n
ID" << tr("Amount") << "" << tr("Next") << "
 " << std::setw(10) << it->GetTunnelID () << "" << std::setw(10) << it->GetTunnelID () << "  " << std::setw(10) << it->GetTunnelID () << ""; + ShowTraffic(s, it->GetNumTransmittedBytes ()); + s << " " << it->GetNextPeerName () << "
\r\n
\r\n"; + } + else + { + s << "" << tr("Transit Tunnels") << ": "; + /* Message on transit tunnels page */ + s << tr("no transit tunnels currently built") << ".
\r\n"; + } + } + + template + static void ShowTransportSessions (std::stringstream& s, const Sessions& sessions, const std::string name) + { + auto comp = [](typename Sessions::mapped_type a, typename Sessions::mapped_type b) + { return a->GetRemoteEndpoint() < b->GetRemoteEndpoint(); }; + std::set sortedSessions(comp); + for (const auto& it : sessions) + { + auto ret = sortedSessions.insert(it.second); + if (!ret.second) + LogPrint(eLogError, "HTTPServer: Duplicate remote endpoint detected: ", (*ret.first)->GetRemoteEndpoint()); + } + std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; + for (const auto& it: sortedSessions) + { + auto endpoint = it->GetRemoteEndpoint (); + if (it && it->IsEstablished () && endpoint.address ().is_v4 ()) + { + tmp_s << "
\r\n"; + if (it->IsOutgoing ()) tmp_s << " ⇒ "; + else tmp_s << "   "; + tmp_s << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " + << endpoint.address ().to_string () << ":" << endpoint.port (); + if (!it->IsOutgoing ()) tmp_s << " ⇒ "; + else tmp_s << "   "; + tmp_s << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; + if (it->GetRelayTag ()) + tmp_s << " [itag:" << it->GetRelayTag () << "]"; + if (it->GetSendQueueSize () > 0) + tmp_s << " [queue:" << it->GetSendQueueSize () << "]"; + if (it->IsSlow ()) tmp_s << " [slow]"; + tmp_s << "
\r\n" << std::endl; + cnt++; + } + if (it && it->IsEstablished () && endpoint.address ().is_v6 ()) + { + tmp_s6 << "
\r\n"; + if (it->IsOutgoing ()) tmp_s6 << " ⇒ "; + else tmp_s6 << "   "; + tmp_s6 << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " + << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); + if (!it->IsOutgoing ()) tmp_s6 << " ⇒ "; + else tmp_s6 << "   "; + tmp_s6 << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; + if (it->GetRelayTag ()) + tmp_s6 << " [itag:" << it->GetRelayTag () << "]"; + if (it->GetSendQueueSize () > 0) + tmp_s6 << " [queue:" << it->GetSendQueueSize () << "]"; + tmp_s6 << "
\r\n" << std::endl; + cnt6++; + } + } + if (!tmp_s.str ().empty ()) + { + s << "
\r\n" + << "\r\n" + << "
\r\n\r\n" + << tmp_s.str () << "
\r\n
\r\n"; + } + if (!tmp_s6.str ().empty ()) + { + s << "
\r\n" + << "\r\n" + << "
\r\n\r\n" + << tmp_s6.str () << "
\r\n
\r\n"; + } + } + + void ShowTransports (std::stringstream& s) + { + s << "" << tr("Transports") << ":
\r\n"; + auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); + if (ntcp2Server) + { + auto sessions = ntcp2Server->GetNTCP2Sessions (); + if (!sessions.empty ()) + ShowTransportSessions (s, sessions, "NTCP2"); + } + auto ssu2Server = i2p::transport::transports.GetSSU2Server (); + if (ssu2Server) + { + auto sessions = ssu2Server->GetSSU2Sessions (); + if (!sessions.empty ()) + ShowTransportSessions (s, sessions, "SSU2"); + } + } + + void ShowSAMSessions (std::stringstream& s) + { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + auto sam = i2p::client::context.GetSAMBridge (); + if (!sam) + { + ShowError(s, tr("SAM disabled")); + return; + } + + if (sam->GetSessions ().size ()) + { + s << "" << tr("SAM sessions") << ":
\r\n
\r\n"; + for (auto& it: sam->GetSessions ()) + { + auto& name = it.second->GetLocalDestination ()->GetNickname (); + auto sam_id = i2p::data::ByteStreamToBase64 ((const uint8_t *)it.first.data (), it.first.length ()); // base64, becuase session name might be UTF-8 + s << "\r\n" << std::endl; + } + s << "
\r\n
\r\n"; + } + else + { + s << "" << tr("SAM sessions") << ": "; + /* Message on SAM sessions page */ + s << tr("no sessions currently running") << ".
\r\n"; + } + } + + void ShowSAMSession (std::stringstream& s, const std::string& id) + { + auto sam = i2p::client::context.GetSAMBridge (); + if (!sam) + { + ShowError(s, tr("SAM disabled")); + return; + } + if (id.empty ()) + { + ShowError(s, tr("No sam_id")); + return; + } + std::vector sam_id(id.length ()); // id is in base64 + size_t l = i2p::data::Base64ToByteStream (id, sam_id.data (), sam_id.size ()); + if (!l) + { + ShowError(s, tr("Invalid sam_id")); + return; + } + auto session = sam->FindSession ( { (const char *)sam_id.data (), l }); + if (!session) + { + ShowError(s, tr("SAM session not found")); + return; + } + + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + s << "" << tr("SAM Session") << ":
\r\n
\r\n"; + auto& ident = session->GetLocalDestination ()->GetIdentHash(); + s << "\r\n"; + s << "
\r\n
\r\n"; + s << "" << tr("Streams") << ":
\r\n
\r\n"; + for (const auto& it: sam->ListSockets({ (const char *)sam_id.data (), l })) + { + s << "
"; + switch (it->GetSocketType ()) + { + case i2p::client::SAMSocketType::eSAMSocketTypeSession : s << "session"; break; + case i2p::client::SAMSocketType::eSAMSocketTypeStream : s << "stream"; break; + case i2p::client::SAMSocketType::eSAMSocketTypeAcceptor : s << "acceptor"; break; + case i2p::client::SAMSocketType::eSAMSocketTypeForward : s << "forward"; break; + default: s << "unknown"; break; + } + if (it->GetSocketType () != i2p::client::SAMSocketType::eSAMSocketTypeTerminated && it->GetSocket ().is_open ()) + s << " [" << it->GetSocket ().remote_endpoint() << "]"; + s << "
\r\n"; + } + s << "
\r\n"; + } + + void ShowI2PTunnels (std::stringstream& s) + { + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + + auto& clientTunnels = i2p::client::context.GetClientTunnels (); + auto httpProxy = i2p::client::context.GetHttpProxy (); + auto socksProxy = i2p::client::context.GetSocksProxy (); + if (!clientTunnels.empty () || httpProxy || socksProxy) + { + s << "" << tr("Client Tunnels") << ":
\r\n
\r\n"; + if (!clientTunnels.empty ()) + { + for (auto& it: clientTunnels) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << it.second->GetName () << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + } + if (httpProxy) + { + auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << "HTTP " << tr("Proxy") << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + if (socksProxy) + { + auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << "SOCKS " << tr("Proxy") << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + s << "
\r\n
\r\n"; + } + + auto& serverTunnels = i2p::client::context.GetServerTunnels (); + if (!serverTunnels.empty ()) { + s << "
\r\n" << tr("Server Tunnels") << ":
\r\n
\r\n"; + for (auto& it: serverTunnels) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << it.second->GetName () << " ⇒ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << ":" << it.second->GetLocalPort (); + s << "
\r\n"<< std::endl; + } + s << "
\r\n
\r\n"; + } + + auto& clientForwards = i2p::client::context.GetClientForwards (); + if (!clientForwards.empty ()) + { + s << "
\r\n" << tr("Client Forwards") << ":
\r\n
\r\n"; + for (auto& it: clientForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << it.second->GetName () << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + s << "
\r\n
\r\n"; + } + auto& serverForwards = i2p::client::context.GetServerForwards (); + if (!serverForwards.empty ()) + { + s << "
\r\n" << tr("Server Forwards") << ":
\r\n
\r\n"; + for (auto& it: serverForwards) + { + auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); + s << "
"; + s << it.second->GetName () << " ⇐ "; + s << i2p::client::context.GetAddressBook ().ToAddress(ident); + s << "
\r\n"<< std::endl; + } + s << "
\r\n
\r\n"; + } + } + + HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr socket): + m_Socket (socket), m_BufferLen (0), expected_host(hostname) + { + /* cache options */ + i2p::config::GetOption("http.auth", needAuth); + i2p::config::GetOption("http.user", user); + i2p::config::GetOption("http.pass", pass); + } + + void HTTPConnection::Receive () + { + m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), + std::bind(&HTTPConnection::HandleReceive, shared_from_this (), + std::placeholders::_1, std::placeholders::_2)); + } + + void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (ecode) { + if (ecode != boost::asio::error::operation_aborted) + Terminate (ecode); + return; + } + if (bytes_transferred <= HTTP_CONNECTION_BUFFER_SIZE) { + m_Buffer[bytes_transferred] = '\0'; + m_BufferLen = bytes_transferred; + } + else { + m_Buffer[HTTP_CONNECTION_BUFFER_SIZE] = '\0'; + m_BufferLen = HTTP_CONNECTION_BUFFER_SIZE; + } + RunRequest(); + Receive (); + } + + void HTTPConnection::RunRequest () + { + HTTPReq request; + int ret = request.parse(m_Buffer); + if (ret < 0) { + m_Buffer[0] = '\0'; + m_BufferLen = 0; + return; /* error */ + } + if (ret == 0) + return; /* need more data */ + + HandleRequest (request); + } + + void HTTPConnection::Terminate (const boost::system::error_code& ecode) + { + if (ecode == boost::asio::error::operation_aborted) + return; + boost::system::error_code ignored_ec; + m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); + m_Socket->close (); + } + + bool HTTPConnection::CheckAuth (const HTTPReq & req) + { + /* method #1: http://user:pass@127.0.0.1:7070/ */ + if (req.uri.find('@') != std::string::npos) { + URL url; + if (url.parse(req.uri) && url.user == user && url.pass == pass) + return true; + } + /* method #2: 'Authorization' header sent */ + auto provided = req.GetHeader ("Authorization"); + if (provided.length () > 0) + { + std::string expected = "Basic " + i2p::data::ToBase64Standard (user + ":" + pass); + if (expected == provided) return true; + } + + LogPrint(eLogWarning, "HTTPServer: Auth failure from ", m_Socket->remote_endpoint().address ()); + return false; + } + + void HTTPConnection::HandleRequest (const HTTPReq & req) + { + std::stringstream s; + std::string content; + HTTPRes res; + + LogPrint(eLogDebug, "HTTPServer: Request: ", req.uri); + + if (needAuth && !CheckAuth(req)) { + res.code = 401; + res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\""); + SendReply(res, content); + return; + } + + bool strictheaders; + i2p::config::GetOption("http.strictheaders", strictheaders); + if (strictheaders) + { + std::string http_hostname; + i2p::config::GetOption("http.hostname", http_hostname); + std::string host = req.GetHeader("Host"); + auto idx = host.find(':'); + /* strip out port so it's just host */ + if (idx != std::string::npos && idx > 0) + { + host = host.substr(0, idx); + } + if (!(host == expected_host || host == http_hostname)) + { + /* deny request as it's from a non whitelisted hostname */ + res.code = 403; + content = "host mismatch"; + SendReply(res, content); + return; + } + } + + // HTML head start + ShowPageHead (s); + if (req.uri.find("page=") != std::string::npos) { + HandlePage (req, res, s); + } else if (req.uri.find("cmd=") != std::string::npos) { + HandleCommand (req, res, s); + } else { + ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); + res.add_header("Refresh", "10"); + } + ShowPageTail (s); + + res.code = 200; + content = s.str (); + SendReply (res, content); + } + + std::map HTTPConnection::m_Tokens; + + uint32_t HTTPConnection::CreateToken () + { + uint32_t token; + RAND_bytes ((uint8_t *)&token, 4); + token &= 0x7FFFFFFF; // clear first bit + auto ts = i2p::util::GetSecondsSinceEpoch (); + for (auto it = m_Tokens.begin (); it != m_Tokens.end (); ) + { + if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT) + it = m_Tokens.erase (it); + else + ++it; + } + m_Tokens[token] = ts; + return token; + } + + void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s) + { + std::map params; + std::string page(""); + URL url; + + url.parse(req.uri); + url.parse_query(params); + page = params["page"]; + + if (page == HTTP_PAGE_TRANSPORTS) + ShowTransports (s); + else if (page == HTTP_PAGE_TUNNELS) + ShowTunnels (s); + else if (page == HTTP_PAGE_COMMANDS) + { + uint32_t token = CreateToken (); + ShowCommands (s, token); + } + else if (page == HTTP_PAGE_TRANSIT_TUNNELS) + ShowTransitTunnels (s); + else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) + ShowLocalDestinations (s); + else if (page == HTTP_PAGE_LOCAL_DESTINATION) + { + uint32_t token = CreateToken (); + ShowLocalDestination (s, params["b32"], token); + } + else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION) + ShowI2CPLocalDestination (s, params["i2cp_id"]); + else if (page == HTTP_PAGE_SAM_SESSIONS) + ShowSAMSessions (s); + else if (page == HTTP_PAGE_SAM_SESSION) + ShowSAMSession (s, params["sam_id"]); + else if (page == HTTP_PAGE_I2P_TUNNELS) + ShowI2PTunnels (s); + else if (page == HTTP_PAGE_LEASESETS) + ShowLeasesSets(s); + else { + res.code = 400; + ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO + return; + } + } + + void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s) + { + std::map params; + URL url; + + url.parse(req.uri); + url.parse_query(params); + + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + std::string redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=commands"; + std::string token = params["token"]; + + if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) + { + ShowError(s, tr("Invalid token")); + return; + } + + std::string cmd = params["cmd"]; + if (cmd == HTTP_COMMAND_RUN_PEER_TEST) + i2p::transport::transports.PeerTest (); + else if (cmd == HTTP_COMMAND_RELOAD_TUNNELS_CONFIG) + i2p::client::context.ReloadConfig (); + else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) + i2p::context.SetAcceptsTunnels (true); + else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) + i2p::context.SetAcceptsTunnels (false); + else if (cmd == HTTP_COMMAND_SHUTDOWN_START) + { + i2p::context.SetAcceptsTunnels (false); +#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) + Daemon.gracefulShutdownInterval = 10*60; +#elif defined(WIN32_APP) + i2p::win32::GracefulShutdown (); +#endif + } + else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) + { + i2p::context.SetAcceptsTunnels (true); +#if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) + Daemon.gracefulShutdownInterval = 0; +#elif defined(WIN32_APP) + i2p::win32::StopGracefulShutdown (); +#endif + } + else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) + { +#ifndef WIN32_APP + Daemon.running = false; +#else + i2p::win32::StopWin32App (); +#endif + } + else if (cmd == HTTP_COMMAND_LOGLEVEL) + { + std::string level = params["level"]; + SetLogLevel (level); + } + else if (cmd == HTTP_COMMAND_KILLSTREAM) + { + std::string b32 = params["b32"]; + uint32_t streamID = std::stoul(params["streamID"], nullptr); + + i2p::data::IdentHash ident; + ident.FromBase32 (b32); + auto dest = i2p::client::context.FindLocalDestination (ident); + + if (streamID) + { + if (dest) + { + if (dest->DeleteStream (streamID)) + s << "" << tr("SUCCESS") << ": " << tr("Stream closed") << "
\r\n
\r\n"; + else + s << "" << tr("ERROR") << ": " << tr("Stream not found or already was closed") << "
\r\n
\r\n"; + } + else + s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
\r\n
\r\n"; + } + else + s << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "
\r\n
\r\n"; + + s << "" << tr("Return to destination page") << "
\r\n"; + s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; + redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32; + res.add_header("Refresh", redirect.c_str()); + return; + } + else if (cmd == HTTP_COMMAND_EXPIRELEASE) + { + std::string b32 = params["b32"]; + std::string lease = params["lease"]; + + i2p::data::IdentHash ident, leaseident; + ident.FromBase32 (b32); + leaseident.FromBase32 (lease); + auto dest = i2p::client::context.FindLocalDestination (ident); + + if (dest) + { + auto leaseset = dest->FindLeaseSet (leaseident); + if (leaseset) + { + leaseset->ExpireLease (); + s << "" << tr("SUCCESS") << ": " << tr("LeaseSet expiration time updated") << "
\r\n
\r\n"; + } + else + s << "" << tr("ERROR") << ": " << tr("LeaseSet is not found or already expired") << "
\r\n
\r\n"; + } + else + s << "" << tr("ERROR") << ": " << tr("Destination not found") << "
\r\n
\r\n"; + + s << "" << tr("Return to destination page") << "
\r\n"; + s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; + redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32; + res.add_header("Refresh", redirect.c_str()); + return; + } + else if (cmd == HTTP_COMMAND_LIMITTRANSIT) + { + uint32_t limit = std::stoul(params["limit"], nullptr); + if (limit >= 2 && limit <= TRANSIT_TUNNELS_LIMIT) + i2p::tunnel::tunnels.SetMaxNumTransitTunnels (limit); + else { + s << "" << tr("ERROR") << ": "; + s << tr("Transit tunnels count must be at least 2 and not exceed %d", TRANSIT_TUNNELS_LIMIT) << "\r\n

\r\n"; + s << "" << tr("Back to commands list") << "\r\n
\r\n"; + s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; + res.add_header("Refresh", redirect.c_str()); + return; + } + } + else if (cmd == HTTP_COMMAND_GET_REG_STRING) + { + std::string b32 = params["b32"]; + std::string name = i2p::http::UrlDecode(params["name"]); + + i2p::data::IdentHash ident; + ident.FromBase32 (b32); + auto dest = i2p::client::context.FindLocalDestination (ident); + + if (dest) + { + std::size_t pos; + pos = name.find (".i2p"); + if (pos == (name.length () - 4)) + { + pos = name.find (".b32.i2p"); + if (pos == std::string::npos) + { + auto signatureLen = dest->GetIdentity ()->GetSignatureLen (); + uint8_t * signature = new uint8_t[signatureLen]; + std::stringstream out; + + out << name << "=" << dest->GetIdentity ()->ToBase64 (); + dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature); + auto sig = i2p::data::ByteStreamToBase64 (signature, signatureLen); + out << "#!sig=" << sig; + s << "" << tr("SUCCESS") << ":
\r\n

\r\n" + "\r\n
\r\n
\r\n" + "" << tr("Register at reg.i2p") << ":\r\n
\r\n" + "" << tr("Description") << ":\r\n\r\n" + "\r\n" + "
\r\n
\r\n"; + delete[] signature; + } + else + s << "" << tr("ERROR") << ": " << tr("Domain can't end with .b32.i2p") << "\r\n
\r\n
\r\n"; + } + else + s << "" << tr("ERROR") << ": " << tr("Domain must end with .i2p") << "\r\n
\r\n
\r\n"; + } + else + s << "" << tr("ERROR") << ": " << tr("Such destination is not found") << "\r\n
\r\n
\r\n"; + + s << "" << tr("Return to destination page") << "\r\n"; + return; + } + else if (cmd == HTTP_COMMAND_SETLANGUAGE) + { + std::string lang = params["lang"]; + std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); + + if (currLang.compare(lang) != 0) + i2p::i18n::SetLanguage(lang); + } + else if (cmd == HTTP_COMMAND_RELOAD_CSS) + { + std::string theme; i2p::config::GetOption("http.theme", theme); + + if (theme != "light" && theme != "black" && theme !="white") LoadExtCSS(theme); + else LoadExtCSS(); + } + else + { + res.code = 400; + ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO + return; + } + + s << "" << tr("SUCCESS") << ": " << tr("Command accepted") << "

\r\n"; + s << "" << tr("Back to commands list") << "
\r\n"; + s << "

" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << ""; + res.add_header("Refresh", redirect.c_str()); + } + + void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) + { + reply.add_header("X-Frame-Options", "SAMEORIGIN"); + reply.add_header("X-Content-Type-Options", "nosniff"); + reply.add_header("X-XSS-Protection", "1; mode=block"); + reply.add_header("Content-Type", "text/html"); + reply.body = content; + + m_SendBuffer = reply.to_string(); + boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (), + std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); + } + + HTTPServer::HTTPServer (const std::string& address, int port): + m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service.get_executor ()), + m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port)), + m_Hostname(address) + { + } + + HTTPServer::~HTTPServer () + { + Stop (); + } + + void HTTPServer::Start () + { + bool needAuth; i2p::config::GetOption("http.auth", needAuth); + std::string user; i2p::config::GetOption("http.user", user); + std::string pass; i2p::config::GetOption("http.pass", pass); + /* generate pass if needed */ + if (needAuth && pass == "") { + uint8_t random[16]; + char alnum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + pass.resize(sizeof(random)); + RAND_bytes(random, sizeof(random)); + for (size_t i = 0; i < sizeof(random); i++) { + pass[i] = alnum[random[i] % (sizeof(alnum) - 1)]; + } + i2p::config::SetOption("http.pass", pass); + LogPrint(eLogInfo, "HTTPServer: Password set to ", pass); + } + + m_IsRunning = true; + m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); + m_Acceptor.listen (); + Accept (); + + LoadExtCSS(); + } + + void HTTPServer::Stop () + { + m_IsRunning = false; + + boost::system::error_code ec; + m_Acceptor.cancel(ec); + if (ec) + LogPrint (eLogDebug, "HTTPServer: Error while cancelling operations on acceptor: ", ec.message ()); + m_Acceptor.close(); + + m_Service.stop (); + if (m_Thread) + { + m_Thread->join (); + m_Thread = nullptr; + } + } + + void HTTPServer::Run () + { + i2p::util::SetThreadName("Webconsole"); + + while (m_IsRunning) + { + try + { + m_Service.run (); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "HTTPServer: Runtime exception: ", ex.what ()); + } + } + } + + void HTTPServer::Accept () + { + auto newSocket = std::make_shared (m_Service); + m_Acceptor.async_accept (*newSocket, std::bind (&HTTPServer::HandleAccept, this, + std::placeholders::_1, newSocket)); + } + + void HTTPServer::HandleAccept(const boost::system::error_code& ecode, + std::shared_ptr newSocket) + { + if (!ecode) + CreateConnection(newSocket); + else + { + if (newSocket) newSocket->close(); + LogPrint(eLogError, "HTTP Server: Error handling accept: ", ecode.message()); + } + if (m_IsRunning) + Accept (); + } + + void HTTPServer::CreateConnection(std::shared_ptr newSocket) + { + auto conn = std::make_shared (m_Hostname, newSocket); + conn->Receive (); + } +} // http +} // i2p