diff --git a/daemon/HTTPServer.cpp b/daemon/HTTPServer.cpp index 7dc6a9d2..36e26785 100644 --- a/daemon/HTTPServer.cpp +++ b/daemon/HTTPServer.cpp @@ -54,6 +54,7 @@ namespace http { " body { font: 100%/1.5em sans-serif; margin: 0; padding: 1.5em; background: #FAFAFA; color: #103456; }\r\n" " a, .slide label { text-decoration: none; color: #894C84; }\r\n" " a:hover, .slide label:hover { color: #FAFAFA; background: #894C84; }\r\n" + " a.button { -webkit-appearance: button; -moz-appearance: button; appearance: button; text-decoration: none; color: initial;}\r\n" " .header { font-size: 2.5em; text-align: center; margin: 1.5em 0; color: #894C84; }\r\n" " .wrapper { margin: 0 auto; padding: 1em; max-width: 60em; }\r\n" " .left { float: left; position: absolute; }\r\n" @@ -64,10 +65,11 @@ namespace http { " .tunnel.building { color: #434343; }\r\n" " caption { font-size: 1.5em; text-align: center; color: #894C84; }\r\n" " table { width: 100%; border-collapse: collapse; text-align: center; }\r\n" - " .slide p, .slide [type='checkbox']{ display:none; }\r\n" - " .slide [type='checkbox']:checked ~ p { display:block; margin-top: 0; padding: 0; }\r\n" + " .slide p, .slide [type='checkbox'] { display: none; }\r\n" + " .slide [type='checkbox']:checked ~ p { display: block; margin-top: 0; padding: 0; }\r\n" " .disabled:after { color: #D33F3F; content: \"Disabled\" }\r\n" " .enabled:after { color: #56B734; content: \"Enabled\" }\r\n" + " .streamdest { width: 120px; max-width: 240px; overflow: hidden; text-overflow: ellipsis;}\r\n" "\r\n"; const char HTTP_PAGE_TUNNELS[] = "tunnels"; @@ -89,10 +91,12 @@ namespace http { 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_PARAM_SAM_SESSION_ID[] = "id"; const char HTTP_PARAM_ADDRESS[] = "address"; static std::string ConvertTime (uint64_t time); + std::map HTTPConnection::m_Tokens; static void ShowUptime (std::stringstream& s, int seconds) { @@ -203,10 +207,7 @@ namespace http { s << "ERROR: " << string << "
\r\n"; } - void ShowStatus ( - std::stringstream& s, - bool includeHiddenContent, - i2p::http::OutputFormatEnum outputFormat) + void ShowStatus (std::stringstream& s, bool includeHiddenContent, i2p::http::OutputFormatEnum outputFormat) { s << "Uptime: "; ShowUptime(s, i2p::context.GetUptime ()); @@ -253,12 +254,12 @@ namespace http { ShowTraffic (s, i2p::transport::transports.GetTotalTransitTransmittedBytes ()); s << " (" << (double) i2p::transport::transports.GetTransitBandwidth () / 1024 << " KiB/s)
\r\n"; s << "Data path: " << i2p::fs::GetDataDir() << "
\r\n"; - s << "
"; - if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { - s << "\r\n\r\n

\r\n"; - } - if(includeHiddenContent) { - s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; + s << "

"; + if((outputFormat==OutputFormatEnum::forWebConsole)||!includeHiddenContent) { + s << "\r\n\r\n

\r\n"; + } + if(includeHiddenContent) { + s << "Router Ident: " << i2p::context.GetRouterInfo().GetIdentHashBase64() << "
\r\n"; s << "Router Family: " << i2p::context.GetRouterInfo().GetProperty("family") << "
\r\n"; s << "Router Caps: " << i2p::context.GetRouterInfo().GetProperty("caps") << "
\r\n"; s << "Our external address:" << "
\r\n" ; @@ -292,12 +293,12 @@ namespace http { } s << address->host.to_string() << ":" << address->port << "
\r\n"; } - } + } s << "

\r\n
\r\n"; - if(outputFormat==OutputFormatEnum::forQtUi) { - s << "
"; - } - s << "Routers: " << i2p::data::netdb.GetNumRouters () << " "; + if(outputFormat==OutputFormatEnum::forQtUi) { + s << "
"; + } + s << "Routers: " << i2p::data::netdb.GetNumRouters () << " "; s << "Floodfills: " << i2p::data::netdb.GetNumFloodfills () << " "; s << "LeaseSets: " << i2p::data::netdb.GetNumLeaseSets () << "
\r\n"; @@ -308,17 +309,17 @@ namespace http { s << "Client Tunnels: " << std::to_string(clientTunnelCount) << " "; s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n
\r\n"; - if(outputFormat==OutputFormatEnum::forWebConsole) { - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - s << "\r\n"; - bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); - s << "\r\n"; - s << "
Services
ServiceState
" << "HTTP Proxy" << "
" << "SOCKS Proxy" << "
" << "BOB" << "
" << "SAM" << "
" << "I2CP" << "
" << "I2PControl" << "
\r\n"; - } + if(outputFormat==OutputFormatEnum::forWebConsole) { + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + s << "\r\n"; + bool i2pcontrol; i2p::config::GetOption("i2pcontrol.enabled", i2pcontrol); + s << "\r\n"; + s << "
Services
ServiceState
" << "HTTP Proxy" << "
" << "SOCKS Proxy" << "
" << "BOB" << "
" << "SAM" << "
" << "I2CP" << "
" << "I2PControl" << "
\r\n"; + } } void ShowLocalDestinations (std::stringstream& s) @@ -352,7 +353,7 @@ namespace http { static void ShowLeaseSetDestination (std::stringstream& s, std::shared_ptr dest) { - s << "Base64:
\r\n
\r\n
\r\n"; if (dest->IsEncryptedLeaseSet ()) { @@ -403,19 +404,21 @@ namespace http { s << "
\r\n"; } - void ShowLocalDestination (std::stringstream& s, const std::string& b32) + void ShowLocalDestination (std::stringstream& s, const std::string& b32, uint32_t token) { s << "Local Destination:
\r\n
\r\n"; i2p::data::IdentHash ident; ident.FromBase32 (b32); auto dest = i2p::client::context.FindLocalDestination (ident); + if (dest) { ShowLeaseSetDestination (s, dest); // show streams - s << "\r\n"; - s << ""; - s << ""; + s << "
Streams
StreamIDDestination
\r\n\r\n\r\n"; + s << ""; + s << ""; s << ""; s << ""; s << ""; @@ -424,13 +427,20 @@ namespace http { s << ""; s << ""; s << ""; - s << "\r\n"; + s << "\r\n\r\n\r\n"; for (const auto& it: dest->GetAllStreams ()) { + auto streamDest = i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()); s << ""; - s << ""; - s << ""; + s << ""; + if (it->GetRecvStreamID ()) { + s << ""; + } else { + s << ""; s << ""; s << ""; s << ""; @@ -441,7 +451,7 @@ namespace http { s << ""; s << "\r\n"; } - s << "
Streams
StreamID"; // Stream closing button column + s << "DestinationSentReceivedOutRTTWindowStatus
" << it->GetSendStreamID () << "" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << "" << it->GetRecvStreamID () << ""; + } + s << "" << streamDest << "" << it->GetNumSentBytes () << "" << it->GetNumReceivedBytes () << "" << it->GetSendQueueSize () << "" << (int)it->GetStatus () << "
"; + s << "\r\n"; } } @@ -858,7 +868,8 @@ namespace http { m_Socket->close (); } - bool HTTPConnection::CheckAuth (const HTTPReq & req) { + 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; @@ -920,7 +931,7 @@ namespace http { } else if (req.uri.find("cmd=") != std::string::npos) { HandleCommand (req, res, s); } else { - ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); + ShowStatus (s, true, i2p::http::OutputFormatEnum::forWebConsole); res.add_header("Refresh", "10"); } ShowPageTail (s); @@ -930,7 +941,23 @@ namespace http { 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; @@ -947,18 +974,7 @@ namespace http { ShowTunnels (s); else if (page == HTTP_PAGE_COMMANDS) { - 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; + uint32_t token = CreateToken (); ShowCommands (s, token); } else if (page == HTTP_PAGE_TRANSIT_TUNNELS) @@ -966,7 +982,10 @@ namespace http { else if (page == HTTP_PAGE_LOCAL_DESTINATIONS) ShowLocalDestinations (s); else if (page == HTTP_PAGE_LOCAL_DESTINATION) - ShowLocalDestination (s, params["b32"]); + { + 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) @@ -992,7 +1011,10 @@ namespace http { url.parse(req.uri); url.parse_query(params); + std::string webroot; i2p::config::GetOption("http.webroot", webroot); + std::string redirect = "5; url=" + webroot + "?page=commands"; std::string token = params["token"]; + if (token.empty () || m_Tokens.find (std::stoi (token)) == m_Tokens.end ()) { ShowError(s, "Invalid token"); @@ -1008,36 +1030,74 @@ namespace http { i2p::context.SetAcceptsTunnels (true); else if (cmd == HTTP_COMMAND_DISABLE_TRANSIT) i2p::context.SetAcceptsTunnels (false); - else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { + 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) { + } + 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) { + } + else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) + { #ifndef WIN32_APP Daemon.running = false; #else i2p::win32::StopWin32App (); #endif - } else if (cmd == HTTP_COMMAND_LOGLEVEL){ + } + else if (cmd == HTTP_COMMAND_LOGLEVEL) + { std::string level = params["level"]; SetLogLevel (level); - } else { + } + 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 << "SUCCESS: Stream closed

\r\n"; + else + s << "ERROR: Stream not found or already was closed

\r\n"; + } + else + s << "ERROR: Destination not found

\r\n"; + } + else + s << "ERROR: StreamID can be null

\r\n"; + + s << "Return to destination page
\r\n"; + s << "

You will be redirected back in 5 seconds"; + redirect = "5; url=" + webroot + "?page=local_destination&b32=" + b32; + res.add_header("Refresh", redirect.c_str()); + return; + } + else + { res.code = 400; ShowError(s, "Unknown command: " + cmd); return; } - std::string webroot; i2p::config::GetOption("http.webroot", webroot); - std::string redirect = "5; url=" + webroot + "?page=commands"; + s << "SUCCESS: Command accepted

\r\n"; s << "Back to commands list
\r\n"; s << "

You will be redirected in 5 seconds"; diff --git a/daemon/HTTPServer.h b/daemon/HTTPServer.h index 3d32ed2b..f5ac95fc 100644 --- a/daemon/HTTPServer.h +++ b/daemon/HTTPServer.h @@ -35,6 +35,7 @@ namespace http void HandlePage (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void HandleCommand (const HTTPReq & req, HTTPRes & res, std::stringstream& data); void SendReply (HTTPRes & res, std::string & content); + uint32_t CreateToken (); private: