HTTPServer.cpp
1 /* 2 * Copyright (c) 2013-2026, The PurpleI2P Project 3 * 4 * This file is part of Purple i2pd project and licensed under BSD3 5 * 6 * See full license text in LICENSE file at top of project tree 7 */ 8 9 #include <time.h> 10 #include <iomanip> 11 #include <sstream> 12 #include <thread> 13 #include <memory> 14 #include <regex> 15 16 #include <boost/asio.hpp> 17 #include <boost/algorithm/string.hpp> 18 19 #include "Base.h" 20 #include "FS.h" 21 #include "Log.h" 22 #include "Config.h" 23 #include "Tunnel.h" 24 #include "Transports.h" 25 #include "NetDb.hpp" 26 #include "HTTP.h" 27 #include "LeaseSet.h" 28 #include "Destination.h" 29 #include "RouterContext.h" 30 #include "ClientContext.h" 31 #include "HTTPServer.h" 32 #include "Daemon.h" 33 #include "util.h" 34 #include "ECIESX25519AEADRatchetSession.h" 35 #include "I18N.h" 36 37 #ifdef WIN32_APP 38 #include "Win32App.h" 39 #endif 40 41 // For image, style and info 42 #include "version.h" 43 #include "HTTPServerResources.h" 44 45 namespace i2p { 46 namespace http { 47 48 static void LoadExtCSS (std::string fileName = "style") 49 { 50 std::stringstream s; 51 std::string styleFile = i2p::fs::DataDirPath ("webconsole/"+fileName+".css"); 52 if (i2p::fs::Exists(styleFile)) { 53 std::ifstream f(styleFile, std::ifstream::binary); 54 s << f.rdbuf(); 55 externalCSS = s.str(); 56 } else if (externalCSS.length() != 0) { // clean up external style if file was removed 57 externalCSS = ""; 58 } 59 } 60 61 static void GetStyles (std::stringstream& s) 62 { 63 if (externalCSS.length() != 0) 64 s << "<style>\r\n" << externalCSS << "</style>\r\n"; 65 else 66 s << internalCSS; 67 } 68 69 const char HTTP_PAGE_TUNNELS[] = "tunnels"; 70 const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; 71 const char HTTP_PAGE_TRANSPORTS[] = "transports"; 72 const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations"; 73 const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination"; 74 const char HTTP_PAGE_I2CP_LOCAL_DESTINATION[] = "i2cp_local_destination"; 75 const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; 76 const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; 77 const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; 78 const char HTTP_PAGE_COMMANDS[] = "commands"; 79 const char HTTP_PAGE_LEASESETS[] = "leasesets"; 80 const char HTTP_COMMAND_ENABLE_TRANSIT[] = "enable_transit"; 81 const char HTTP_COMMAND_DISABLE_TRANSIT[] = "disable_transit"; 82 const char HTTP_COMMAND_SHUTDOWN_START[] = "shutdown_start"; 83 const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; 84 const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; 85 const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; 86 const char HTTP_COMMAND_RELOAD_TUNNELS_CONFIG[] = "reload_tunnels_config"; 87 const char HTTP_COMMAND_LOGLEVEL[] = "set_loglevel"; 88 const char HTTP_COMMAND_KILLSTREAM[] = "closestream"; 89 const char HTTP_COMMAND_LIMITTRANSIT[] = "limittransit"; 90 const char HTTP_COMMAND_GET_REG_STRING[] = "get_reg_string"; 91 const char HTTP_COMMAND_SETLANGUAGE[] = "setlanguage"; 92 const char HTTP_COMMAND_RELOAD_CSS[] = "reload_css"; 93 const char HTTP_COMMAND_EXPIRELEASE[] = "expirelease"; 94 95 static std::string ConvertTime (uint64_t time) 96 { 97 struct tm caltime; 98 lldiv_t divTime = lldiv(time, 1000); 99 time_t t = divTime.quot; 100 #ifdef _WIN32 101 localtime_s(&caltime, &t); 102 #else 103 localtime_r(&t, &caltime); 104 #endif 105 char date[128]; 106 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); 107 return date; 108 } 109 110 static void ShowUptime (std::stringstream& s, int seconds) 111 { 112 int num; 113 114 if ((num = seconds / 86400) > 0) { 115 s << ntr("%d day", "%d days", num, num) << ", "; 116 seconds -= num * 86400; 117 } 118 if ((num = seconds / 3600) > 0) { 119 s << ntr("%d hour", "%d hours", num, num) << ", "; 120 seconds -= num * 3600; 121 } 122 if ((num = seconds / 60) > 0) { 123 s << ntr("%d minute", "%d minutes", num, num) << ", "; 124 seconds -= num * 60; 125 } 126 s << ntr("%d second", "%d seconds", seconds, seconds); 127 } 128 129 static void ShowTraffic (std::stringstream& s, uint64_t bytes) 130 { 131 s << std::fixed << std::setprecision(2); 132 auto numKBytes = (double) bytes / 1024; 133 if (numKBytes < 1024) 134 /* Kibibyte */ 135 s << numKBytes << " KiB"; 136 else if (numKBytes < 1024 * 1024) 137 /* Mebibyte */ 138 s << (numKBytes / 1024) << " MiB" ; 139 else 140 /* Gibibyte */ 141 s << (numKBytes / 1024 / 1024) << " GiB"; 142 } 143 144 static void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, bool explr, int bytes) 145 { 146 std::string state; 147 std::string_view stateText; 148 switch (eState) 149 { 150 case i2p::tunnel::eTunnelStateBuildReplyReceived : 151 case i2p::tunnel::eTunnelStatePending : state = "building"; break; 152 case i2p::tunnel::eTunnelStateBuildFailed : state = "failed"; stateText = "declined"; break; 153 case i2p::tunnel::eTunnelStateTestFailed : state = "failed"; stateText = "test failed"; break; 154 case i2p::tunnel::eTunnelStateFailed : state = "failed"; break; 155 case i2p::tunnel::eTunnelStateExpiring : state = "expiring"; break; 156 case i2p::tunnel::eTunnelStateEstablished : state = "established"; break; 157 default: state = "unknown"; break; 158 } 159 if (stateText.empty ()) stateText = tr(state); 160 161 s << "<span class=\"tunnel " << state << "\"> " << stateText << ((explr) ? " (" + std::string(tr("exploratory")) + ")" : "") << "</span>, "; // TODO: 162 ShowTraffic(s, bytes); 163 s << "\r\n"; 164 } 165 166 static void SetLogLevel (const std::string& level) 167 { 168 if (level == "none" || level == "critical" || level == "error" || level == "warn" || level == "info" || level == "debug") 169 i2p::log::Logger().SetLogLevel(level); 170 else { 171 LogPrint(eLogError, "HTTPServer: Unknown loglevel set attempted"); 172 return; 173 } 174 i2p::log::Logger().Reopen (); 175 } 176 177 static void ShowPageHead (std::stringstream& s) 178 { 179 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 180 std::string theme; i2p::config::GetOption("http.theme", theme); 181 182 const auto isThemeRegex = std::regex("^(white|black|light)"); 183 if (!std::regex_search(theme, isThemeRegex)) 184 { 185 LoadExtCSS(theme); 186 } 187 else 188 { 189 LoadExtCSS(); 190 } 191 192 // Page language 193 std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); // get current used language 194 auto it = i2p::i18n::languages.find(currLang); 195 // fallback to default English, if language is not supported 196 if (it == i2p::i18n::languages.end()) { 197 it = i2p::i18n::languages.find("english"); 198 } 199 std::string langCode = it->second.ShortCode; 200 201 // Right to Left language option 202 bool rtl = i2p::client::context.GetLanguage ()->GetRTL(); 203 204 s << 205 "<!DOCTYPE html>\r\n" 206 "<html lang=\"" << langCode << "\"" << (rtl ? " dir=\"rtl\"" : "") << ">\r\n" 207 "<head>\r\n" /* TODO: Find something to parse html/template system. This is horrible. */ 208 " <meta charset=\"UTF-8\">\r\n" 209 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n" 210 " <link rel=\"shortcut icon\" href=\"" << itoopieFavicon << "\">\r\n" 211 " <title>" << tr(/* tr: Webconsole page title */ "Purple I2P Webconsole") << "</title>\r\n"; 212 GetStyles(s); 213 if (theme == "black") 214 { 215 s << 216 "<style>:root {\r\n" 217 "--main-bg-color: #242424;\r\n" 218 "--main-text-color: #17ab5c;\r\n" 219 "--main-link-color: #bf64b7;\r\n" 220 "--main-link-hover-color: #000000;\r\n" 221 "}\r\n</style>"; 222 223 } 224 s << 225 "</head>\r\n" 226 "<body>\r\n" 227 "<div class=\"header\">" << tr("<b>i2pd</b> webconsole") << "</div>\r\n" 228 "<div class=\"wrapper\">\r\n" 229 "<div class=\"menu\">\r\n" 230 " <a href=\"" << webroot << "\">" << tr("Main page") << "</a><br><br>\r\n" 231 " <a href=\"" << webroot << "?page=" << HTTP_PAGE_COMMANDS << "\">" << tr("Router commands") << "</a><br>\r\n" 232 " <a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATIONS << "\">" << tr("Local Destinations") << "</a><br>\r\n"; 233 if (i2p::context.IsFloodfill ()) 234 s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_LEASESETS << "\">" << tr("LeaseSets") << "</a><br>\r\n"; 235 s << 236 " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TUNNELS << "\">" << tr("Tunnels") << "</a><br>\r\n"; 237 if (i2p::context.AcceptsTunnels () || i2p::tunnel::tunnels.CountTransitTunnels()) 238 s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSIT_TUNNELS << "\">" << tr("Transit Tunnels") << "</a><br>\r\n"; 239 s << 240 " <a href=\"" << webroot << "?page=" << HTTP_PAGE_TRANSPORTS << "\">" << tr("Transports") << "</a><br>\r\n" 241 " <a href=\"" << webroot << "?page=" << HTTP_PAGE_I2P_TUNNELS << "\">" << tr("I2P tunnels") << "</a><br>\r\n"; 242 if (i2p::client::context.GetSAMBridge ()) 243 s << " <a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSIONS << "\">" << tr("SAM sessions") << "</a><br>\r\n"; 244 s << 245 "</div>\r\n\r\n" 246 "<div class=\"content\">\r\n\r\n"; 247 } 248 249 static void ShowPageTail (std::stringstream& s) 250 { 251 s << 252 "</div>\r\n</div>\r\n" 253 "</body>\r\n" 254 "</html>\r\n"; 255 } 256 257 static void ShowError(std::stringstream& s, std::string_view string) 258 { 259 s << "<b>" << tr("ERROR") << ":</b> " << string << "<br>\r\n"; 260 } 261 262 static void ShowNetworkStatus (std::stringstream& s, RouterStatus status, bool testing, RouterError error) 263 { 264 switch (status) 265 { 266 case eRouterStatusOK: s << tr("OK"); break; 267 case eRouterStatusFirewalled: s << tr("Firewalled"); break; 268 case eRouterStatusUnknown: s << tr("Unknown"); break; 269 case eRouterStatusProxy: s << tr("Proxy"); break; 270 case eRouterStatusMesh: s << tr("Mesh"); break; 271 case eRouterStatusStan: s << tr("Stan"); break; 272 default: s << tr("Unknown"); 273 } 274 if (testing) 275 s << " (" << tr("Testing") << ")"; 276 if (error != eRouterErrorNone) 277 { 278 switch (error) 279 { 280 case eRouterErrorClockSkew: 281 s << " - " << tr("Clock skew"); 282 break; 283 case eRouterErrorOffline: 284 s << " - " << tr("Offline"); 285 break; 286 case eRouterErrorSymmetricNAT: 287 s << " - " << tr("Symmetric NAT"); 288 break; 289 case eRouterErrorFullConeNAT: 290 s << " - " << tr("Full cone NAT"); 291 break; 292 case eRouterErrorNoDescriptors: 293 s << " - " << tr("No Descriptors"); 294 break; 295 default: ; 296 } 297 } 298 } 299 300 void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) 301 { 302 s << "<b>" << tr("Uptime") << ":</b> "; 303 ShowUptime(s, i2p::context.GetUptime ()); 304 s << "<br>\r\n"; 305 if (i2p::context.SupportsV4 () || i2p::context.GetStatus () != eRouterStatusUnknown) // don't show Unknown for ipv6-only 306 { 307 s << "<b>" << tr("Network status") << ":</b> "; 308 ShowNetworkStatus (s, i2p::context.GetStatus (), i2p::context.GetTesting(), i2p::context.GetError ()); 309 s << "<br>\r\n"; 310 } 311 if (i2p::context.SupportsV6 ()) 312 { 313 s << "<b>" << tr("Network status v6") << ":</b> "; 314 ShowNetworkStatus (s, i2p::context.GetStatusV6 (), i2p::context.GetTestingV6(), i2p::context.GetErrorV6 ()); 315 s << "<br>\r\n"; 316 } 317 auto remains = Daemon.GetGracefulShutdownInterval (); 318 if (remains > 0) 319 { 320 s << "<b>" << tr("Stopping in") << ":</b> "; 321 ShowUptime(s, remains); 322 s << "<br>\r\n"; 323 } 324 auto family = i2p::context.GetFamily (); 325 if (family.length () > 0) 326 s << "<b>"<< tr("Family") << ":</b> " << family << "<br>\r\n"; 327 s << "<b>" << tr("Tunnel creation success rate") << ":</b> " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%<br>\r\n"; 328 bool isTotalTCSR; 329 i2p::config::GetOption("http.showTotalTCSR", isTotalTCSR); 330 if (isTotalTCSR) { 331 s << "<b>" << tr("Total tunnel creation success rate") << ":</b> " << i2p::tunnel::tunnels.GetTotalTunnelCreationSuccessRate() << "%<br/>\r\n"; 332 } 333 s << std::fixed << std::setprecision(2); 334 /* Kibibyte/s */ 335 s << "<b>" << tr("Received") << ":</b> "; 336 ShowTraffic (s, i2p::transport::transports.GetTotalReceivedBytes ()); 337 s << " (" << ((double) i2p::transport::transports.GetInBandwidth15s () / 1024) << " KiB/s)" << "<br>\r\n"; 338 s << "<b>" << tr("Sent") << ":</b> "; 339 ShowTraffic (s, i2p::transport::transports.GetTotalSentBytes ()); 340 s << " (" << ((double) i2p::transport::transports.GetOutBandwidth15s () / 1024) << " KiB/s)" << "<br>\r\n"; 341 s << "<b>" << tr("Transit") << ":</b> "; 342 ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); 343 s << " (" << ((double) i2p::transport::transports.GetTransitBandwidth15s () / 1024) << " KiB/s)" << "<br>\r\n"; 344 s << "<b>" << tr("Data path") << ":</b> " << i2p::fs::GetUTF8DataDir() << "<br>\r\n"; 345 s << "<div class='slide'>"; 346 if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { 347 s << "<label for=\"slide-info\">" << tr("Hidden content. Press on text to see.") << "</label>\r\n"; 348 s << "<input type=\"checkbox\" id=\"slide-info\" />\r\n"; 349 s << "<div class=\"slidecontent\">\r\n"; 350 } 351 if (includeHiddenContent) 352 { 353 s << "<b>" << tr("Router Ident") << ":</b> " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "<br>\r\n"; 354 if (!i2p::context.GetRouterInfo().GetProperty("family").empty()) 355 s << "<b>" << tr("Router Family") << ":</b> " << i2p::context.GetRouterInfo().GetProperty("family") << "<br>\r\n"; 356 s << "<b>" << tr("Router Caps") << ":</b> " << i2p::context.GetRouterInfo().GetProperty("caps") << "<br>\r\n"; 357 s << "<b>" << tr("Version") << ":</b> " VERSION "<br>\r\n"; 358 s << "<b>"<< tr("Our external address") << ":</b>" << "<br>\r\n"; 359 s << "<table class=\"extaddr\">\r\n<tbody>\r\n"; 360 auto addresses = i2p::context.GetRouterInfo().GetAddresses (); 361 if (addresses) 362 { 363 for (const auto& address : *addresses) 364 { 365 if (!address) continue; 366 s << "<tr>\r\n<td>"; 367 switch (address->transportStyle) 368 { 369 case i2p::data::RouterInfo::eTransportNTCP2: 370 s << "NTCP2"; 371 break; 372 case i2p::data::RouterInfo::eTransportSSU2: 373 s << "SSU2"; 374 break; 375 default: 376 s << tr("Unknown"); 377 } 378 bool v6 = address->IsV6 (); 379 if (v6) 380 { 381 if (address->IsV4 ()) s << "v4"; 382 s << "v6"; 383 } 384 s << "</td>\r\n"; 385 if (address->published) { 386 s << "<td style=\"padding-left: 0.5em;\">"; 387 s << (v6 ? "[" : "") << address->host.to_string() << (v6 ? "]:" : ":"); 388 s << address->port << "</td>\r\n"; 389 } 390 else 391 { 392 /* tr: Shown when router doesn't publish itself and have "Firewalled" state */ 393 s << "<td style=\"padding-left: 0.5em;\">" << tr("supported"); 394 if (address->port) 395 s << " :" << address->port; 396 s << "</td>\r\n"; 397 } 398 s << "</tr>\r\n"; 399 } 400 } 401 s << "</tbody>\r\n</table>\r\n"; 402 } 403 if ((outputFormat == OutputFormatEnum::forWebConsole) || !includeHiddenContent) { 404 s << "</div>\r\n"; // class slidecontent 405 } 406 s << "</div>\r\n"; // class slide 407 if (outputFormat == OutputFormatEnum::forQtUi) { 408 s << "<br>"; 409 } 410 s << "<b>" << tr("Routers") << ":</b> " << i2p::data::netdb.GetNumRouters () << " "; 411 s << "<b>" << tr("Floodfills") << ":</b> " << i2p::data::netdb.GetNumFloodfills () << " "; 412 s << "<b>" << tr("LeaseSets") << ":</b> " << i2p::data::netdb.GetNumLeaseSets () << "<br>\r\n"; 413 414 size_t clientTunnelCount = i2p::tunnel::tunnels.CountOutboundTunnels(); 415 clientTunnelCount += i2p::tunnel::tunnels.CountInboundTunnels(); 416 size_t transitTunnelCount = i2p::tunnel::tunnels.CountTransitTunnels(); 417 418 s << "<b>" << tr("Client Tunnels") << ":</b> " << std::to_string(clientTunnelCount) << " "; 419 s << "<b>" << tr("Transit Tunnels") << ":</b> " << std::to_string(transitTunnelCount) << "<br>\r\n<br>\r\n"; 420 421 if (outputFormat==OutputFormatEnum::forWebConsole) { 422 bool httpproxy = i2p::client::context.GetHttpProxy () ? true : false; 423 bool socksproxy = i2p::client::context.GetSocksProxy () ? true : false; 424 bool bob = i2p::client::context.GetBOBCommandChannel () ? true : false; 425 bool sam = i2p::client::context.GetSAMBridge () ? true : false; 426 bool i2cp = i2p::client::context.GetI2CPServer () ? true : false; 427 bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); 428 s << "<table class=\"services\">\r\n<caption>" << tr("Services") << "</caption>\r\n<tbody>\r\n"; 429 s << "<tr><td>" << "HTTP " << tr("Proxy") << "</td><td class='" << (httpproxy ? "enabled" : "disabled") << "'>" << (httpproxy ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; 430 s << "<tr><td>" << "SOCKS " << tr("Proxy") << "</td><td class='" << (socksproxy ? "enabled" : "disabled") << "'>" << (socksproxy ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; 431 s << "<tr><td>" << "BOB" << "</td><td class='" << (bob ? "enabled" : "disabled") << "'>" << (bob ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; 432 s << "<tr><td>" << "SAM" << "</td><td class='" << (sam ? "enabled" : "disabled") << "'>" << (sam ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; 433 s << "<tr><td>" << "I2CP" << "</td><td class='" << (i2cp ? "enabled" : "disabled") << "'>" << (i2cp ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; 434 s << "<tr><td>" << "I2PControl" << "</td><td class='" << (i2pcontrol ? "enabled" : "disabled") << "'>" << (i2pcontrol ? tr("Enabled") : tr("Disabled")) << "</td></tr>\r\n"; 435 s << "</tbody>\r\n</table>\r\n<br>\r\n"; 436 } 437 } 438 439 void ShowLocalDestinations (std::stringstream& s) 440 { 441 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 442 s << "<b>" << tr("Local Destinations") << ":</b><br>\r\n<div class=\"list\">\r\n"; 443 for (auto& it: i2p::client::context.GetDestinations ()) 444 { 445 auto ident = it.second->GetIdentHash (); 446 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 447 s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a></div>\r\n" << std::endl; 448 } 449 s << "</div>\r\n<br>\r\n"; 450 451 auto i2cpServer = i2p::client::context.GetI2CPServer (); 452 if (i2cpServer && !(i2cpServer->GetSessions ().empty ())) 453 { 454 s << "<br><b>I2CP "<< tr("Local Destinations") << ":</b><br>\r\n<div class=\"list\">\r\n"; 455 for (auto& it: i2cpServer->GetSessions ()) 456 { 457 auto dest = it.second->GetDestination (); 458 if (dest) 459 { 460 auto ident = dest->GetIdentHash (); 461 auto& name = dest->GetNickname (); 462 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_I2CP_LOCAL_DESTINATION << "&i2cp_id=" << it.first << "\">[ "; 463 s << name << " ]</a> ⇔ " << i2p::client::context.GetAddressBook ().ToAddress(ident) <<"</div>\r\n" << std::endl; 464 } 465 } 466 s << "</div>\r\n<br>\r\n"; 467 } 468 } 469 470 static void ShowHop(std::stringstream& s, const i2p::data::IdentityEx& ident) 471 { 472 auto identHash = ident.GetIdentHash(); 473 auto router = i2p::data::netdb.FindRouter(identHash); 474 s << i2p::data::GetIdentHashAbbreviation(identHash); 475 if (router) 476 s << "<small style=\"color:gray\"> " << router->GetBandwidthCap() << "</small>"; 477 } 478 479 static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr<const i2p::client::LeaseSetDestination> dest, uint32_t token) 480 { 481 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 482 s << "<b>Base32:</b><br>\r\n<textarea readonly cols=\"80\" rows=\"1\">"; 483 s << dest->GetIdentHash ().ToBase32 () << "</textarea><br>\r\n<br>\r\n"; 484 485 s << "<b>Base64:</b><br>\r\n<textarea readonly cols=\"80\" rows=\"8\">"; 486 s << dest->GetIdentity ()->ToBase64 () << "</textarea><br>\r\n<br>\r\n"; 487 488 if (dest->IsEncryptedLeaseSet ()) 489 { 490 i2p::data::BlindedPublicKey blinded (dest->GetIdentity (), dest->IsPerClientAuth ()); 491 s << "<div class='slide'><label for='slide-b33'><b>" << tr("Encrypted B33 address") << ":</b></label>\r\n"; 492 s << "<input type=\"checkbox\" id=\"slide-b33\" />\r\n"; 493 s << "<div class=\"slidecontent\">\r\n"; 494 s << blinded.ToB33 () << ".b32.i2p<br>\r\n"; 495 s << "</div>\r\n</div>\r\n"; 496 } 497 498 if (dest->IsPublic() && token && !dest->IsEncryptedLeaseSet ()) 499 { 500 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 501 s << "<div class='slide'><label for='slide-regaddr'><b>" << tr("Address registration line") << "</b></label>\r\n" 502 "<input type=\"checkbox\" id=\"slide-regaddr\" />\r\n" 503 "<div class=\"slidecontent\">\r\n" 504 "<form method=\"get\" action=\"" << webroot << "\">\r\n" 505 " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_GET_REG_STRING << "\">\r\n" 506 " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n" 507 " <input type=\"hidden\" name=\"b32\" value=\"" << dest->GetIdentHash ().ToBase32 () << "\">\r\n" 508 " <b>" << tr("Domain") << ":</b>\r\n<input type=\"text\" maxlength=\"67\" name=\"name\" placeholder=\"domain.i2p\" required>\r\n" 509 " <button type=\"submit\">" << tr("Generate") << "</button>\r\n" 510 "</form>\r\n" 511 "<small>" << tr("<b>Note:</b> result string can be used only for registering 2LD domains (example.i2p). For registering subdomains please use i2pd-tools.") << "</small>\r\n" 512 "</div>\r\n</div>\r\n<br>\r\n"; 513 } 514 515 if (dest->GetNumRemoteLeaseSets()) 516 { 517 s << "<div class='slide'><label for='slide-lease'><b>" << tr("LeaseSets") << ":</b> <i>" << dest->GetNumRemoteLeaseSets () 518 << "</i></label>\r\n<input type=\"checkbox\" id=\"slide-lease\" />\r\n" 519 << "<div class=\"slidecontent\">\r\n" 520 << "<table>\r\n<thead><tr>" 521 << "<th>" << tr("Address") << "</th>" 522 << "<th style=\"width:5px;\"> </th>" // LeaseSet expiration button column 523 << "<th>" << tr("Type") << "</th>" 524 << "<th>" << tr("EncType") << "</th>" 525 << "</tr></thead>\r\n<tbody class=\"tableitem\">"; 526 for(auto& it: dest->GetLeaseSets ()) 527 { 528 s << "<tr>" 529 << "<td>" << it.first.ToBase32 () << "</td>" 530 << "<td><a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_EXPIRELEASE<< "&b32=" << dest->GetIdentHash ().ToBase32 () 531 << "&lease=" << it.first.ToBase32 () << "&token=" << token << "\" title=\"" << tr("Expire LeaseSet") << "\"> ✘ </a></td>" 532 << "<td>" << (int)it.second->GetStoreType () << "</td>" 533 << "<td>" << (int)it.second->GetEncryptionType () <<"</td>" 534 << "</tr>\r\n"; 535 } 536 s << "</tbody>\r\n</table>\r\n"; 537 s << "</div>\r\n</div>\r\n<br>\r\n"; 538 } else 539 s << "<b>" << tr("LeaseSets") << ":</b> <i>0</i><br>\r\n<br>\r\n"; 540 541 auto pool = dest->GetTunnelPool (); 542 if (pool) 543 { 544 s << "<b>" << tr("Inbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; 545 for (auto & it : pool->GetInboundTunnels ()) { 546 s << "<div class=\"listitem\">"; 547 // for each tunnel hop if not zero-hop 548 if (it->GetNumHops ()) 549 { 550 it->VisitTunnelHops( 551 [&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent) 552 { 553 s << "⇒ "; 554 ShowHop(s, *hopIdent); 555 s << " "; 556 } 557 ); 558 } 559 s << "⇒ " << it->GetTunnelID () << ":me"; 560 if (it->LatencyIsKnown()) 561 s << " ( " << tr(/* tr: Milliseconds */ "%dms", it->GetMeanLatency()) << " )"; 562 ShowTunnelDetails(s, it->GetState (), false, it->GetNumReceivedBytes ()); 563 s << "</div>\r\n"; 564 } 565 s << "</div>\r\n<br>\r\n"; 566 s << "<b>" << tr("Outbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; 567 for (auto & it : pool->GetOutboundTunnels ()) { 568 s << "<div class=\"listitem\">"; 569 s << it->GetTunnelID () << ":me ⇒"; 570 // for each tunnel hop if not zero-hop 571 if (it->GetNumHops ()) 572 { 573 it->VisitTunnelHops( 574 [&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent) 575 { 576 s << " "; 577 ShowHop(s, *hopIdent); 578 s << " ⇒"; 579 } 580 ); 581 } 582 if (it->LatencyIsKnown()) 583 s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; 584 ShowTunnelDetails(s, it->GetState (), false, it->GetNumSentBytes ()); 585 s << "</div>\r\n"; 586 } 587 s << "</div>\r\n<br>\r\n"; 588 } 589 s << "<br>\r\n"; 590 591 s << "<b>" << tr("Tags") << "</b><br>\r\n" 592 << tr("Incoming") << ": <i>" << dest->GetNumIncomingTags () << "</i><br>\r\n"; 593 if (!dest->GetSessions ().empty ()) { 594 std::stringstream tmp_s; uint32_t out_tags = 0; 595 for (const auto& it: dest->GetSessions ()) { 596 tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.first) << "</td><td>" << it.second->GetNumOutgoingTags () << "</td></tr>\r\n"; 597 out_tags += it.second->GetNumOutgoingTags (); 598 } 599 s << "<div class='slide'><label for='slide-tags'>" << tr("Outgoing") << ": <i>" << out_tags << "</i></label>\r\n" 600 << "<input type=\"checkbox\" id=\"slide-tags\" />\r\n" 601 << "<div class=\"slidecontent\">\r\n" 602 << "<table>\r\n<thead><tr><th>" << tr("Destination") << "</th><th>" << tr("Amount") << "</th></tr></thead>\r\n" 603 << "<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody>\r\n</table>\r\n" 604 << "</div>\r\n</div>\r\n"; 605 } else 606 s << tr("Outgoing") << ": <i>0</i><br>\r\n"; 607 s << "<br>\r\n"; 608 609 auto numECIESx25519Tags = dest->GetNumIncomingECIESx25519Tags (); 610 if (numECIESx25519Tags > 0) { 611 s << "<b>ECIESx25519</b><br>\r\n" << tr("Incoming Tags") << ": <i>" << numECIESx25519Tags << "</i><br>\r\n"; 612 if (!dest->GetECIESx25519Sessions ().empty ()) 613 { 614 std::stringstream tmp_s; uint32_t ecies_sessions = 0; 615 for (const auto& it: dest->GetECIESx25519Sessions ()) { 616 tmp_s << "<tr><td>" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetDestination ()) << "</td><td>" << it.second->GetState () << "</td></tr>\r\n"; 617 ecies_sessions++; 618 } 619 s << "<div class='slide'><label for='slide-ecies-sessions'>" << tr("Tags sessions") << ": <i>" << ecies_sessions << "</i></label>\r\n" 620 << "<input type=\"checkbox\" id=\"slide-ecies-sessions\" />\r\n" 621 << "<div class=\"slidecontent\">\r\n" 622 << "<table>\r\n<thead><tr><th>" << tr("Destination") << "</th><th>" << tr("Status") << "</th></tr></thead>\r\n" 623 << "<tbody class=\"tableitem\">\r\n" << tmp_s.str () << "</tbody>\r\n</table>\r\n" 624 << "</div>\r\n</div>\r\n"; 625 } else 626 s << tr("Tags sessions") << ": <i>0</i><br>\r\n"; 627 s << "<br>\r\n"; 628 } 629 } 630 631 void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) 632 { 633 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 634 s << "<b>" << tr("Local Destination") << ":</b><br>\r\n<br>\r\n"; 635 i2p::data::IdentHash ident; 636 ident.FromBase32 (b32); 637 auto dest = i2p::client::context.FindLocalDestination (ident); 638 639 if (dest) 640 { 641 ShowLeaseSetDestination (s, dest, token); 642 643 // Print table with streams information 644 s << "<table>\r\n<caption>" << tr("Streams") << "</caption>\r\n" 645 << "<thead><tr>" 646 << "<th style=\"width:25px;\">StreamID</th>" 647 << "<th style=\"width:5px;\"> </th>" // Stream closing button column 648 << "<th class=\"streamdest\">Destination</th>" 649 << "<th>Sent</th>" 650 << "<th>Received</th>" 651 << "<th>Out</th>" 652 << "<th>In</th>" 653 << "<th>Buf</th>" 654 << "<th>RTT</th>" 655 << "<th>Window</th>" 656 << "<th>Status</th>" 657 << "</tr></thead>\r\n" 658 << "<tbody class=\"tableitem\">\r\n"; 659 660 for (const auto& it: dest->GetAllStreams ()) 661 { 662 auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()); 663 std::string streamDestShort = streamDest.substr(0,12) + "….b32.i2p"; 664 s << "<tr>"; 665 s << "<td>" << it->GetRecvStreamID () << "</td>"; 666 if (it->GetRecvStreamID ()) { 667 s << "<td><a class=\"button\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_KILLSTREAM << "&b32=" << b32 << "&streamID=" 668 << it->GetRecvStreamID () << "&token=" << token << "\" title=\"" << tr("Close stream") << "\"> ✘ </a></td>"; 669 } 670 else { 671 s << "<td> !! FIXME !! </td>"; // TODO: FIXME: Undefined HTML code 672 } 673 s << "<td class=\"streamdest\" title=\"" << streamDest << "\">" << streamDestShort << "</td>"; 674 s << "<td>" << it->GetNumSentBytes () << "</td>"; 675 s << "<td>" << it->GetNumReceivedBytes () << "</td>"; 676 s << "<td>" << it->GetSendQueueSize () << "</td>"; 677 s << "<td>" << it->GetReceiveQueueSize () << "</td>"; 678 s << "<td>" << it->GetSendBufferSize () << "</td>"; 679 s << "<td>" << it->GetRTT () << "</td>"; 680 s << "<td>" << it->GetWindowSize () << "</td>"; 681 s << "<td>" << (int)it->GetStatus () << "</td>"; 682 s << "</tr>\r\n"; 683 } 684 s << "</tbody>\r\n</table>\r\n<br>\r\n"; 685 } 686 else 687 ShowError(s, tr("Such destination is not found")); 688 } 689 690 void ShowI2CPLocalDestination (std::stringstream& s, const std::string& id) 691 { 692 auto i2cpServer = i2p::client::context.GetI2CPServer (); 693 if (i2cpServer) 694 { 695 s << "<b>I2CP " << tr("Local Destination") << ":</b><br>\r\n<br>\r\n"; 696 auto it = i2cpServer->GetSessions ().find (std::stoi (id)); 697 if (it != i2cpServer->GetSessions ().end ()) 698 ShowLeaseSetDestination (s, it->second->GetDestination (), 0); 699 else 700 ShowError(s, tr("I2CP session not found")); 701 } 702 else 703 ShowError(s, tr("I2CP is not enabled")); 704 } 705 706 void ShowLeasesSets(std::stringstream& s) 707 { 708 if (i2p::data::netdb.GetNumLeaseSets ()) 709 { 710 s << "<b>" << tr("LeaseSets") << ":</b><br>\r\n<div class=\"list\">\r\n"; 711 int counter = 1; 712 // for each lease set 713 i2p::data::netdb.VisitLeaseSets( 714 [&s, &counter](const i2p::data::IdentHash dest, std::shared_ptr<i2p::data::LeaseSet> leaseSet) 715 { 716 // create copy of lease set so we extract leases 717 auto storeType = leaseSet->GetStoreType (); 718 std::unique_ptr<i2p::data::LeaseSet> ls; 719 if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET) 720 ls.reset (new i2p::data::LeaseSet (leaseSet->GetBuffer(), leaseSet->GetBufferLen())); 721 else 722 { 723 ls.reset (new i2p::data::LeaseSet2 (storeType)); 724 ls->Update (leaseSet->GetBuffer(), leaseSet->GetBufferLen(), nullptr, false); 725 } 726 if (!ls) return; 727 s << "<div class=\"leaseset listitem"; 728 if (ls->IsExpired()) 729 s << " expired"; // additional css class for expired 730 s << "\">\r\n"; 731 if (!ls->IsValid()) 732 s << "<div class=\"invalid\">!! " << tr("Invalid") << " !! </div>\r\n"; 733 s << "<div class=\"slide\"><label for=\"slide" << counter << "\">" << dest.ToBase32() << "</label>\r\n"; 734 s << "<input type=\"checkbox\" id=\"slide" << (counter++) << "\" />\r\n"; 735 s << "<div class=\"slidecontent\">\r\n"; 736 s << "<b>" << tr("Store type") << ":</b> " << (int)storeType << "<br>\r\n"; 737 s << "<b>" << tr("Expires") << ":</b> " << ConvertTime(ls->GetExpirationTime()) << "<br>\r\n"; 738 if (storeType == i2p::data::NETDB_STORE_TYPE_LEASESET || storeType == i2p::data::NETDB_STORE_TYPE_STANDARD_LEASESET2) 739 { 740 // leases information is available 741 auto leases = ls->GetNonExpiredLeases(); 742 s << "<b>" << tr("Non Expired Leases") << ": " << leases.size() << "</b><br>\r\n"; 743 for ( auto & l : leases ) 744 { 745 s << "<b>" << tr("Gateway") << ":</b> " << l->tunnelGateway.ToBase64() << "<br>\r\n"; 746 s << "<b>" << tr("TunnelID") << ":</b> " << l->tunnelID << "<br>\r\n"; 747 s << "<b>" << tr("EndDate") << ":</b> " << ConvertTime(l->endDate) << "<br>\r\n"; 748 } 749 } 750 s << "</div>\r\n</div>\r\n"; // class slide class slidecontent 751 s << "</div>\r\n"; // class leaseset listitem 752 } 753 ); 754 // end for each lease set 755 s << "</div>\r\n<br>\r\n"; 756 } 757 else if (!i2p::context.IsFloodfill ()) 758 { 759 s << "<b>" << tr("LeaseSets") << ":</b> " << tr(/* Message on LeaseSets page */ "floodfill mode is disabled") << ".<br>\r\n"; 760 } 761 else 762 { 763 s << "<b>" << tr("LeaseSets") << ":</b> 0<br>\r\n"; 764 } 765 } 766 767 void ShowTunnels (std::stringstream& s) 768 { 769 s << "<b>" << tr("Tunnels") << ":</b><br>\r\n"; 770 s << "<b>" << tr("Queue size") << ":</b> " << i2p::tunnel::tunnels.GetQueueSize () << "<br>\r\n<br>\r\n"; 771 s << "<b>" << tr("TBM Queue size") << ":</b> " << i2p::tunnel::tunnels.GetTBMQueueSize () << "<br>\r\n<br>\r\n"; 772 773 auto ExplPool = i2p::tunnel::tunnels.GetExploratoryPool (); 774 775 s << "<b>" << tr("Inbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; 776 for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { 777 s << "<div class=\"listitem\">"; 778 if (it->GetNumHops ()) 779 { 780 it->VisitTunnelHops( 781 [&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent) 782 { 783 s << "⇒ "; 784 ShowHop(s, *hopIdent); 785 s << " "; 786 } 787 ); 788 } 789 s << "⇒ " << it->GetTunnelID () << ":me"; 790 if (it->LatencyIsKnown()) 791 s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; 792 ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumReceivedBytes ()); 793 s << "</div>\r\n"; 794 } 795 s << "</div>\r\n<br>\r\n"; 796 s << "<b>" << tr("Outbound tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; 797 for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { 798 s << "<div class=\"listitem\">"; 799 s << it->GetTunnelID () << ":me ⇒"; 800 // for each tunnel hop if not zero-hop 801 if (it->GetNumHops ()) 802 { 803 it->VisitTunnelHops( 804 [&s](std::shared_ptr<const i2p::data::IdentityEx> hopIdent) 805 { 806 s << " "; 807 ShowHop(s, *hopIdent); 808 s << " ⇒"; 809 } 810 ); 811 } 812 if (it->LatencyIsKnown()) 813 s << " ( " << tr("%dms", it->GetMeanLatency()) << " )"; 814 ShowTunnelDetails(s, it->GetState (), (it->GetTunnelPool () == ExplPool), it->GetNumSentBytes ()); 815 s << "</div>\r\n"; 816 } 817 s << "</div>\r\n<br>\r\n"; 818 } 819 820 static void ShowCommands (std::stringstream& s, uint32_t token) 821 { 822 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 823 824 s << "<b>" << tr("Router commands") << "</b><br>\r\n<br>\r\n<div class=\"commands\">\r\n"; 825 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RUN_PEER_TEST << "&token=" << token << "\">" << tr("Run peer test") << "</a><br>\r\n"; 826 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RELOAD_TUNNELS_CONFIG << "&token=" << token << "\">" << tr("Reload tunnels configuration") << "</a><br>\r\n"; 827 828 if (i2p::context.AcceptsTunnels ()) 829 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_DISABLE_TRANSIT << "&token=" << token << "\">" << tr("Decline transit tunnels") << "</a><br>\r\n"; 830 else 831 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_ENABLE_TRANSIT << "&token=" << token << "\">" << tr("Accept transit tunnels") << "</a><br>\r\n"; 832 833 #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) 834 if (Daemon.gracefulShutdownInterval) 835 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token << "\">" << tr("Cancel graceful shutdown") << "</a><br>\r\n"; 836 else 837 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "&token=" << token << "\">" << tr("Start graceful shutdown") << "</a><br>\r\n"; 838 #elif defined(WIN32_APP) 839 if (i2p::util::DaemonWin32::Instance().isGraceful) 840 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_CANCEL << "&token=" << token << "\">" << tr("Cancel graceful shutdown") << "</a><br>\r\n"; 841 else 842 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_START << "&token=" << token << "\">" << tr("Start graceful shutdown") << "</a><br>\r\n"; 843 #endif 844 845 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_SHUTDOWN_NOW << "&token=" << token << "\">" << tr("Force shutdown") << "</a><br><br>\r\n"; 846 s << " <a href=\"" << webroot << "?cmd=" << HTTP_COMMAND_RELOAD_CSS << "&token=" << token << "\">" << tr("Reload external CSS styles") << "</a>\r\n"; 847 s << "</div>"; 848 849 s << "<br>\r\n<small>" << tr("<b>Note:</b> any action done here are not persistent and not changes your config files.") << "</small>\r\n<br>\r\n"; 850 851 auto loglevel = i2p::log::Logger().GetLogLevel(); 852 s << "<b>" << tr("Logging level") << "</b><br>\r\n"; 853 s << " <a class=\"button" << (loglevel == eLogNone ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=none&token=" << token << "\"> none </a> \r\n"; 854 s << " <a class=\"button" << (loglevel == eLogCritical ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=critical&token=" << token << "\"> critical </a> \r\n"; 855 s << " <a class=\"button" << (loglevel == eLogError ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=error&token=" << token << "\"> error </a> \r\n"; 856 s << " <a class=\"button" << (loglevel == eLogWarning ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=warn&token=" << token << "\"> warn </a> \r\n"; 857 s << " <a class=\"button" << (loglevel == eLogInfo ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=info&token=" << token << "\"> info </a> \r\n"; 858 s << " <a class=\"button" << (loglevel == eLogDebug ? " selected" : "") << "\" href=\"" << webroot << "?cmd=" << HTTP_COMMAND_LOGLEVEL << "&level=debug&token=" << token << "\"> debug </a><br>\r\n<br>\r\n"; 859 860 uint32_t maxTunnels = i2p::tunnel::tunnels.GetMaxNumTransitTunnels (); 861 s << "<b>" << tr("Transit tunnels limit") << "</b><br>\r\n"; 862 s << "<form method=\"get\" action=\"" << webroot << "\">\r\n"; 863 s << " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_LIMITTRANSIT << "\">\r\n"; 864 s << " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n"; 865 s << " <input type=\"number\" min=\"2\" max=\"" << TRANSIT_TUNNELS_LIMIT <<"\" name=\"limit\" value=\"" << maxTunnels << "\">\r\n"; 866 s << " <button type=\"submit\">" << tr("Change") << "</button>\r\n"; 867 s << "</form>\r\n<br>\r\n"; 868 869 // get current used language 870 std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); 871 872 s << "<b>" 873 << tr("Change language") 874 << "</b><br>\r\n" 875 << "<form method=\"get\" action=\"" << webroot << "\">\r\n" 876 << " <input type=\"hidden\" name=\"cmd\" value=\"" << HTTP_COMMAND_SETLANGUAGE << "\">\r\n" 877 << " <input type=\"hidden\" name=\"token\" value=\"" << token << "\">\r\n" 878 << " <select name=\"lang\" id=\"lang\">\r\n"; 879 880 for (const auto& it: i2p::i18n::languages) 881 s << " <option value=\"" << it.first << "\"" << ((it.first.compare(currLang) == 0) ? " selected" : "") << ">" << it.second.LocaleName << "</option>\r\n"; 882 883 s << " </select>\r\n" 884 << " <button type=\"submit\">" 885 << tr("Change") 886 << "</button>\r\n" 887 << "</form>\r\n<br>\r\n"; 888 889 } 890 891 void ShowTransitTunnels (std::stringstream& s) 892 { 893 if (i2p::tunnel::tunnels.CountTransitTunnels()) 894 { 895 s << "<b>" << tr("Transit Tunnels") << ":</b><br>\r\n"; 896 s << "<table>\r\n"; 897 s << "<thead><tr><th>⇒</th><th>ID</th><th>⇒</th><th>" << tr("Amount") << "</th><th>" << tr("Next") << "</th></tr></thead>\r\n"; 898 s << "<tbody class=\"tableitem\">\r\n"; 899 for (const auto& it: i2p::tunnel::tunnels.GetTransitTunnels ()) 900 { 901 s << "<tr class=\"tcell_center\">"; 902 if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelGateway>(it)) 903 s << "<td> </td><td class=\"tcell_right\">" << std::setw(10) << it->GetTunnelID () << "</td><td>⇒</td>"; 904 else if (std::dynamic_pointer_cast<i2p::tunnel::TransitTunnelEndpoint>(it)) 905 s << "<td>⇒</td><td class=\"tcell_right\">" << std::setw(10) << it->GetTunnelID () << "</td><td> </td>"; 906 else 907 s << "<td>⇒</td><td class=\"tcell_right\">" << std::setw(10) << it->GetTunnelID () << "</td><td>⇒</td>"; 908 s << "<td class=\"tcell_right\">"; 909 ShowTraffic(s, it->GetNumTransmittedBytes ()); 910 s << "</td>"; 911 s << "<td class=\"tcell_left\"> " << it->GetNextPeerName () << "</td>"; 912 s << "</tr>\r\n"; 913 } 914 s << "</tbody>\r\n</table>\r\n<br>\r\n"; 915 } 916 else 917 { 918 s << "<b>" << tr("Transit Tunnels") << ":</b> "; 919 /* Message on transit tunnels page */ 920 s << tr("no transit tunnels currently built") << ".<br>\r\n"; 921 } 922 } 923 924 template<typename Sessions> 925 static void ShowTransportSessions (std::stringstream& s, const Sessions& sessions, const std::string name) 926 { 927 auto comp = [](typename Sessions::mapped_type a, typename Sessions::mapped_type b) 928 { return a->GetRemoteEndpoint() < b->GetRemoteEndpoint(); }; 929 std::set<typename Sessions::mapped_type, decltype(comp)> sortedSessions(comp); 930 for (const auto& it : sessions) 931 { 932 auto ret = sortedSessions.insert(it.second); 933 if (!ret.second) 934 LogPrint(eLogError, "HTTPServer: Duplicate remote endpoint detected: ", (*ret.first)->GetRemoteEndpoint()); 935 } 936 std::stringstream tmp_s, tmp_s6; uint16_t cnt = 0, cnt6 = 0; 937 for (const auto& it: sortedSessions) 938 { 939 auto endpoint = it->GetRemoteEndpoint (); 940 if (it && it->IsEstablished () && endpoint.address ().is_v4 ()) 941 { 942 tmp_s << "<div class=\"listitem\">\r\n"; 943 if (it->IsOutgoing ()) tmp_s << " ⇒ "; 944 else tmp_s << " "; 945 tmp_s << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " 946 << endpoint.address ().to_string () << ":" << endpoint.port (); 947 if (!it->IsOutgoing ()) tmp_s << " ⇒ "; 948 else tmp_s << " "; 949 tmp_s << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; 950 if (it->GetRelayTag ()) 951 tmp_s << " [itag:" << it->GetRelayTag () << "]"; 952 if (it->GetSendQueueSize () > 0) 953 tmp_s << " [queue:" << it->GetSendQueueSize () << "]"; 954 if (it->IsSlow ()) tmp_s << " [slow]"; 955 tmp_s << "</div>\r\n" << std::endl; 956 cnt++; 957 } 958 if (it && it->IsEstablished () && endpoint.address ().is_v6 ()) 959 { 960 tmp_s6 << "<div class=\"listitem\">\r\n"; 961 if (it->IsOutgoing ()) tmp_s6 << " ⇒ "; 962 else tmp_s6 << " "; 963 tmp_s6 << i2p::data::GetIdentHashAbbreviation (it->GetRemoteIdentity ()->GetIdentHash ()) << ": " 964 << "[" << endpoint.address ().to_string () << "]:" << endpoint.port (); 965 if (!it->IsOutgoing ()) tmp_s6 << " ⇒ "; 966 else tmp_s6 << " "; 967 tmp_s6 << " [" << it->GetNumSentBytes () << ":" << it->GetNumReceivedBytes () << "]"; 968 if (it->GetRelayTag ()) 969 tmp_s6 << " [itag:" << it->GetRelayTag () << "]"; 970 if (it->GetSendQueueSize () > 0) 971 tmp_s6 << " [queue:" << it->GetSendQueueSize () << "]"; 972 tmp_s6 << "</div>\r\n" << std::endl; 973 cnt6++; 974 } 975 } 976 if (!tmp_s.str ().empty ()) 977 { 978 s << "<div class='slide'><label for='slide_" << boost::algorithm::to_lower_copy(name) << "'><b>" << name 979 << "</b> ( " << cnt << " )</label>\r\n" 980 << "<input type=\"checkbox\" id=\"slide_" << boost::algorithm::to_lower_copy(name) << "\" />\r\n" 981 << "<div class=\"slidecontent list\">\r\n\r\n" 982 << tmp_s.str () 983 << "<div class=\"listitem\">\r\n" 984 << " </div>\r\n" << std::endl 985 << "</div>\r\n</div>\r\n\r\n"; 986 } 987 if (!tmp_s6.str ().empty ()) 988 { 989 s << "<div class='slide'><label for='slide_" << boost::algorithm::to_lower_copy(name) << "v6'><b>" << name 990 << "v6</b> ( " << cnt6 << " )</label>\r\n" 991 << "<input type=\"checkbox\" id=\"slide_" << boost::algorithm::to_lower_copy(name) << "v6\" />\r\n" 992 << "<div class=\"slidecontent list\">\r\n\r\n" 993 << tmp_s6.str () 994 << "<div class=\"listitem\">\r\n" 995 << " </div>\r\n" << std::endl 996 << "</div>\r\n</div>\r\n\r\n"; 997 } 998 } 999 1000 void ShowTransports (std::stringstream& s) 1001 { 1002 s << "<b>" << tr("Transports") << ":</b><br>\r\n"; 1003 auto ntcp2Server = i2p::transport::transports.GetNTCP2Server (); 1004 if (ntcp2Server) 1005 { 1006 auto sessions = ntcp2Server->GetNTCP2Sessions (); 1007 if (!sessions.empty ()) 1008 ShowTransportSessions (s, sessions, "NTCP2"); 1009 } 1010 auto ssu2Server = i2p::transport::transports.GetSSU2Server (); 1011 if (ssu2Server) 1012 { 1013 auto sessions = ssu2Server->GetSSU2Sessions (); 1014 if (!sessions.empty ()) 1015 ShowTransportSessions (s, sessions, "SSU2"); 1016 } 1017 } 1018 1019 void ShowSAMSessions (std::stringstream& s) 1020 { 1021 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 1022 auto sam = i2p::client::context.GetSAMBridge (); 1023 if (!sam) 1024 { 1025 ShowError(s, tr("SAM disabled")); 1026 return; 1027 } 1028 1029 if (sam->GetSessions ().size ()) 1030 { 1031 s << "<b>" << tr("SAM sessions") << ":</b><br>\r\n<div class=\"list\">\r\n"; 1032 for (auto& it: sam->GetSessions ()) 1033 { 1034 auto& name = it.second->GetLocalDestination ()->GetNickname (); 1035 auto sam_id = i2p::data::ByteStreamToBase64 ((const uint8_t *)it.first.data (), it.first.length ()); // base64, becuase session name might be UTF-8 1036 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_SAM_SESSION << "&sam_id=" << sam_id << "\">"; 1037 s << name << " (" << it.first << ")</a></div>\r\n" << std::endl; 1038 } 1039 s << "</div>\r\n<br>\r\n"; 1040 } 1041 else 1042 { 1043 s << "<b>" << tr("SAM sessions") << ":</b> "; 1044 /* Message on SAM sessions page */ 1045 s << tr("no sessions currently running") << ".<br>\r\n"; 1046 } 1047 } 1048 1049 void ShowSAMSession (std::stringstream& s, const std::string& id) 1050 { 1051 auto sam = i2p::client::context.GetSAMBridge (); 1052 if (!sam) 1053 { 1054 ShowError(s, tr("SAM disabled")); 1055 return; 1056 } 1057 if (id.empty ()) 1058 { 1059 ShowError(s, tr("No sam_id")); 1060 return; 1061 } 1062 std::vector<uint8_t> sam_id(id.length ()); // id is in base64 1063 size_t l = i2p::data::Base64ToByteStream (id, sam_id.data (), sam_id.size ()); 1064 if (!l) 1065 { 1066 ShowError(s, tr("Invalid sam_id")); 1067 return; 1068 } 1069 auto session = sam->FindSession ( { (const char *)sam_id.data (), l }); 1070 if (!session) 1071 { 1072 ShowError(s, tr("SAM session not found")); 1073 return; 1074 } 1075 1076 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 1077 s << "<b>" << tr("SAM Session") << ":</b><br>\r\n<div class=\"list\">\r\n"; 1078 auto& ident = session->GetLocalDestination ()->GetIdentHash(); 1079 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 1080 s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "</a></div>\r\n"; 1081 s << "</div>\r\n<br>\r\n"; 1082 s << "<b>" << tr("Streams") << ":</b><br>\r\n<div class=\"list\">\r\n"; 1083 for (const auto& it: sam->ListSockets({ (const char *)sam_id.data (), l })) 1084 { 1085 s << "<div class=\"listitem\">"; 1086 switch (it->GetSocketType ()) 1087 { 1088 case i2p::client::SAMSocketType::eSAMSocketTypeSession : s << "session"; break; 1089 case i2p::client::SAMSocketType::eSAMSocketTypeStream : s << "stream"; break; 1090 case i2p::client::SAMSocketType::eSAMSocketTypeAcceptor : s << "acceptor"; break; 1091 case i2p::client::SAMSocketType::eSAMSocketTypeForward : s << "forward"; break; 1092 default: s << "unknown"; break; 1093 } 1094 if (it->GetSocketType () != i2p::client::SAMSocketType::eSAMSocketTypeTerminated && it->GetSocket ().is_open ()) 1095 s << " [" << it->GetSocket ().remote_endpoint() << "]"; 1096 s << "</div>\r\n"; 1097 } 1098 s << "</div>\r\n"; 1099 } 1100 1101 void ShowI2PTunnels (std::stringstream& s) 1102 { 1103 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 1104 1105 auto& clientTunnels = i2p::client::context.GetClientTunnels (); 1106 auto httpProxy = i2p::client::context.GetHttpProxy (); 1107 auto socksProxy = i2p::client::context.GetSocksProxy (); 1108 if (!clientTunnels.empty () || httpProxy || socksProxy) 1109 { 1110 s << "<b>" << tr("Client Tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; 1111 if (!clientTunnels.empty ()) 1112 { 1113 for (auto& it: clientTunnels) 1114 { 1115 auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); 1116 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 1117 s << it.second->GetName () << "</a> ⇐ "; 1118 s << i2p::client::context.GetAddressBook ().ToAddress(ident); 1119 s << "</div>\r\n"<< std::endl; 1120 } 1121 } 1122 if (httpProxy) 1123 { 1124 auto& ident = httpProxy->GetLocalDestination ()->GetIdentHash(); 1125 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 1126 s << "HTTP " << tr("Proxy") << "</a> ⇐ "; 1127 s << i2p::client::context.GetAddressBook ().ToAddress(ident); 1128 s << "</div>\r\n"<< std::endl; 1129 } 1130 if (socksProxy) 1131 { 1132 auto& ident = socksProxy->GetLocalDestination ()->GetIdentHash(); 1133 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 1134 s << "SOCKS " << tr("Proxy") << "</a> ⇐ "; 1135 s << i2p::client::context.GetAddressBook ().ToAddress(ident); 1136 s << "</div>\r\n"<< std::endl; 1137 } 1138 s << "</div>\r\n<br>\r\n"; 1139 } 1140 1141 auto& serverTunnels = i2p::client::context.GetServerTunnels (); 1142 if (!serverTunnels.empty ()) { 1143 s << "<br>\r\n<b>" << tr("Server Tunnels") << ":</b><br>\r\n<div class=\"list\">\r\n"; 1144 for (auto& it: serverTunnels) 1145 { 1146 auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); 1147 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 1148 s << it.second->GetName () << "</a> ⇒ "; 1149 s << i2p::client::context.GetAddressBook ().ToAddress(ident); 1150 s << ":" << it.second->GetLocalPort (); 1151 s << "</a></div>\r\n"<< std::endl; 1152 } 1153 s << "</div>\r\n<br>\r\n"; 1154 } 1155 1156 auto& clientForwards = i2p::client::context.GetClientForwards (); 1157 if (!clientForwards.empty ()) 1158 { 1159 s << "<br>\r\n<b>" << tr("Client Forwards") << ":</b><br>\r\n<div class=\"list\">\r\n"; 1160 for (auto& it: clientForwards) 1161 { 1162 auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); 1163 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 1164 s << it.second->GetName () << "</a> ⇐ "; 1165 s << i2p::client::context.GetAddressBook ().ToAddress(ident); 1166 s << "</div>\r\n"<< std::endl; 1167 } 1168 s << "</div>\r\n<br>\r\n"; 1169 } 1170 auto& serverForwards = i2p::client::context.GetServerForwards (); 1171 if (!serverForwards.empty ()) 1172 { 1173 s << "<br>\r\n<b>" << tr("Server Forwards") << ":</b><br>\r\n<div class=\"list\">\r\n"; 1174 for (auto& it: serverForwards) 1175 { 1176 auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); 1177 s << "<div class=\"listitem\"><a href=\"" << webroot << "?page=" << HTTP_PAGE_LOCAL_DESTINATION << "&b32=" << ident.ToBase32 () << "\">"; 1178 s << it.second->GetName () << "</a> ⇐ "; 1179 s << i2p::client::context.GetAddressBook ().ToAddress(ident); 1180 s << "</div>\r\n"<< std::endl; 1181 } 1182 s << "</div>\r\n<br>\r\n"; 1183 } 1184 } 1185 1186 HTTPConnection::HTTPConnection (std::string hostname, std::shared_ptr<boost::asio::ip::tcp::socket> socket): 1187 m_Socket (socket), m_BufferLen (0), expected_host(hostname) 1188 { 1189 /* cache options */ 1190 i2p::config::GetOption("http.auth", needAuth); 1191 i2p::config::GetOption("http.user", user); 1192 i2p::config::GetOption("http.pass", pass); 1193 } 1194 1195 void HTTPConnection::Receive () 1196 { 1197 m_Socket->async_read_some (boost::asio::buffer (m_Buffer, HTTP_CONNECTION_BUFFER_SIZE), 1198 std::bind(&HTTPConnection::HandleReceive, shared_from_this (), 1199 std::placeholders::_1, std::placeholders::_2)); 1200 } 1201 1202 void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) 1203 { 1204 if (ecode) { 1205 if (ecode != boost::asio::error::operation_aborted) 1206 Terminate (ecode); 1207 return; 1208 } 1209 if (bytes_transferred <= HTTP_CONNECTION_BUFFER_SIZE) { 1210 m_Buffer[bytes_transferred] = '\0'; 1211 m_BufferLen = bytes_transferred; 1212 } 1213 else { 1214 m_Buffer[HTTP_CONNECTION_BUFFER_SIZE] = '\0'; 1215 m_BufferLen = HTTP_CONNECTION_BUFFER_SIZE; 1216 } 1217 RunRequest(); 1218 Receive (); 1219 } 1220 1221 void HTTPConnection::RunRequest () 1222 { 1223 HTTPReq request; 1224 int ret = request.parse(m_Buffer); 1225 if (ret < 0) { 1226 m_Buffer[0] = '\0'; 1227 m_BufferLen = 0; 1228 return; /* error */ 1229 } 1230 if (ret == 0) 1231 return; /* need more data */ 1232 1233 HandleRequest (request); 1234 } 1235 1236 void HTTPConnection::Terminate (const boost::system::error_code& ecode) 1237 { 1238 if (ecode == boost::asio::error::operation_aborted) 1239 return; 1240 boost::system::error_code ignored_ec; 1241 m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); 1242 m_Socket->close (); 1243 } 1244 1245 bool HTTPConnection::CheckAuth (const HTTPReq & req) 1246 { 1247 /* method #1: http://user:pass@127.0.0.1:7070/ */ 1248 if (req.uri.find('@') != std::string::npos) { 1249 URL url; 1250 if (url.parse(req.uri) && url.user == user && url.pass == pass) 1251 return true; 1252 } 1253 /* method #2: 'Authorization' header sent */ 1254 auto provided = req.GetHeader ("Authorization"); 1255 if (provided.length () > 0) 1256 { 1257 std::string expected = "Basic " + i2p::data::ToBase64Standard (user + ":" + pass); 1258 if (expected == provided) return true; 1259 } 1260 1261 LogPrint(eLogWarning, "HTTPServer: Auth failure from ", m_Socket->remote_endpoint().address ()); 1262 return false; 1263 } 1264 1265 void HTTPConnection::HandleRequest (const HTTPReq & req) 1266 { 1267 std::stringstream s; 1268 std::string content; 1269 HTTPRes res; 1270 1271 LogPrint(eLogDebug, "HTTPServer: Request: ", req.uri); 1272 1273 if (needAuth && !CheckAuth(req)) { 1274 res.code = 401; 1275 res.add_header("WWW-Authenticate", "Basic realm=\"WebAdmin\""); 1276 SendReply(res, content); 1277 return; 1278 } 1279 1280 bool strictheaders; 1281 i2p::config::GetOption("http.strictheaders", strictheaders); 1282 if (strictheaders) 1283 { 1284 std::string http_hostname; 1285 i2p::config::GetOption("http.hostname", http_hostname); 1286 std::string host = req.GetHeader("Host"); 1287 auto idx = host.find(':'); 1288 /* strip out port so it's just host */ 1289 if (idx != std::string::npos && idx > 0) 1290 { 1291 host = host.substr(0, idx); 1292 } 1293 if (!(host == expected_host || host == http_hostname)) 1294 { 1295 /* deny request as it's from a non whitelisted hostname */ 1296 res.code = 403; 1297 content = "host mismatch"; 1298 SendReply(res, content); 1299 return; 1300 } 1301 } 1302 1303 // HTML head start 1304 ShowPageHead (s); 1305 if (req.uri.find("page=") != std::string::npos) { 1306 HandlePage (req, res, s); 1307 } else if (req.uri.find("cmd=") != std::string::npos) { 1308 HandleCommand (req, res, s); 1309 } else { 1310 ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); 1311 res.add_header("Refresh", "10"); 1312 } 1313 ShowPageTail (s); 1314 1315 res.code = 200; 1316 content = s.str (); 1317 SendReply (res, content); 1318 } 1319 1320 std::map<uint32_t, uint32_t> HTTPConnection::m_Tokens; 1321 1322 uint32_t HTTPConnection::CreateToken () 1323 { 1324 uint32_t token; 1325 RAND_bytes ((uint8_t *)&token, 4); 1326 token &= 0x7FFFFFFF; // clear first bit 1327 auto ts = i2p::util::GetSecondsSinceEpoch (); 1328 for (auto it = m_Tokens.begin (); it != m_Tokens.end (); ) 1329 { 1330 if (ts > it->second + TOKEN_EXPIRATION_TIMEOUT) 1331 it = m_Tokens.erase (it); 1332 else 1333 ++it; 1334 } 1335 m_Tokens[token] = ts; 1336 return token; 1337 } 1338 1339 void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s) 1340 { 1341 std::map<std::string, std::string> params; 1342 std::string page(""); 1343 URL url; 1344 1345 url.parse(req.uri); 1346 url.parse_query(params); 1347 page = params["page"]; 1348 1349 if (page == HTTP_PAGE_TRANSPORTS) 1350 ShowTransports (s); 1351 else if (page == HTTP_PAGE_TUNNELS) 1352 ShowTunnels (s); 1353 else if (page == HTTP_PAGE_COMMANDS) 1354 { 1355 uint32_t token = CreateToken (); 1356 ShowCommands (s, token); 1357 } 1358 else if (page == HTTP_PAGE_TRANSIT_TUNNELS) 1359 ShowTransitTunnels (s); 1360 else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) 1361 ShowLocalDestinations (s); 1362 else if (page == HTTP_PAGE_LOCAL_DESTINATION) 1363 { 1364 uint32_t token = CreateToken (); 1365 ShowLocalDestination (s, params["b32"], token); 1366 } 1367 else if (page == HTTP_PAGE_I2CP_LOCAL_DESTINATION) 1368 ShowI2CPLocalDestination (s, params["i2cp_id"]); 1369 else if (page == HTTP_PAGE_SAM_SESSIONS) 1370 ShowSAMSessions (s); 1371 else if (page == HTTP_PAGE_SAM_SESSION) 1372 ShowSAMSession (s, params["sam_id"]); 1373 else if (page == HTTP_PAGE_I2P_TUNNELS) 1374 ShowI2PTunnels (s); 1375 else if (page == HTTP_PAGE_LEASESETS) 1376 ShowLeasesSets(s); 1377 else { 1378 res.code = 400; 1379 ShowError(s, std::string (tr("Unknown page")) + ": " + page); // TODO 1380 return; 1381 } 1382 } 1383 1384 void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s) 1385 { 1386 std::map<std::string, std::string> params; 1387 URL url; 1388 1389 url.parse(req.uri); 1390 url.parse_query(params); 1391 1392 std::string webroot; i2p::config::GetOption("http.webroot", webroot); 1393 std::string redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=commands"; 1394 std::string token = params["token"]; 1395 1396 if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) 1397 { 1398 ShowError(s, tr("Invalid token")); 1399 return; 1400 } 1401 1402 std::string cmd = params["cmd"]; 1403 if (cmd == HTTP_COMMAND_RUN_PEER_TEST) 1404 i2p::transport::transports.PeerTest (); 1405 else if (cmd == HTTP_COMMAND_RELOAD_TUNNELS_CONFIG) 1406 i2p::client::context.ReloadConfig (); 1407 else if (cmd == HTTP_COMMAND_ENABLE_TRANSIT) 1408 i2p::context.SetAcceptsTunnels (true); 1409 else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) 1410 i2p::context.SetAcceptsTunnels (false); 1411 else if (cmd == HTTP_COMMAND_SHUTDOWN_START) 1412 { 1413 i2p::context.SetAcceptsTunnels (false); 1414 #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) 1415 Daemon.gracefulShutdownInterval = 10*60; 1416 #elif defined(WIN32_APP) 1417 i2p::win32::GracefulShutdown (); 1418 #endif 1419 } 1420 else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) 1421 { 1422 i2p::context.SetAcceptsTunnels (true); 1423 #if ((!defined(WIN32) && !defined(QT_GUI_LIB) && !defined(ANDROID)) || defined(ANDROID_BINARY)) 1424 Daemon.gracefulShutdownInterval = 0; 1425 #elif defined(WIN32_APP) 1426 i2p::win32::StopGracefulShutdown (); 1427 #endif 1428 } 1429 else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) 1430 { 1431 #ifndef WIN32_APP 1432 Daemon.running = false; 1433 #else 1434 i2p::win32::StopWin32App (); 1435 #endif 1436 } 1437 else if (cmd == HTTP_COMMAND_LOGLEVEL) 1438 { 1439 std::string level = params["level"]; 1440 SetLogLevel (level); 1441 } 1442 else if (cmd == HTTP_COMMAND_KILLSTREAM) 1443 { 1444 std::string b32 = params["b32"]; 1445 uint32_t streamID = std::stoul(params["streamID"], nullptr); 1446 1447 i2p::data::IdentHash ident; 1448 ident.FromBase32 (b32); 1449 auto dest = i2p::client::context.FindLocalDestination (ident); 1450 1451 if (streamID) 1452 { 1453 if (dest) 1454 { 1455 if (dest->DeleteStream (streamID)) 1456 s << "<b>" << tr("SUCCESS") << "</b>: " << tr("Stream closed") << "<br>\r\n<br>\r\n"; 1457 else 1458 s << "<b>" << tr("ERROR") << "</b>: " << tr("Stream not found or already was closed") << "<br>\r\n<br>\r\n"; 1459 } 1460 else 1461 s << "<b>" << tr("ERROR") << "</b>: " << tr("Destination not found") << "<br>\r\n<br>\r\n"; 1462 } 1463 else 1464 s << "<b>" << tr("ERROR") << "</b>: " << tr("StreamID can't be null") << "<br>\r\n<br>\r\n"; 1465 1466 s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a><br>\r\n"; 1467 s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>"; 1468 redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32; 1469 res.add_header("Refresh", redirect.c_str()); 1470 return; 1471 } 1472 else if (cmd == HTTP_COMMAND_EXPIRELEASE) 1473 { 1474 std::string b32 = params["b32"]; 1475 std::string lease = params["lease"]; 1476 1477 i2p::data::IdentHash ident, leaseident; 1478 ident.FromBase32 (b32); 1479 leaseident.FromBase32 (lease); 1480 auto dest = i2p::client::context.FindLocalDestination (ident); 1481 1482 if (dest) 1483 { 1484 auto leaseset = dest->FindLeaseSet (leaseident); 1485 if (leaseset) 1486 { 1487 leaseset->ExpireLease (); 1488 s << "<b>" << tr("SUCCESS") << "</b>: " << tr("LeaseSet expiration time updated") << "<br>\r\n<br>\r\n"; 1489 } 1490 else 1491 s << "<b>" << tr("ERROR") << "</b>: " << tr("LeaseSet is not found or already expired") << "<br>\r\n<br>\r\n"; 1492 } 1493 else 1494 s << "<b>" << tr("ERROR") << "</b>: " << tr("Destination not found") << "<br>\r\n<br>\r\n"; 1495 1496 s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a><br>\r\n"; 1497 s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>"; 1498 redirect = std::to_string(COMMAND_REDIRECT_TIMEOUT) + "; url=" + webroot + "?page=local_destination&b32=" + b32; 1499 res.add_header("Refresh", redirect.c_str()); 1500 return; 1501 } 1502 else if (cmd == HTTP_COMMAND_LIMITTRANSIT) 1503 { 1504 uint32_t limit = std::stoul(params["limit"], nullptr); 1505 if (limit >= 2 && limit <= TRANSIT_TUNNELS_LIMIT) 1506 i2p::tunnel::tunnels.SetMaxNumTransitTunnels (limit); 1507 else { 1508 s << "<b>" << tr("ERROR") << "</b>: "; 1509 s << tr("Transit tunnels count must be at least 2 and not exceed %d", TRANSIT_TUNNELS_LIMIT) << "\r\n<br><br>\r\n"; 1510 s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a>\r\n<br>\r\n"; 1511 s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>"; 1512 res.add_header("Refresh", redirect.c_str()); 1513 return; 1514 } 1515 } 1516 else if (cmd == HTTP_COMMAND_GET_REG_STRING) 1517 { 1518 std::string b32 = params["b32"]; 1519 std::string name = i2p::http::UrlDecode(params["name"]); 1520 1521 i2p::data::IdentHash ident; 1522 ident.FromBase32 (b32); 1523 auto dest = i2p::client::context.FindLocalDestination (ident); 1524 1525 if (dest) 1526 { 1527 std::size_t pos; 1528 pos = name.find (".i2p"); 1529 if (pos == (name.length () - 4)) 1530 { 1531 pos = name.find (".b32.i2p"); 1532 if (pos == std::string::npos) 1533 { 1534 auto signatureLen = dest->GetIdentity ()->GetSignatureLen (); 1535 uint8_t * signature = new uint8_t[signatureLen]; 1536 std::stringstream out; 1537 1538 out << name << "=" << dest->GetIdentity ()->ToBase64 (); 1539 dest->Sign ((uint8_t *)out.str ().c_str (), out.str ().length (), signature); 1540 auto sig = i2p::data::ByteStreamToBase64 (signature, signatureLen); 1541 out << "#!sig=" << sig; 1542 s << "<b>" << tr("SUCCESS") << "</b>:<br>\r\n<form action=\"http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/add\" method=\"post\" rel=\"noreferrer\" target=\"_blank\">\r\n" 1543 "<textarea readonly name=\"record\" cols=\"80\" rows=\"10\">" << out.str () << "</textarea>\r\n<br>\r\n<br>\r\n" 1544 "<b>" << tr("Register at reg.i2p") << ":</b>\r\n<br>\r\n" 1545 "<b>" << tr("Description") << ":</b>\r\n<input type=\"text\" maxlength=\"64\" name=\"desc\" placeholder=\"" << tr("A bit information about service on domain") << "\">\r\n" 1546 "<input type=\"submit\" value=\"" << tr("Submit") << "\">\r\n" 1547 "</form>\r\n<br>\r\n"; 1548 delete[] signature; 1549 } 1550 else 1551 s << "<b>" << tr("ERROR") << "</b>: " << tr("Domain can't end with .b32.i2p") << "\r\n<br>\r\n<br>\r\n"; 1552 } 1553 else 1554 s << "<b>" << tr("ERROR") << "</b>: " << tr("Domain must end with .i2p") << "\r\n<br>\r\n<br>\r\n"; 1555 } 1556 else 1557 s << "<b>" << tr("ERROR") << "</b>: " << tr("Such destination is not found") << "\r\n<br>\r\n<br>\r\n"; 1558 1559 s << "<a href=\"" << webroot << "?page=local_destination&b32=" << b32 << "\">" << tr("Return to destination page") << "</a>\r\n"; 1560 return; 1561 } 1562 else if (cmd == HTTP_COMMAND_SETLANGUAGE) 1563 { 1564 std::string lang = params["lang"]; 1565 std::string currLang = i2p::client::context.GetLanguage ()->GetLanguage(); 1566 1567 if (currLang.compare(lang) != 0) 1568 i2p::i18n::SetLanguage(lang); 1569 } 1570 else if (cmd == HTTP_COMMAND_RELOAD_CSS) 1571 { 1572 std::string theme; i2p::config::GetOption("http.theme", theme); 1573 1574 if (theme != "light" && theme != "black" && theme !="white") LoadExtCSS(theme); 1575 else LoadExtCSS(); 1576 } 1577 else 1578 { 1579 res.code = 400; 1580 ShowError(s, std::string (tr("Unknown command")) + ": " + cmd); // TODO 1581 return; 1582 } 1583 1584 s << "<b>" << tr("SUCCESS") << "</b>: " << tr("Command accepted") << "<br><br>\r\n"; 1585 s << "<a href=\"" << webroot << "?page=commands\">" << tr("Back to commands list") << "</a><br>\r\n"; 1586 s << "<p>" << tr("You will be redirected in %d seconds", COMMAND_REDIRECT_TIMEOUT) << "</b>"; 1587 res.add_header("Refresh", redirect.c_str()); 1588 } 1589 1590 void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) 1591 { 1592 reply.add_header("X-Frame-Options", "SAMEORIGIN"); 1593 reply.add_header("X-Content-Type-Options", "nosniff"); 1594 reply.add_header("X-XSS-Protection", "1; mode=block"); 1595 reply.add_header("Content-Type", "text/html"); 1596 reply.body = content; 1597 1598 m_SendBuffer = reply.to_string(); 1599 boost::asio::async_write (*m_Socket, boost::asio::buffer(m_SendBuffer), boost::asio::transfer_all (), 1600 std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); 1601 } 1602 1603 HTTPServer::HTTPServer (const std::string& address, int port): 1604 m_IsRunning (false), m_Thread (nullptr), m_Work (m_Service.get_executor ()), 1605 m_Acceptor (m_Service, boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address(address), port)), 1606 m_Hostname(address) 1607 { 1608 } 1609 1610 HTTPServer::~HTTPServer () 1611 { 1612 Stop (); 1613 } 1614 1615 void HTTPServer::Start () 1616 { 1617 bool needAuth; i2p::config::GetOption("http.auth", needAuth); 1618 std::string user; i2p::config::GetOption("http.user", user); 1619 std::string pass; i2p::config::GetOption("http.pass", pass); 1620 /* generate pass if needed */ 1621 if (needAuth && pass == "") { 1622 uint8_t random[16]; 1623 char alnum[] = "0123456789" 1624 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 1625 "abcdefghijklmnopqrstuvwxyz"; 1626 pass.resize(sizeof(random)); 1627 RAND_bytes(random, sizeof(random)); 1628 for (size_t i = 0; i < sizeof(random); i++) { 1629 pass[i] = alnum[random[i] % (sizeof(alnum) - 1)]; 1630 } 1631 i2p::config::SetOption("http.pass", pass); 1632 LogPrint(eLogInfo, "HTTPServer: Password set to ", pass); 1633 } 1634 1635 m_IsRunning = true; 1636 m_Thread.reset (new std::thread (std::bind (&HTTPServer::Run, this))); 1637 m_Acceptor.listen (); 1638 Accept (); 1639 1640 LoadExtCSS(); 1641 } 1642 1643 void HTTPServer::Stop () 1644 { 1645 m_IsRunning = false; 1646 1647 boost::system::error_code ec; 1648 m_Acceptor.cancel(ec); 1649 if (ec) 1650 LogPrint (eLogDebug, "HTTPServer: Error while cancelling operations on acceptor: ", ec.message ()); 1651 m_Acceptor.close(); 1652 1653 m_Service.stop (); 1654 if (m_Thread) 1655 { 1656 m_Thread->join (); 1657 m_Thread = nullptr; 1658 } 1659 } 1660 1661 void HTTPServer::Run () 1662 { 1663 i2p::util::SetThreadName("Webconsole"); 1664 1665 while (m_IsRunning) 1666 { 1667 try 1668 { 1669 m_Service.run (); 1670 } 1671 catch (std::exception& ex) 1672 { 1673 LogPrint (eLogError, "HTTPServer: Runtime exception: ", ex.what ()); 1674 } 1675 } 1676 } 1677 1678 void HTTPServer::Accept () 1679 { 1680 auto newSocket = std::make_shared<boost::asio::ip::tcp::socket> (m_Service); 1681 m_Acceptor.async_accept (*newSocket, std::bind (&HTTPServer::HandleAccept, this, 1682 std::placeholders::_1, newSocket)); 1683 } 1684 1685 void HTTPServer::HandleAccept(const boost::system::error_code& ecode, 1686 std::shared_ptr<boost::asio::ip::tcp::socket> newSocket) 1687 { 1688 if (!ecode) 1689 CreateConnection(newSocket); 1690 else 1691 { 1692 if (newSocket) newSocket->close(); 1693 LogPrint(eLogError, "HTTP Server: Error handling accept: ", ecode.message()); 1694 } 1695 if (m_IsRunning) 1696 Accept (); 1697 } 1698 1699 void HTTPServer::CreateConnection(std::shared_ptr<boost::asio::ip::tcp::socket> newSocket) 1700 { 1701 auto conn = std::make_shared<HTTPConnection> (m_Hostname, newSocket); 1702 conn->Receive (); 1703 } 1704 } // http 1705 } // i2p