/* * Copyright (c) 2013-2020, 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 "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 and info #include "version.h" namespace i2p { namespace http { const std::string i2pdfavicon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' fill='%23405' rx='5'/%3E%3Ccircle cx='32' cy='32' r='4' fill='%23e580ff'/%3E%3Cg fill='%23d42aff'%3E%3Ccircle cx='20' cy='32' r='4'/%3E%3Ccircle cx='44' cy='32' r='4'/%3E%3Ccircle cx='32' cy='20' r='4'/%3E%3Ccircle cx='32' cy='44' r='4'/%3E%3C/g%3E%3Cg fill='%2380a'%3E%3Ccircle cx='20' cy='56' r='4'/%3E%3Ccircle cx='44' cy='8' r='4'/%3E%3Ccircle cx='44' cy='56' r='4'/%3E%3Ccircle cx='8' cy='44' r='4'/%3E%3Ccircle cx='56' cy='20' r='4'/%3E%3Ccircle cx='56' cy='44' r='4'/%3E%3Ccircle cx='8' cy='20' r='4'/%3E%3Ccircle cx='20' cy='8' r='4'/%3E%3C/g%3E%3Cg fill='%23aa00d4'%3E%3Ccircle cx='32' cy='56' r='4'/%3E%3Ccircle cx='44' cy='20' r='4'/%3E%3Ccircle cx='44' cy='44' r='4'/%3E%3Ccircle cx='8' cy='32' r='4'/%3E%3Ccircle cx='56' cy='32' r='4'/%3E%3Ccircle cx='32' cy='8' r='4'/%3E%3Ccircle cx='20' cy='44' r='4'/%3E%3Ccircle cx='20' cy='20' r='4'/%3E%3C/g%3E%3Cg fill='%23660080'%3E%3Ccircle cx='8' cy='56' r='4'/%3E%3Ccircle cx='56' cy='8' r='4'/%3E%3Ccircle cx='56' cy='56' r='4'/%3E%3Ccircle cx='8' cy='8' r='4'/%3E%3C/g%3E%3C/svg%3E"; // Bundled style const std::string internalCSS = "\r\n"; // for external style sheet std::string externalCSS; static void LoadExtCSS () { std::stringstream s; std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.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_TUNNEL_SUMMARY[] = "tunnel_summary"; const char HTTP_PAGE_LOCAL_TUNNELS[] = "local_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_CONFIG[] = "reload_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_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; static std::string ConvertTime (uint64_t time) { lldiv_t divTime = lldiv(time, 1000); time_t t = divTime.quot; struct tm *tm = localtime(&t); char date[128]; snprintf(date, sizeof(date), "%02d/%02d/%d %02d:%02d:%02d.%03lld", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, divTime.rem); return date; } static void ShowUptime (std::stringstream& s, int seconds) { int num; if ((num = seconds / 86400) > 0) { s << num << " " << tr("day", "days", num) << ", "; seconds -= num * 86400; } if ((num = seconds / 3600) > 0) { s << num << " " << tr("hour", "hours", num) << ", "; seconds -= num * 3600; } if ((num = seconds / 60) > 0) { s << num << " " << tr("minute", "minutes", num) << ", "; seconds -= num * 60; } s << seconds << " " << tr("second", "seconds", seconds); } static void ShowTraffic (std::stringstream& s, uint64_t bytes) { s << std::fixed << std::setprecision(0); auto numKBytes = (double) bytes / 1024; if (numKBytes < 1) { s << std::fixed << std::setprecision(2); s << numKBytes * 1024 << " " << tr(/* tr: Byte */ "B"); } else if (numKBytes < 1024) { s << numKBytes << " " << tr(/* tr: Kibibit */ "K"); } else if (numKBytes < 1024 * 1024) { s << std::fixed << std::setprecision(1); s << numKBytes / 1024 << " " << tr(/* tr: Mebibit */ "M"); } else if (numKBytes < 1024 * 1024 * 1024) { s << std::fixed << std::setprecision(2); s << numKBytes / 1024 / 1024 << " " << tr(/* tr: Gibibit */ "G"); } else { s << numKBytes / 1024 / 1024 / 1024 << " " << tr(/* tr: Tibibit */ "T"); } } static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, double bytes) { std::string state, stateText; switch (eState) { case i2p::tunnel::eTunnelStateBuildReplyReceived : case i2p::tunnel::eTunnelStatePending : state = "building"; break; case i2p::tunnel::eTunnelStateBuildFailed : case i2p::tunnel::eTunnelStateTestFailed : 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 (state == "building") stateText = tr("building"); else if (state == "failed") stateText = tr("failed"); else if (state == "expiring") stateText = tr("expiring"); else if (state == "established") stateText = tr("established"); else stateText = tr("unknown"); s << "" << stateText << ((explr) ? " (" + tr("exploratory") + ")" : "") << ""; s << std::fixed << std::setprecision(0); if (bytes > 1024 * 1024 * 1024) { s << std::fixed << std::setprecision(2); s << " " << (double) (bytes / 1024 / 1024 / 1024) << "G\r\n"; } else if (bytes > 1024 * 1024) { s << std::fixed << std::setprecision(1); s << " " << (double) (bytes / 1024 / 1024) << "M\r\n"; } else if (bytes > 1024) { s << " " << (int) (bytes / 1024) << "K\r\n"; } else { s << " " << (int) (bytes) << "B\r\n"; } } static void SetLogLevel (const std::string& level) { if (level == "none" || 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); // Page language std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language auto it = i2p::i18n::languages.find(currLang); std::string langCode = it->second.ShortCode; // SAM auto sam = i2p::client::context.GetSAMBridge (); std::map params; std::string page(""); URL url; url.parse_query(params); page = params["page"]; std::string token = params["token"]; s << "\r\n" "\r\n" "\r\n" /* TODO: Find something to parse html/template system. This is horrible. */ "\r\n" "\r\n" "\r\n" "Purple I2P | " VERSION "\r\n"; GetStyles(s); s << "\r\n" "\r\n" "
\r\n\r\n" "\r\n" << "\r\n"; } static void ShowPageTail (std::stringstream& s) { s << "
" "" << tr("Main page") << " " // TODO placeholder for graceful shutdown button (requires token) "Shutdown"; // placeholder for toggle transit (requires token) if (i2p::context.AcceptsTunnels ()) { s << "No transit"; } else { s << "Accept transit"; } s << "
\r\n"; if (i2p::context.IsFloodfill ()) s << "" << tr("LeaseSets") << "\r\n"; s << "" << tr("Destinations") << "\r\n" // "" << tr("Services") << "\r\n" // "" << tr("Transit") << "\r\n" "" << tr ("Transports") << "\r\n" "" << tr("Tunnels") << "\r\n"; if (sam && sam->GetSessions ().size ()) { s << "" << tr("SAM Sessions") << "\r\n"; } s << "" << tr("Control") << "\r\n
\r\n" "
\r\n" "\r\n" "\r\n"; } static void ShowError(std::stringstream& s, const std::string& string) { s << "\r\n" << tr("ERROR") << ": " << string << "\r\n"; } static void ShowNetworkStatus (std::stringstream& s, RouterStatus status) { switch (status) { case eRouterStatusOK: s << tr("OK"); break; case eRouterStatusTesting: s << tr("Testing"); 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 eRouterStatusError: { s << tr("Error"); switch (i2p::context.GetError ()) { case eRouterErrorClockSkew: s << " - " << tr("Clock skew"); break; case eRouterErrorOffline: s << " - " << tr("Offline"); break; case eRouterErrorSymmetricNAT: s << " - " << tr("Symmetric NAT"); break; default: ; } break; } default: s << tr("Unknown"); } } void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) { s << "" << tr("Network Status") << ""; ShowNetworkStatus (s, i2p::context.GetStatus ()); if (i2p::context.SupportsV6 ()) { s << "" << tr("IPv6") << " "; ShowNetworkStatus (s, i2p::context.GetStatusV6 ()); } s << "\r\n"; #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) if (auto remains = Daemon.gracefulShutdownInterval) { s << "" << tr("Shutdown") << ""; ShowUptime(s, remains); s << "…\r\n"; } #elif defined(WIN32_APP) if (i2p::win32::g_GracefulShutdownEndtime != 0) { uint16_t remains = (i2p::win32::g_GracefulShutdownEndtime - GetTickCount()) / 1000; s << "" << tr("Shutdown") << ""; ShowUptime(s, remains); s << "…\r\n"; } #endif s << "" << tr("Bandwidth") << ""; s << std::fixed << std::setprecision(0); if (i2p::transport::transports.GetInBandwidth () > 1024*1024*1024 || i2p::transport::transports.GetInBandwidth () < 10240 && i2p::transport::transports.GetInBandwidth () > 0) s << std::fixed << std::setprecision(2); else if (i2p::transport::transports.GetInBandwidth () > 1024*1024) s << std::fixed << std::setprecision(1); s << (double) i2p::transport::transports.GetInBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "K/s"); s << " / "; s << std::fixed << std::setprecision(0); if (i2p::transport::transports.GetOutBandwidth () > 1024*1024*1024 || i2p::transport::transports.GetOutBandwidth () < 10240 && i2p::transport::transports.GetOutBandwidth () > 0) s << std::fixed << std::setprecision(2); else if (i2p::transport::transports.GetOutBandwidth () > 1024*1024) s << std::fixed << std::setprecision(1); s << (double) i2p::transport::transports.GetOutBandwidth () / 1024 << " " << tr(/* tr: Kibibit/s */ "K/s"); s << ""; if ((i2p::context.AcceptsTunnels() || i2p::tunnel::tunnels.CountTransitTunnels()) && (i2p::transport::transports.GetTotalReceivedBytes () > 0)) { s << std::fixed << std::setprecision(0); if (i2p::transport::transports.GetTransitBandwidth () > 1024*1024*1024 || i2p::transport::transports.GetTransitBandwidth () < 10240 && i2p::transport::transports.GetTransitBandwidth () > 0) s << std::fixed << std::setprecision(2); else if (i2p::transport::transports.GetTransitBandwidth () > 1024*1024) s << std::fixed << std::setprecision(1); s << " / "; s << (double) i2p::transport::transports.GetTransitBandwidth () / 1024; s << " " << tr(/* tr: Kibibit/s */ "K/s") << ""; } s << "\r\n"; s << "" << tr("Transferred") << ""; s << std::fixed << std::setprecision(0); if (i2p::transport::transports.GetTotalReceivedBytes () > 1024*1024*1024) s << std::fixed << std::setprecision(2); else if (i2p::transport::transports.GetTotalReceivedBytes () > 1024*1024) s << std::fixed << std::setprecision(1); ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); s << " / "; s << std::fixed << std::setprecision(0); if (i2p::transport::transports.GetTotalSentBytes () > 1024*1024*1024) s << std::fixed << std::setprecision(2); else if (i2p::transport::transports.GetTotalSentBytes () > 1024*1024) s << std::fixed << std::setprecision(1); ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); s << ""; if ((i2p::context.AcceptsTunnels() || i2p::tunnel::tunnels.CountTransitTunnels()) && (i2p::transport::transports.GetTotalReceivedBytes () > 0)) { s << " / "; s << std::fixed << std::setprecision(0); // should set 0 bytes to no decimal places, but doesn't! if (i2p::transport::transports.GetTotalTransitTransmittedBytes () > 1024*1024*1024) s << std::fixed << std::setprecision(2); else if (i2p::transport::transports.GetTotalTransitTransmittedBytes () > 1024*1024) s << std::fixed << std::setprecision(1); ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); s << ""; } s << "\r\n"; s << "" << tr("Build Success") << ""; s << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%\r\n"; s << "" << tr("Routers") << "" << i2p::data::netdb.GetNumRouters () << "\r\n"; s << "" << tr("Floodfills") << "" << i2p::data::netdb.GetNumFloodfills () << "\r\n"; s << "" << tr("LeaseSets") << "" << i2p::data::netdb.GetNumLeaseSets () << "\r\n"; size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); std::string webroot; i2p::config::GetOption("http.webroot", webroot); if (!(i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels())) s << ""; else s << ""; s << "" << tr("Local Tunnels") << "" << std::to_string(clientTunnelCount) << "\r\n"; if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels()) { s << "" << tr("Transit Tunnels") << "" << std::to_string(i2p::tunnel::tunnels.CountTransitTunnels()) << "\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); if (httpproxy || socksproxy || bob || sam || i2cp || i2pcontrol) { s << "" << "" << tr("Router Services") << "\r\n"; s << "
"; if (httpproxy) s << " HTTP " << tr("Proxy") << " "; if (socksproxy) s << " SOCKS " << tr("Proxy") << " "; if (bob) s << " BOB "; if (sam) s << " SAM "; if (i2cp) s << " I2CP "; if (i2pcontrol) s << " I2PControl"; s << "
\r\n\r\n"; } } s << "\r\n"; } void ShowLocalDestinations (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); s << "" << tr("Client 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("Server 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 ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest, uint32_t token) { s << "\r\n"; s << "
\r\n" << "\r\n"; s << "
\r\n
"; s << dest->GetIdentity ()->ToBase64 () << "
\r\n
\r\n
\r\n\r\n"; if (dest->IsEncryptedLeaseSet ()) { i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); s << "" << tr("Encrypted B33 Address") << "\r\n"; s << "" << blinded.ToB33 () << ".b32.i2p\r\n"; } if(dest->IsPublic()) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); auto base32 = dest->GetIdentHash ().ToBase32 (); s << "" << tr("Address Registration String") << "\r\n" "
\r\n" " \r\n" " \r\n" " \r\n" " \r\n" " \r\n" "
\r\n
"; s << tr("Note: Result string can be used only for registering 2LD domains (example.i2p).") << " " << tr("For registering subdomains, please use i2pd-tools."); s << "
\r\n\r\n"; } if(dest->GetNumRemoteLeaseSets()) { s << "\r\n"; s << "
\r\n\r\n" << "\r\n"; s << "
\r\n\r\n\r\n" << "" << "" << "" << "\r\n\r\n"; for(auto& it: dest->GetLeaseSets ()) s << "\r\n" << "" << "" << "\r\n"; s << "\r\n
" << tr("Address") << "" << tr("Type") << "" << tr("EncType") << "
" << it.first.ToBase32 () << "" << (int)it.second->GetStoreType () << "" << (int)it.second->GetEncryptionType () <<"
\r\n
\r\n
\r\n\r\n"; } else s << "" << tr("No LeaseSets currently active") << "\r\n"; auto pool = dest->GetTunnelPool (); if (pool) { s << "\r\n"; s << "
\r\n\r\n" << "\r\n"; s << "
\r\n
\r\n"; for (auto & it : pool->GetInboundTunnels ()) { // inbound tunnels s << "
" << "[" << tr("In") << "] " << ""; it->Print(s); if(it->LatencyIsKnown()) { s << " "; if (it->GetMeanLatency() >= 1000) { s << std::fixed << std::setprecision(2); s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << " "; } else { s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " "; } } else { // placeholder for alignment s << " ---  "; } ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); s << "
\r\n"; } for (auto & it : pool->GetOutboundTunnels ()) { // outbound tunnels s << "
" << "[" << tr("Out") << "] " << ""; it->Print(s); if(it->LatencyIsKnown()) { s << " "; if (it->GetMeanLatency() >= 1000) { s << std::fixed << std::setprecision(2); s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << " "; } else { s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " "; } } else { // placeholder for alignment s << " ---  "; } ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); s << "
\r\n"; } } s << "
\r\n
\r\n
\r\n\r\n"; if (dest->GetNumIncomingTags () > 0) { s << "" << tr("Incoming Session Tags") << " [" << dest->GetNumIncomingTags () << "]\r\n"; } else { s << "" << tr("No Incoming Session Tags") << "\r\n"; } if (!dest->GetSessions ().empty ()) { std::stringstream tmp_s; uint32_t out_tags = 0; s << ""; 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 << "" << tr("Outgoing Session Tags") << " [" << out_tags << "]\r\n" << "\r\n" << "\r\n\r\n\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Count") << "
\r\n\r\n"; } else s << "" << tr("No Outgoing Session Tags") << "\r\n"; auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); if (numECIESx25519Tags > 0) { s << "ECIESx25519"; s << "" << 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\r\n" << tmp_s.str () << "
" << tr("Destination") << "" << tr("Status") << "
\r\n
\r\n
\r\n"; } else s << "" << tr("No Tag Sessions") << "\r\n"; } } void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) { i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); if (dest) { std::string b32Short = b32.substr(0,6); s << "" << tr("Local Destination") << " [" << b32Short << "]\r\n"; } else s << "" << tr("Local Destination") << " [" << tr("Not Found") << "]\r\n"; if (dest) { ShowLeaseSetDestination (s, dest, token); // Print table with streams information s << "\r\n"; s << "
\r\n\r\n" << "\r\n"; s << "
\r\n\r\n\r\n"; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << "\r\n\r\n"; s << "\r\n"; for (const auto& it: dest->GetAllStreams ()) { auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()); std::string streamDestShort = streamDest.substr(0,10) + "…b32.i2p"; s << ""; s << ""; s << ""; s << std::fixed << std::setprecision(0); if (it->GetNumSentBytes () > 1024 * 1024 * 1024) { s << std::fixed << std::setprecision(2); s << ""; } else if (it->GetNumSentBytes () > 1024 * 1024) { s << std::fixed << std::setprecision(2); s << ""; } else { s << ""; } if (it->GetNumReceivedBytes () > 1024 * 1024 * 1024) { s << std::fixed << std::setprecision(2); s << ""; } else if (it->GetNumReceivedBytes () > 1024 * 1024) { s << std::fixed << std::setprecision(1); s << ""; } else { s << ""; } s << ""; s << ""; s << ""; s << ""; s << ""; s << ""; s << "\r\n"; } s << "\r\n
IDDestinationTXRXOutInBufRTTWinStatus
" << " " << it->GetRecvStreamID () << "" << streamDestShort << "" << (double) it->GetNumSentBytes () / 1024 / 1024 / 1024 << "G" << (double) it->GetNumSentBytes () / 1024 / 1024 << "M" << it->GetNumSentBytes () / 1024 << "K" << (double) it->GetNumReceivedBytes () / 1024 / 1024 / 1024 << "G" << (double) it->GetNumReceivedBytes () / 1024 / 1024 << "M" << it->GetNumReceivedBytes () / 1024 << "K" << it->GetSendQueueSize () << "" << it->GetReceiveQueueSize () << "" << it->GetSendBufferSize () << "" << it->GetRTT () << "" << it->GetWindowSize () << "" << (int) it->GetStatus () << "
\r\n
\r\n
\r\n"; } } 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, leaseSet->GetBuffer(), leaseSet->GetBufferLen())); 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" << "\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
\r\n"; } ); s << "\r\n"; // end for each lease set } else if (!i2p::context.IsFloodfill ()) { s << "" << tr("No LeaseSets") << " (" << tr("not floodfill") << ")\r\n"; } else { s << "" << tr("No LeaseSets") << "\r\n"; } } void ShowTunnels (std::stringstream& s) { s << "" << tr("Local Tunnels") << "\r\n"; s << "" << tr("Queue size") << ": " << i2p::tunnel::tunnels.GetQueueSize () << "\r\n"; auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); s << "\r\n"; s << "
\r\n\r\n" << "\r\n"; // TODO: separate client & exploratory tunnels into sections and flag individual services? s << "
\r\n
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { if (it->GetTunnelPool () == ExplPool) { s << "
" << "[" << tr("In") << "] " << ""; it->Print(s); if(it->LatencyIsKnown()) { s << " "; if (it->GetMeanLatency() >= 1000) { s << std::fixed << std::setprecision(2); s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << " "; } else { s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " "; } } else { // placeholder for alignment s << " ---  "; } ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); s << "
\r\n"; } } for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { if (it->GetTunnelPool () == ExplPool) { s << "
" << "[" << tr("Out") << "] " << ""; it->Print(s); if(it->LatencyIsKnown()) { s << " "; if (it->GetMeanLatency() >= 1000) { s << std::fixed << std::setprecision(2); s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << " "; } else { s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " "; } } else { // placeholder for alignment s << " ---  "; } ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); s << "\r\n
\r\n"; } } s << "
\r\n
\r\n
\r\n"; s << "
\r\n\r\n" << "\r\n"; // TODO: flag individual services by name s << "
\r\n
\r\n"; for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { if (it->GetTunnelPool () != ExplPool) { s << "
" << "[" << tr("In") << "] " << ""; it->Print(s); if(it->LatencyIsKnown()) { s << " "; if (it->GetMeanLatency() >= 1000) { s << std::fixed << std::setprecision(2); s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << " "; } else { s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " "; } } else { // placeholder for alignment s << " ---  "; } ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); s << "
\r\n"; } } for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { if (it->GetTunnelPool () != ExplPool) { s << "
" << "[" << tr("Out") << "] " << ""; it->Print(s); if(it->LatencyIsKnown()) { s << " "; if (it->GetMeanLatency() >= 1000) { s << std::fixed << std::setprecision(2); s << (double) it->GetMeanLatency() / 1000 << tr(/* tr: seconds */ "s") << " "; } else { s << it->GetMeanLatency() << tr(/* tr: Milliseconds */ "ms") << " "; } } else { // placeholder for alignment s << " ---  "; } ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); s << "\r\n
\r\n"; } } s << "
\r\n
\r\n
\r\n\r\n"; } void ShowTunnelSummary (std::stringstream& s) { std::string webroot; i2p::config::GetOption("http.webroot", webroot); size_t localInCount = i2p::tunnel::tunnels.CountInboundTunnels(); size_t localOutCount = i2p::tunnel::tunnels.CountOutboundTunnels(); size_t transitCount = i2p::tunnel::tunnels.CountTransitTunnels(); s << "" << tr("Tunnel Summary") << "\r\n"; s << "\r\n"; s << "\r\n" << "" << "" << "\r\n"; s << "" << "\r\n"; if (transitCount > 0) { s << "" << "\r\n"; } s << "
" << tr("Type") << "" << tr("Inbound") << "" << tr("Outbound") << "" << tr("View Details") << "
" << tr("Local") << "" << localInCount << "" << localOutCount << "View
" << tr("Transit") << "" << transitCount << "View
\r\n"; s << ""; ShowI2PTunnels (s); s << "\r\n"; } static void ShowCommands (std::stringstream& s, uint32_t token) { s << "\r\n"; s << "
\r\n\r\n" << "\r\n"; s << "
\r\n\r\n"; s << "\r\n"; s << "\r\n"; if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) s << "\r\n"; auto family = i2p::context.GetFamily (); if (family.length () > 0) s << "\r\n"; if (address->IsNTCP2 () && !address->IsPublishedNTCP2 ()) { s << "\r\n\r\n"; continue; } switch (address->transportStyle) { case i2p::data::RouterInfo::eTransportNTCP: { s << "\r\n"; break; } case i2p::data::RouterInfo::eTransportSSU: { s << "\r\n"; break; } default: s << "\r\n"; } s << "\r\n\r\n"; } s << "\r\n"; s << "\r\n"; s << "
" << tr("Router Identity") << "" << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
" << tr("Router Caps") << "" << i2p::context.GetRouterInfo().GetProperty("caps") << "
" << tr("Router Family") << "" << i2p::context.GetRouterInfo().GetProperty("family") << "
"<< tr("Family") << "" << family << "
\r\n"; for (const auto& address : i2p::context.GetRouterInfo().GetAddresses()) { s << "
NTCP2"; if (address->host.is_v6 ()) s << "v6"; s << "" << tr("supported") << "
NTCP"; if (address->IsPublishedNTCP2 ()) s << "2"; if (address->host.is_v6 ()) s << "v6"; s << "SSU"; if (address->host.is_v6 ()) s << "v6"; s << "" << tr("Unknown") << "" << address->host.to_string() << ":" << address->port << "
" << tr("Uptime") << ""; ShowUptime(s, i2p::context.GetUptime ()); s << "
" << tr("Data path") << "" << i2p::fs::GetUTF8DataDir() << "
\r\n
\r\n
\r\n\r\n"; std::string webroot; i2p::config::GetOption("http.webroot", webroot); s << "" << tr("Router Commands") << "" << "
\r\n"; std::string styleFile = i2p::fs::DataDirPath ("webconsole/style.css"); if (i2p::fs::Exists(styleFile)) { s << "" << tr("Reload external CSS stylesheet") << ""; } s << " " << tr("Run peer test") << "
\r\n"; // s << " Reload config
\r\n"; if (i2p::context.AcceptsTunnels ()) s << " " << tr("Decline transit tunnels") << "
\r\n"; else s << " " << tr("Accept transit tunnels") << "
\r\n"; if (i2p::tunnel::tunnels.CountTransitTunnels()) { #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"; /* TODO graceful shutdown button in header with .notify dialog if transit tunnels active to offer option to shutdown immediately only one option? displayed in the header */ } else { s << " " << tr("Shutdown") << ""; } s << "
\r\n"; s << "\r\n
" << tr("Note: Configuration changes made here persist for the duration of the router session and will not be saved to your config file.") << "
\r\n"; const LogLevel loglevel = i2p::log::Logger().GetLogLevel(); s << "" << tr("Logging Level") << "\r\n"; s << "
"; s << "none\r\n"; s << "error\r\n"; s << "warn\r\n"; s << "info\r\n"; s << "debug" << "
\r\n\r\n"; if (i2p::context.AcceptsTunnels ()) { uint16_t maxTunnels = GetMaxNumTransitTunnels (); s << "" << tr("Maximum Transit Tunnels") << "\r\n"; s << "
\r\n"; s << "
\r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; s << "
\r\n
\r\n\r\n"; } std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language s << "" << tr("Console Display Language") << "\r\n"; s << "
\r\n"; s << "
\r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; s << " \r\n"; s << "
\r\n
\r\n\r\n"; } void ShowTransitTunnels (std::stringstream& s) { if(i2p::tunnel::tunnels.CountTransitTunnels()) { int count = i2p::tunnel::tunnels.GetTransitTunnels().size(); s << "" << tr("Transit Tunnels"); s << " [" << count << "]" << ""; s << "\r\n"; s << "
7) s << "id=\"transit\" "; s << "class=\"list\">\r\n"; for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) { const auto& expiry = i2p::tunnel::tunnels.GetTransitTunnelsExpirationTimeout (); s << "
"; double bytes = it->GetNumTransmittedBytes (); s << std::fixed << std::setprecision(0); if (bytes > 1024 * 1024 * 1024) { s << std::fixed << std::setprecision(2); s << "" << (double) (bytes / 1024 / 1024 / 1024) << "G "; } else if (bytes > 1024 * 1024) { s << std::fixed << std::setprecision(1); s << "" << (double) (bytes / 1024 / 1024) << "M "; } else if (bytes > 1024) { s << "" << (int) (bytes / 1024) << "K "; } else { s << "" << (int) (bytes) << "B "; } // TODO: tunnel expiry per tunnel, not most recent //s << "" << expiry << tr("s" /* translation: seconds */) << " "; s << "" << it->GetTunnelID () << " "; if (std::dynamic_pointer_cast(it)) s << "" << tr("inbound gateway") << ""; else if (std::dynamic_pointer_cast(it)) s << "" << tr("outbound endpoint") << ""; else s << "" << tr("participant") << ""; s << "
\r\n"; } s << "
\r\n"; } else { s << "" << tr("No active transit tunnels") << "\r\n"; } } template static void ShowNTCPTransports (std::stringstream& s, const Sessions& sessions, const std::string name) { std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; for (const auto& it: sessions ) { if (it.second && it.second->IsEstablished () && !it.second->GetRemoteEndpoint ().address ().is_v6 ()) { tmp_s << "
"; if (it.second->IsOutgoing ()) tmp_s << ""; else tmp_s << ""; tmp_s << " "; tmp_s << "" << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << "" << " GetRemoteEndpoint ().address ().to_string () << "\" data-tooltip=\"" << tr("Lookup address on gwhois.org") << "\">" << it.second->GetRemoteEndpoint ().address ().to_string () << ""; tmp_s << std::fixed << std::setprecision(0); if (it.second->GetNumSentBytes () > 1024 * 1024) { tmp_s << std::fixed << std::setprecision(1); tmp_s << " " << (double) it.second->GetNumSentBytes () / 1024 / 1024 << "M"; } else { tmp_s << " " << (double) it.second->GetNumSentBytes () / 1024 << "K"; } tmp_s << std::fixed << std::setprecision(0); if (it.second->GetNumReceivedBytes () > 1024 * 1024) { tmp_s << std::fixed << std::setprecision(1); tmp_s << " " << (double) it.second->GetNumReceivedBytes () / 1024 / 1024 << "M"; } else { tmp_s << " " << (double) it.second->GetNumReceivedBytes () / 1024 << "K"; } tmp_s << "
\r\n" << std::endl; cnt++; } if (it.second && it.second->IsEstablished () && it.second->GetRemoteEndpoint ().address ().is_v6 ()) { tmp_s6 << "
"; if (it.second->IsOutgoing ()) tmp_s6 << ""; else tmp_s6 << ""; tmp_s6 << " "; tmp_s6 << "" << i2p::data::GetIdentHashAbbreviation (it.second->GetRemoteIdentity ()->GetIdentHash ()) << "" << " GetRemoteEndpoint ().address ().to_string () << "\" data-tooltip=\"" << tr("Lookup address on gwhois.org") << "\">" << it.second->GetRemoteEndpoint ().address ().to_string () << ""; tmp_s6 << std::fixed << std::setprecision(0); if (it.second->GetNumSentBytes () > 1024 * 1024) { tmp_s6 << std::fixed << std::setprecision(1); tmp_s6 << " " << (double) it.second->GetNumSentBytes () / 1024 / 1024 << "M"; } else { tmp_s6 << " " << (double) it.second->GetNumSentBytes () / 1024 << "K"; } tmp_s6 << " /"; tmp_s6 << std::fixed << std::setprecision(0); if (it.second->GetNumReceivedBytes () > 1024 * 1024) { tmp_s6 << std::fixed << std::setprecision(1); tmp_s6 << " " << (double) it.second->GetNumReceivedBytes () / 1024 / 1024 << "M"; } else { tmp_s6 << " " << (double) it.second->GetNumReceivedBytes () / 1024 << "K"; } tmp_s6 << "
\r\n" << std::endl; cnt6++; } } if (!tmp_s.str ().empty ()) { s << "
\r\n\r\n
" << tmp_s.str () << "
\r\n
\r\n"; } if (!tmp_s6.str ().empty ()) { s << "
\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 ()) ShowNTCPTransports (s, sessions, "NTCP2"); } auto ssuServer = i2p::transport::transports.GetSSUServer (); if (ssuServer) { auto sessions = ssuServer->GetSessions (); if (!sessions.empty ()) { s << "
\r\n" << "\r\n" << "
\r\n"; for (const auto& it: sessions) { s << "
"; if (it.second->IsOutgoing ()) s << ""; else s << ""; s << " "; auto endpoint = it.second->GetRemoteEndpoint (); // s << " " << endpoint.address ().to_string () << ":" << endpoint.port () << ""; s << " " << endpoint.address ().to_string () << ":" << endpoint.port () << ""; s << std::fixed << std::setprecision(0); if (it.second->GetNumSentBytes () > 1024 * 1024) { s << std::fixed << std::setprecision(1); s << " " << (double) it.second->GetNumSentBytes () / 1024 / 1024 << "M"; } else { s << " " << (double) it.second->GetNumSentBytes () / 1024 << "K"; } s << " /"; s << std::fixed << std::setprecision(0); if (it.second->GetNumReceivedBytes () > 1024 * 1024) { s << std::fixed << std::setprecision(1); s << " " << (double) it.second->GetNumReceivedBytes () / 1024 / 1024 << "M"; } else { s << " " << (double) it.second->GetNumReceivedBytes () / 1024 << "K"; } if (it.second->GetRelayTag ()) s << " " << it.second->GetRelayTag () << ""; s << "
\r\n" << std::endl; } s << "
\r\n
\r\n"; } auto sessions6 = ssuServer->GetSessionsV6 (); if (!sessions6.empty ()) { s << "
\r\n\r\n" << "\r\n" << "
\r\n"; for (const auto& it: sessions6) { s << "
"; if (it.second->IsOutgoing ()) s << ""; else s << ""; s << " "; auto endpoint = it.second->GetRemoteEndpoint (); s << " " << endpoint.address ().to_string () << ":" << endpoint.port () << ""; s << std::fixed << std::setprecision(0); if (it.second->GetNumSentBytes () > 1024 * 1024) { s << std::fixed << std::setprecision(1); s << " " << (double) it.second->GetNumSentBytes () / 1024 / 1024 << "M"; } else { s << " " << (double) it.second->GetNumSentBytes () / 1024 << "K"; } s << " /"; s << std::fixed << std::setprecision(0); if (it.second->GetNumReceivedBytes () > 1024 * 1024) { s << std::fixed << std::setprecision(1); s << " " << (double) it.second->GetNumReceivedBytes () / 1024 / 1024 << "M"; } else { s << " " << (double) it.second->GetNumReceivedBytes () / 1024 << "K"; } if (it.second->GetRelayTag ()) s << " " << it.second->GetRelayTag () << ""; s << "\r\n
\r\n" << std::endl; } s << "
\r\n
\r\n\r\n"; } } } 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
\r\n"; for (auto& it: sam->GetSessions ()) { auto& name = it.second->GetLocalDestination ()->GetNickname (); s << "\r\n" << std::endl; } s << "
\r\n\r\n"; } else s << "" << tr("No active SAM sessions") << "\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; } auto session = sam->FindSession (id); 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
\r\n"; auto& ident = session->GetLocalDestination ()->GetIdentHash(); s << "\r\n"; s << "
\r\n"; s << "" << tr("Streams") << "\r\n
\r\n"; for (const auto& it: sam->ListSockets(id)) { s << "
"; switch (it->GetSocketType ()) { case i2p::client::eSAMSocketTypeSession : s << "session"; break; case i2p::client::eSAMSocketTypeStream : s << "stream"; break; case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; case i2p::client::eSAMSocketTypeForward : s << "forward"; break; default: s << "unknown"; break; } 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); s << "" << tr("Service Tunnels") << ""; s << "\r\n"; s << "
\r\n\r\n" << "\r\n"; s << "
\r\n"; s << "
\r\n"; for (auto& it: i2p::client::context.GetClientTunnels ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); s << "
"; s << it.second->GetName () << " "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } auto httpProxy = i2p::client::context.GetHttpProxy (); if (httpProxy) { auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); s << "
"; s << "HTTP " << tr("Proxy") << " "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; } auto socksProxy = i2p::client::context.GetSocksProxy (); 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
\r\n"; auto& serverTunnels = i2p::client::context.GetServerTunnels (); if (!serverTunnels.empty ()) { s << "\r\n\r\n\r\n"; s << "
\r\n\r\n" << "\r\n"; s << "
\r\n"; s << "
\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
\r\n\r\n"; } auto& clientForwards = i2p::client::context.GetClientForwards (); if (!clientForwards.empty ()) { s << "\r\n\r\n\r\n"; s << "
\r\n\r\n" << "\r\n"; s << "
\r\n"; s << "
\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
\r\n\r\n"; } auto& serverForwards = i2p::client::context.GetServerForwards (); if (!serverForwards.empty ()) { s << "\r\n\r\n\r\n"; s << "
\r\n\r\n" << "\r\n"; s << "
\r\n"; s << "
\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
\r\n\r\n"; } // s << "
\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; } m_Buffer[bytes_transferred] = '\0'; m_BufferLen = bytes_transferred; 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("summary") != std::string::npos || req.uri.find("commands") != std::string::npos || (req.uri.find("local_destinations") != std::string::npos && req.uri.find("b32") == std::string::npos)) res.add_header("Refresh", "10"); 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", "5"); } 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_TUNNEL_SUMMARY) { ShowTunnelSummary (s); /* ShowTunnels (s); ShowI2PTunnels (s); ShowTransitTunnels (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_LOCAL_TUNNELS) { ShowTunnels (s); } else if (page == HTTP_PAGE_LEASESETS) { ShowLeasesSets(s); } else { res.code = 400; ShowError(s, tr("Unknown page") + ": " + page); 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 = "2; 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_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"; else s << "" << "" << tr("ERROR") << ": " << tr("Stream not found or already was closed") << "\r\n"; } else s << "" << "" << tr("ERROR") << ": " << tr("Destination not found") << "\r\n"; } else s << "" << "" << tr("ERROR") << ": " << tr("StreamID can't be null") << "\r\n"; redirect = "2; 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 > 0 && limit <= 65535) SetMaxNumTransitTunnels (limit); else { s << "" << "" << tr("ERROR") << ": " << tr("Transit tunnels count must not exceed 65535") << "\r\n"; 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]; char * sig = new char[signatureLen*2]; std::stringstream out; out << name << "=" << dest->GetIdentity ()->ToBase64 (); dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature); auto len = i2p::data::ByteStreamToBase64 (signature, signatureLen, sig, signatureLen*2); sig[len] = 0; 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"; delete[] signature; delete[] sig; } else s << "" << tr("ERROR") << ": " << tr("Domain can't end with .b32.i2p") << "\r\n"; } else s << "" << tr("ERROR") << ": " << tr("Domain must end with .i2p") << "\r\n"; } else s << "" << tr("ERROR") << ": " << tr("No such destination found") << "\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 styleFile = i2p::fs::DataDirPath ("webconsole/style.css"); if (i2p::fs::Exists(styleFile)) LoadExtCSS(); else ShowError(s, tr("No CSS file found on disk!")); } else { res.code = 400; ShowError(s, tr("Unknown command") + ": " + cmd); return; } s << "" << ""; if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) s << tr("Immediate router shutdown initiated"); else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) s << tr("Router shutdown cancelled"); else if (cmd == HTTP_COMMAND_RELOAD_CSS) { s << tr("Console CSS stylesheet reloaded"); } else if (cmd == HTTP_COMMAND_LIMITTRANSIT) s << tr("Maximum transit tunnels configured for session"); else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) s << tr("Transit tunnels enabled for session"); else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) s << tr("Transit tunnels disabled for session"); else if (cmd == HTTP_COMMAND_SETLANGUAGE) s << tr("Console language updated"); else if (cmd == HTTP_COMMAND_LOGLEVEL) s << tr("Log level updated for session"); else s << "" << tr("SUCCESS") << ": " << tr("Command accepted"); s << "\r\n"; 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.add_header("Server", "i2pd " VERSION " webconsole" ); reply.body = content; m_SendBuffer = reply.to_string(); boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), 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), m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::address::from_string(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; 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) { if(newSocket) newSocket->close(); LogPrint(eLogError, "HTTP Server: Error handling accept ", ecode.message()); if(ecode != boost::asio::error::operation_aborted) Accept(); return; } CreateConnection(newSocket); Accept (); } void HTTPServer::CreateConnection(std::shared_ptr newSocket) { auto conn = std::make_shared (m_Hostname, newSocket); conn->Receive (); } } // http } // i2p