diff --git a/ClientContext.cpp b/ClientContext.cpp index 59f11f21..2eb81af2 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -328,7 +328,8 @@ namespace client localDestination = CreateNewLocalDestination (k, false, &options); } auto clientTunnel = new I2PClientTunnel (name, dest, address, port, localDestination, destinationPort); - if (m_ClientTunnels.insert (std::make_pair (port, std::unique_ptr(clientTunnel))).second) + if (m_ClientTunnels.insert (std::make_pair (clientTunnel->GetAcceptor ().local_endpoint (), + std::unique_ptr(clientTunnel))).second) clientTunnel->Start (); else LogPrint (eLogError, "Clients: I2P client tunnel with port ", port, " already exists"); @@ -382,7 +383,7 @@ namespace client serverTunnel->SetAccessList (idents); } if (m_ServerTunnels.insert (std::make_pair ( - std::make_tuple (localDestination->GetIdentHash (), inPort), + std::make_pair (localDestination->GetIdentHash (), inPort), std::unique_ptr(serverTunnel))).second) serverTunnel->Start (); else diff --git a/ClientContext.h b/ClientContext.h index a05c2161..3381228b 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -2,9 +2,9 @@ #define CLIENT_CONTEXT_H__ #include -#include #include #include +#include #include "Destination.h" #include "HTTPProxy.h" #include "SOCKS.h" @@ -78,8 +78,8 @@ namespace client i2p::proxy::HTTPProxy * m_HttpProxy; i2p::proxy::SOCKSProxy * m_SocksProxy; - std::map > m_ClientTunnels; // port->tunnel - std::map, std::unique_ptr > m_ServerTunnels; // ->tunnel + std::map > m_ClientTunnels; // local endpoint->tunnel + std::map, std::unique_ptr > m_ServerTunnels; // ->tunnel SAMBridge * m_SamBridge; BOBCommandChannel * m_BOBCommandChannel; diff --git a/Config.cpp b/Config.cpp index 8d42895a..44dec286 100644 --- a/Config.cpp +++ b/Config.cpp @@ -131,11 +131,19 @@ namespace config { #endif ; + options_description limits("Limits options"); + limits.add_options() + ("limits.transittunnels", value()->default_value(2500), "Maximum active transit sessions (default:2500)") + ; + options_description httpserver("HTTP Server options"); httpserver.add_options() ("http.enabled", value()->default_value(true), "Enable or disable webconsole") ("http.address", value()->default_value("127.0.0.1"), "Webconsole listen address") ("http.port", value()->default_value(7070), "Webconsole listen port") + ("http.auth", value()->default_value(false), "Enable Basic HTTP auth for webconsole") + ("http.user", value()->default_value("i2pd"), "Username for basic auth") + ("http.pass", value()->default_value(""), "Password for basic auth (default: random, see logs)") ; options_description httpproxy("HTTP Proxy options"); @@ -180,14 +188,27 @@ namespace config { ("i2pcontrol.key", value()->default_value("i2pcontrol.key.pem"), "I2PCP connection cerificate key") ; + options_description precomputation("Precomputation options"); + precomputation.add_options() + ("precomputation.elgamal", +#if defined(__x86_64__) + value()->default_value(false), +#else + value()->default_value(true), +#endif + "Enable or disable elgamal precomputation table") + ; + m_OptionsDesc .add(general) + .add(limits) .add(httpserver) .add(httpproxy) .add(socksproxy) .add(sam) .add(bob) .add(i2pcontrol) + .add(precomputation) ; } diff --git a/Crypto.cpp b/Crypto.cpp index 8416a337..742296f5 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -146,9 +146,87 @@ namespace crypto } // DH/ElGamal + + const int ELGAMAL_SHORT_EXPONENT_NUM_BITS = 226; + const int ELGAMAL_SHORT_EXPONENT_NUM_BYTES = ELGAMAL_SHORT_EXPONENT_NUM_BITS/8+1; + const int ELGAMAL_FULL_EXPONENT_NUM_BITS = 2048; + const int ELGAMAL_FULL_EXPONENT_NUM_BYTES = ELGAMAL_FULL_EXPONENT_NUM_BITS/8; + #define elgp GetCryptoConstants ().elgp #define elgg GetCryptoConstants ().elgg + static BN_MONT_CTX * g_MontCtx = nullptr; + static void PrecalculateElggTable (BIGNUM * table[][255], int len) // table is len's array of array of 255 bignums + { + if (len <= 0) return; + BN_CTX * ctx = BN_CTX_new (); + g_MontCtx = BN_MONT_CTX_new (); + BN_MONT_CTX_set (g_MontCtx, elgp, ctx); + auto montCtx = BN_MONT_CTX_new (); + BN_MONT_CTX_copy (montCtx, g_MontCtx); + for (int i = 0; i < len; i++) + { + table[i][0] = BN_new (); + if (!i) + BN_to_montgomery (table[0][0], elgg, montCtx, ctx); + else + BN_mod_mul_montgomery (table[i][0], table[i-1][254], table[i-1][0], montCtx, ctx); + for (int j = 1; j < 255; j++) + { + table[i][j] = BN_new (); + BN_mod_mul_montgomery (table[i][j], table[i][j-1], table[i][0], montCtx, ctx); + } + } + BN_MONT_CTX_free (montCtx); + BN_CTX_free (ctx); + } + + static void DestroyElggTable (BIGNUM * table[][255], int len) + { + for (int i = 0; i < len; i++) + for (int j = 0; j < 255; j++) + { + BN_free (table[i][j]); + table[i][j] = nullptr; + } + BN_MONT_CTX_free (g_MontCtx); + } + + static BIGNUM * ElggPow (const uint8_t * exp, int len, BIGNUM * table[][255], BN_CTX * ctx) + // exp is in Big Endian + { + if (len <= 0) return nullptr; + auto montCtx = BN_MONT_CTX_new (); + BN_MONT_CTX_copy (montCtx, g_MontCtx); + BIGNUM * res = nullptr; + for (int i = 0; i < len; i++) + { + if (res) + { + if (exp[i]) + BN_mod_mul_montgomery (res, res, table[len-1-i][exp[i]-1], montCtx, ctx); + } + else if (exp[i]) + res = BN_dup (table[len-i-1][exp[i]-1]); + } + if (res) + BN_from_montgomery (res, res, montCtx, ctx); + BN_MONT_CTX_free (montCtx); + return res; + } + + static BIGNUM * ElggPow (const BIGNUM * exp, BIGNUM * table[][255], BN_CTX * ctx) + { + auto len = BN_num_bytes (exp); + uint8_t * buf = new uint8_t[len]; + BN_bn2bin (exp, buf); + auto ret = ElggPow (buf, len, table, ctx); + delete[] buf; + return ret; + } + + static BIGNUM * (* g_ElggTable)[255] = nullptr; + // DH DHKeys::DHKeys (): m_IsUpdated (true) @@ -169,7 +247,23 @@ namespace crypto { if (m_DH->priv_key) { BN_free (m_DH->priv_key); m_DH->priv_key = NULL; }; if (m_DH->pub_key) { BN_free (m_DH->pub_key); m_DH->pub_key = NULL; }; - DH_generate_key (m_DH); +#if !defined(__x86_64__) // use short exponent for non x64 + m_DH->priv_key = BN_new (); + BN_rand (m_DH->priv_key, ELGAMAL_SHORT_EXPONENT_NUM_BITS, 0, 1); +#endif + if (g_ElggTable) + { +#if defined(__x86_64__) + m_DH->priv_key = BN_new (); + BN_rand (m_DH->priv_key, ELGAMAL_FULL_EXPONENT_NUM_BITS, 0, 1); +#endif + auto ctx = BN_CTX_new (); + m_DH->pub_key = ElggPow (m_DH->priv_key, g_ElggTable, ctx); + BN_CTX_free (ctx); + } + else + DH_generate_key (m_DH); + if (priv) bn2buf (m_DH->priv_key, priv, 256); if (pub) bn2buf (m_DH->pub_key, pub, 256); m_IsUpdated = true; @@ -200,11 +294,18 @@ namespace crypto ctx = BN_CTX_new (); // select random k BIGNUM * k = BN_new (); - BN_rand_range (k, elgp); - if (BN_is_zero (k)) BN_one (k); - // caulculate a +#if defined(__x86_64__) + BN_rand (k, ELGAMAL_FULL_EXPONENT_NUM_BITS, -1, 1); // full exponent for x64 +#else + BN_rand (k, ELGAMAL_SHORT_EXPONENT_NUM_BITS, -1, 1); // short exponent of 226 bits +#endif + // calculate a a = BN_new (); - BN_mod_exp (a, elgg, k, elgp, ctx); + if (g_ElggTable) + a = ElggPow (k, g_ElggTable, ctx); + else + BN_mod_exp (a, elgg, k, elgp, ctx); + BIGNUM * y = BN_new (); BN_bin2bn (key, 256, y); // calculate b1 @@ -279,6 +380,14 @@ namespace crypto { #if defined(__x86_64__) || defined(__i386__) || defined(_MSC_VER) RAND_bytes (priv, 256); +#else + // lower 226 bits (28 bytes and 2 bits) only. short exponent + auto numBytes = (ELGAMAL_SHORT_EXPONENT_NUM_BITS)/8 + 1; // 29 + auto numZeroBytes = 256 - numBytes; + RAND_bytes (priv + numZeroBytes, numBytes); + memset (priv, 0, numZeroBytes); + priv[numZeroBytes] &= 0x03; +#endif BN_CTX * ctx = BN_CTX_new (); BIGNUM * p = BN_new (); BN_bin2bn (priv, 256, p); @@ -286,11 +395,6 @@ namespace crypto bn2buf (p, pub, 256); BN_free (p); BN_CTX_free (ctx); -#else - DHKeys dh; - dh.GenerateKeys (priv, pub); - -#endif } // HMAC @@ -695,17 +799,38 @@ namespace crypto } }*/ - void InitCrypto () + void InitCrypto (bool precomputation) { SSL_library_init (); /* auto numLocks = CRYPTO_num_locks(); for (int i = 0; i < numLocks; i++) m_OpenSSLMutexes.emplace_back (new std::mutex); CRYPTO_set_locking_callback (OpensslLockingCallback);*/ + if (precomputation) + { +#if defined(__x86_64__) + g_ElggTable = new BIGNUM * [ELGAMAL_FULL_EXPONENT_NUM_BYTES][255]; + PrecalculateElggTable (g_ElggTable, ELGAMAL_FULL_EXPONENT_NUM_BYTES); +#else + g_ElggTable = new BIGNUM * [ELGAMAL_SHORT_EXPONENT_NUM_BYTES][255]; + PrecalculateElggTable (g_ElggTable, ELGAMAL_SHORT_EXPONENT_NUM_BYTES); +#endif + } } void TerminateCrypto () { + if (g_ElggTable) + { + DestroyElggTable (g_ElggTable, +#if defined(__x86_64__) + ELGAMAL_FULL_EXPONENT_NUM_BYTES +#else + ELGAMAL_SHORT_EXPONENT_NUM_BYTES +#endif + ); + delete[] g_ElggTable; g_ElggTable = nullptr; + } /* CRYPTO_set_locking_callback (nullptr); m_OpenSSLMutexes.clear ();*/ } diff --git a/Crypto.h b/Crypto.h index e633f8bf..e333940e 100644 --- a/Crypto.h +++ b/Crypto.h @@ -273,7 +273,7 @@ namespace crypto #endif }; - void InitCrypto (); + void InitCrypto (bool precomputation); void TerminateCrypto (); } } diff --git a/Daemon.cpp b/Daemon.cpp index f15fe3e3..c98bce05 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -13,6 +13,7 @@ #include "RouterInfo.h" #include "RouterContext.h" #include "Tunnel.h" +#include "HTTP.h" #include "NetDb.h" #include "Garlic.h" #include "Streaming.h" @@ -36,7 +37,7 @@ namespace i2p Daemon_Singleton_Private() {}; ~Daemon_Singleton_Private() {}; - std::unique_ptr httpServer; + std::unique_ptr httpServer; std::unique_ptr m_I2PControlService; #ifdef USE_UPNP @@ -117,7 +118,8 @@ namespace i2p LogPrint(eLogDebug, "FS: main config file: ", config); LogPrint(eLogDebug, "FS: data directory: ", datadir); - i2p::crypto::InitCrypto (); + bool precomputation; i2p::config::GetOption("precomputation.elgamal", precomputation); + i2p::crypto::InitCrypto (precomputation); i2p::context.Init (); uint16_t port; i2p::config::GetOption("port", port); @@ -140,6 +142,8 @@ namespace i2p i2p::context.SetSupportsV6 (ipv6); i2p::context.SetSupportsV4 (ipv4); i2p::context.SetAcceptsTunnels (!transit); + uint16_t transitTunnels; i2p::config::GetOption("limits.transittunnels", transitTunnels); + SetMaxNumTransitTunnels (transitTunnels); bool isFloodfill; i2p::config::GetOption("floodfill", isFloodfill); if (isFloodfill) { @@ -199,7 +203,7 @@ namespace i2p std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); LogPrint(eLogInfo, "Daemon: starting HTTP Server at ", httpAddr, ":", httpPort); - d.httpServer = std::unique_ptr(new i2p::util::HTTPServer(httpAddr, httpPort)); + d.httpServer = std::unique_ptr(new i2p::http::HTTPServer(httpAddr, httpPort)); d.httpServer->Start(); } diff --git a/Destination.cpp b/Destination.cpp index c53c4ee6..1534cbf9 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -9,6 +9,7 @@ #include "Timestamp.h" #include "NetDb.h" #include "Destination.h" +#include "util.h" namespace i2p { @@ -35,28 +36,30 @@ namespace client { auto it = params->find (I2CP_PARAM_INBOUND_TUNNEL_LENGTH); if (it != params->end ()) - { - int len = boost::lexical_cast(it->second); + { + + int len = i2p::util::lexical_cast(it->second, inboundTunnelLen); if (len > 0) { - inboundTunnelLen = len; - LogPrint (eLogInfo, "Destination: Inbound tunnel length set to ", len); + inboundTunnelLen = len; } + LogPrint (eLogInfo, "Destination: Inbound tunnel length set to ", inboundTunnelLen); } it = params->find (I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH); if (it != params->end ()) { - int len = boost::lexical_cast(it->second); + + int len = i2p::util::lexical_cast(it->second, outboundTunnelLen); if (len > 0) { - outboundTunnelLen = len; - LogPrint (eLogInfo, "Destination: Outbound tunnel length set to ", len); + outboundTunnelLen = len; } + LogPrint (eLogInfo, "Destination: Outbound tunnel length set to ", outboundTunnelLen); } it = params->find (I2CP_PARAM_INBOUND_TUNNELS_QUANTITY); if (it != params->end ()) { - int quantity = boost::lexical_cast(it->second); + int quantity = i2p::util::lexical_cast(it->second, inboundTunnelsQuantity); if (quantity > 0) { inboundTunnelsQuantity = quantity; @@ -66,7 +69,7 @@ namespace client it = params->find (I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY); if (it != params->end ()) { - int quantity = boost::lexical_cast(it->second); + int quantity = i2p::util::lexical_cast(it->second, outboundTunnelsQuantity); if (quantity > 0) { outboundTunnelsQuantity = quantity; @@ -76,11 +79,11 @@ namespace client it = params->find (I2CP_PARAM_TAGS_TO_SEND); if (it != params->end ()) { - int tagsToSend = boost::lexical_cast(it->second); + int tagsToSend = i2p::util::lexical_cast(it->second, numTags); if (tagsToSend > 0) { numTags = tagsToSend; - LogPrint (eLogInfo, "Destination: Tags to send set to ", tagsToSend); + LogPrint (eLogInfo, "Destination: Tags to send set to ", tagsToSend); } } it = params->find (I2CP_PARAM_EXPLICIT_PEERS); @@ -780,5 +783,19 @@ namespace client } LogPrint(eLogError, "Destinations: Can't save keys to ", path); } + + std::vector > ClientDestination::GetAllStreams () const + { + std::vector > ret; + if (m_StreamingDestination) + { + for (auto& it: m_StreamingDestination->GetStreams ()) + ret.push_back (it.second); + } + for (auto& it: m_StreamingDestinationsByPorts) + for (auto& it1: it.second->GetStreams ()) + ret.push_back (it1.second); + return ret; + } } } diff --git a/Destination.h b/Destination.h index 44cb7424..3011b4cb 100644 --- a/Destination.h +++ b/Destination.h @@ -159,6 +159,7 @@ namespace client // for HTTP only int GetNumRemoteLeaseSets () const { return m_RemoteLeaseSets.size (); }; + std::vector > GetAllStreams () const; }; } } diff --git a/HTTP.cpp b/HTTP.cpp new file mode 100644 index 00000000..74acfb7a --- /dev/null +++ b/HTTP.cpp @@ -0,0 +1,389 @@ +/* +* Copyright (c) 2013-2016, 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 "HTTP.h" + +namespace i2p { +namespace http { + const char *HTTP_METHODS[] = { + "GET", "HEAD", "POST", "PUT", "PATCH", + "DELETE", "OPTIONS", "CONNECT", + NULL + }; + const char *HTTP_VERSIONS[] = { + "HTTP/1.0", "HTTP/1.1", NULL + }; + + bool in_cstr_array(const char **haystack, const char *needle) { + for (const char *p = haystack[0]; p != NULL; p++) { + if (strcmp(p, needle) == 0) + return true; + } + return false; + } + + void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { + std::size_t count = 0; + std::stringstream ss(line); + std::string token; + while (1) { + count++; + if (limit > 0 && count >= limit) + delim = '\n'; /* reset delimiter */ + if (!std::getline(ss, token, delim)) + break; + tokens.push_back(token); + } + } + + bool parse_header_line(const std::string & line, std::map & headers) { + std::size_t pos = 0; + std::size_t len = 2; /* strlen(": ") */ + if ((pos = line.find(": ", pos)) == std::string::npos) + return false; + while (isspace(line.at(pos + len))) + len++; + std::string name = line.substr(0, pos); + std::string value = line.substr(pos + len); + headers[name] = value; + return true; + } + + bool URL::parse(const char *str, std::size_t len) { + std::string url(str, len ? len : strlen(str)); + return parse(url); + } + + bool URL::parse(const std::string& url) { + std::size_t pos_p = 0; /* < current parse position */ + std::size_t pos_c = 0; /* < work position */ + if (url.at(0) != '/') { + /* schema */ + pos_c = url.find("://"); + if (pos_c != std::string::npos) { + schema = url.substr(0, pos_c); + pos_p = pos_c + 3; + } + /* user[:pass] */ + pos_c = url.find('@', pos_p); + if (pos_c != std::string::npos) { + std::size_t delim = url.find(':', pos_p); + if (delim != std::string::npos && delim < pos_c) { + user = url.substr(pos_p, delim - pos_p); + delim += 1; + pass = url.substr(delim, pos_c - delim); + } else { + user = url.substr(pos_p, pos_c - pos_p); + } + pos_p = pos_c + 1; + } + /* hostname[:port][/path] */ + pos_c = url.find_first_of(":/", pos_p); + if (pos_c == std::string::npos) { + /* only hostname, without post and path */ + host = url.substr(pos_p, std::string::npos); + return true; + } else if (url.at(pos_c) == ':') { + host = url.substr(pos_p, pos_c - pos_p); + /* port[/path] */ + pos_p = pos_c + 1; + pos_c = url.find('/', pos_p); + std::string port_str = (pos_c == std::string::npos) + ? url.substr(pos_p, std::string::npos) + : url.substr(pos_p, pos_c - pos_p); + /* stoi throws exception on failure, we don't need it */ + for (char c : port_str) { + if (c < '0' || c > '9') + return false; + port *= 10; + port += c - '0'; + } + if (pos_c == std::string::npos) + return true; /* no path part */ + pos_p = pos_c; + } else { + /* start of path part found */ + host = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c; + } + } + + /* pos_p now at start of path part */ + pos_c = url.find_first_of("?#", pos_p); + if (pos_c == std::string::npos) { + /* only path, without fragment and query */ + path = url.substr(pos_p, std::string::npos); + return true; + } else if (url.at(pos_c) == '?') { + /* found query part */ + path = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + pos_c = url.find('#', pos_p); + if (pos_c == std::string::npos) { + /* no fragment */ + query = url.substr(pos_p, std::string::npos); + return true; + } else { + query = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + } + } else { + /* found fragment part */ + path = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + } + + /* pos_p now at start of fragment part */ + frag = url.substr(pos_p, std::string::npos); + return true; + } + + bool URL::parse_query(std::map & params) { + std::vector tokens; + strsplit(query, tokens, '&'); + + params.clear(); + for (auto it : tokens) { + std::size_t eq = it.find ('='); + if (eq != std::string::npos) { + auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); + params.insert(e); + } else { + auto e = std::pair(it, ""); + params.insert(e); + } + } + return true; + } + + std::string URL::to_string() { + std::string out = ""; + if (schema != "") { + out = schema + "://"; + if (user != "" && pass != "") { + out += user + ":" + pass + "@"; + } else if (user != "") { + out += user + "@"; + } + if (port) { + out += host + ":" + std::to_string(port); + } else { + out += host; + } + } + out += path; + if (query != "") + out += "?" + query; + if (frag != "") + out += "#" + frag; + return out; + } + + int HTTPReq::parse(const char *buf, size_t len) { + std::string str(buf, len); + return parse(str); + } + + int HTTPReq::parse(const std::string& str) { + enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE; + std::size_t eoh = str.find(HTTP_EOH); /* request head size */ + std::size_t eol = 0, pos = 0; + URL url; + + if (eoh == std::string::npos) + return 0; /* str not contains complete request */ + + while ((eol = str.find(CRLF, pos)) != std::string::npos) { + if (expect == REQ_LINE) { + std::string line = str.substr(pos, eol - pos); + std::vector tokens; + strsplit(line, tokens, ' '); + if (tokens.size() != 3) + return -1; + if (!in_cstr_array(HTTP_METHODS, tokens[0].c_str())) + return -1; + if (!in_cstr_array(HTTP_VERSIONS, tokens[2].c_str())) + return -1; + if (!url.parse(tokens[1])) + return -1; + /* all ok */ + method = tokens[0]; + uri = tokens[1]; + version = tokens[2]; + expect = HEADER_LINE; + } else { + std::string line = str.substr(pos, eol - pos); + if (!parse_header_line(line, headers)) + return -1; + } + pos = eol + strlen(CRLF); + if (pos >= eoh) + break; + } + auto it = headers.find("Host"); + if (it != headers.end ()) { + host = it->second; + } else if (version == "HTTP/1.1") { + return -1; /* 'Host' header required for HTTP/1.1 */ + } else if (url.host != "") { + host = url.host; + } + return eoh + strlen(HTTP_EOH); + } + + std::string HTTPReq::to_string() { + std::stringstream ss; + ss << method << " " << uri << " " << version << CRLF; + ss << "Host: " << host << CRLF; + for (auto & h : headers) { + ss << h.first << ": " << h.second << CRLF; + } + ss << CRLF; + return ss.str(); + } + + bool HTTPRes::is_chunked() { + auto it = headers.find("Transfer-Encoding"); + if (it == headers.end()) + return false; + if (it->second.find("chunked") == std::string::npos) + return true; + return false; + } + + long int HTTPRes::length() { + unsigned long int length = 0; + auto it = headers.find("Content-Length"); + if (it == headers.end()) + return -1; + errno = 0; + length = std::strtoul(it->second.c_str(), (char **) NULL, 10); + if (errno != 0) + return -1; + return length; + } + + int HTTPRes::parse(const char *buf, size_t len) { + std::string str(buf, len); + return parse(str); + } + + int HTTPRes::parse(const std::string& str) { + enum { RES_LINE, HEADER_LINE } expect = RES_LINE; + std::size_t eoh = str.find(HTTP_EOH); /* request head size */ + std::size_t eol = 0, pos = 0; + + if (eoh == std::string::npos) + return 0; /* str not contains complete request */ + + while ((eol = str.find(CRLF, pos)) != std::string::npos) { + if (expect == RES_LINE) { + std::string line = str.substr(pos, eol - pos); + std::vector tokens; + strsplit(line, tokens, ' ', 3); + if (tokens.size() != 3) + return -1; + if (!in_cstr_array(HTTP_VERSIONS, tokens[0].c_str())) + return -1; + code = atoi(tokens[1].c_str()); + if (code < 100 || code >= 600) + return -1; + /* all ok */ + version = tokens[0]; + status = tokens[2]; + expect = HEADER_LINE; + } else { + std::string line = str.substr(pos, eol - pos); + if (!parse_header_line(line, headers)) + return -1; + } + pos = eol + strlen(CRLF); + if (pos >= eoh) + break; + } + + return eoh + strlen(HTTP_EOH); + } + + std::string HTTPRes::to_string() { + std::stringstream ss; + ss << version << " " << code << " " << status << CRLF; + for (auto & h : headers) { + ss << h.first << ": " << h.second << CRLF; + } + ss << CRLF; + return ss.str(); + } + + const char * HTTPCodeToStatus(int code) { + const char *ptr; + switch (code) { + case 105: ptr = "Name Not Resolved"; break; + /* success */ + case 200: ptr = "OK"; break; + case 206: ptr = "Partial Content"; break; + /* redirect */ + case 301: ptr = "Moved Permanently"; break; + case 302: ptr = "Found"; break; + case 304: ptr = "Not Modified"; break; + case 307: ptr = "Temporary Redirect"; break; + /* client error */ + case 400: ptr = "Bad Request"; break; + case 401: ptr = "Unauthorized"; break; + case 403: ptr = "Forbidden"; break; + case 404: ptr = "Not Found"; break; + case 407: ptr = "Proxy Authentication Required"; break; + case 408: ptr = "Request Timeout"; break; + /* server error */ + case 500: ptr = "Internal Server Error"; break; + case 502: ptr = "Bad Gateway"; break; + case 503: ptr = "Not Implemented"; break; + case 504: ptr = "Gateway Timeout"; break; + default: ptr = "Unknown Status"; break; + } + return ptr; + } + + std::string UrlDecode(const std::string& data, bool allow_null) { + std::string decoded(data); + size_t pos = 0; + while ((pos = decoded.find('%', pos)) != std::string::npos) { + char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); + if (c == '\0' && !allow_null) { + pos += 3; + continue; + } + decoded.replace(pos, 3, 1, c); + pos++; + } + return decoded; + } + + bool MergeChunkedResponse (std::istream& in, std::ostream& out) { + std::string hexLen; + long int len; + while (!in.eof ()) { + std::getline (in, hexLen); + errno = 0; + len = strtoul(hexLen.c_str(), (char **) NULL, 16); + if (errno != 0) + return false; /* conversion error */ + if (len == 0) + return true; /* end of stream */ + if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */ + return false; /* too large chunk */ + char * buf = new char[len]; + in.read (buf, len); + out.write (buf, len); + delete[] buf; + std::getline (in, hexLen); // read \r\n after chunk + } + return true; + } +} // http +} // i2p diff --git a/HTTP.h b/HTTP.h new file mode 100644 index 00000000..864ad88b --- /dev/null +++ b/HTTP.h @@ -0,0 +1,121 @@ +/* +* Copyright (c) 2013-2016, 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 +*/ + +#ifndef HTTP_H__ +#define HTTP_H__ + +#include +#include +#include +#include +#include + +namespace i2p { +namespace http { + const char CRLF[] = "\r\n"; /**< HTTP line terminator */ + const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */ + extern const char *HTTP_METHODS[]; /**< list of valid HTTP methods */ + extern const char *HTTP_VERSIONS[]; /**< list of valid HTTP versions */ + + struct URL { + std::string schema; + std::string user; + std::string pass; + std::string host; + unsigned short int port; + std::string path; + std::string query; + std::string frag; + + URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {}; + + /** + * @brief Tries to parse url from string + * @return true on success, false on invalid url + */ + bool parse (const char *str, size_t len = 0); + bool parse (const std::string& url); + + /** + * @brief Parse query part of url to key/value map + * @note Honestly, this should be implemented with std::multimap + */ + bool parse_query(std::map & params); + + /** + * @brief Serialize URL structure to url + * @note Returns relative url if schema if empty, absolute url otherwise + */ + std::string to_string (); + }; + + struct HTTPReq { + std::map headers; + std::string version; + std::string method; + std::string uri; + std::string host; + + HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {}; + + /** + * @brief Tries to parse HTTP request from string + * @return -1 on error, 0 on incomplete query, >0 on success + * @note Positive return value is a size of header + */ + int parse(const char *buf, size_t len); + int parse(const std::string& buf); + + /** @brief Serialize HTTP request to string */ + std::string to_string(); + }; + + struct HTTPRes { + std::map headers; + std::string version; + std::string status; + unsigned short int code; + + HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {} + + /** + * @brief Tries to parse HTTP response from string + * @return -1 on error, 0 on incomplete query, >0 on success + * @note Positive return value is a size of header + */ + int parse(const char *buf, size_t len); + int parse(const std::string& buf); + + /** @brief Serialize HTTP response to string */ + std::string to_string(); + + /** @brief Checks that response declared as chunked data */ + bool is_chunked(); + + /** @brief Returns declared response length or -1 if unknown */ + long int length(); + }; + + /** + * @brief returns HTTP status string by integer code + * @param code HTTP code [100, 599] + * @return Immutable string with status + */ + const char * HTTPCodeToStatus(int code); + + /** + * @brief Replaces %-encoded characters in string with their values + * @param data Source string + * @param null If set to true - decode also %00 sequence, otherwise - skip + * @return Decoded string + */ + std::string UrlDecode(const std::string& data, bool null = false); +} // http +} // i2p + +#endif /* HTTP_H__ */ diff --git a/HTTPServer.cpp b/HTTPServer.cpp index f7efcba9..7ad8279d 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1,31 +1,34 @@ #include #include +#include +#include +#include + +#include #include -#include -#include + #include "Base.h" #include "FS.h" #include "Log.h" +#include "Config.h" #include "Tunnel.h" #include "TransitTunnel.h" #include "Transports.h" #include "NetDb.h" -#include "I2PEndian.h" -#include "Streaming.h" +#include "HTTP.h" +#include "LeaseSet.h" #include "Destination.h" #include "RouterContext.h" #include "ClientContext.h" #include "HTTPServer.h" +#include "Daemon.h" // For image and info #include "version.h" -namespace i2p -{ -namespace util -{ - - const std::string HTTPConnection::itoopieImage = +namespace i2p { +namespace http { + const char *itoopieImage = "\"ICToopie"; - const std::string HTTPConnection::itoopieFavicon = + const char *itoopieFavicon = "data:image/png;base64," "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv" "8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My4wOGVynO" @@ -190,227 +193,130 @@ namespace util "MYez0Gm9P2iWna0GOcDp8KY2JhAsnbSQS6Ahh9OgrlklINeM40bWhAkBd4SLIEh8cBURLhOeiBIArVA" "U4yTRvJItk5PRehQVFaYfpbt9PBtTmdziaXyyUzjaHT/QZBQuKHAA0UxAAAAABJRU5ErkJggg=="; - const char HTTP_COMMAND_TUNNELS[] = "tunnels"; - const char HTTP_COMMAND_TRANSIT_TUNNELS[] = "transit_tunnels"; - const char HTTP_COMMAND_TRANSPORTS[] = "transports"; + const char *cssStyles = + "\r\n"; + + const char HTTP_PAGE_TUNNELS[] = "tunnels"; + const char HTTP_PAGE_TRANSIT_TUNNELS[] = "transit_tunnels"; + const char HTTP_PAGE_TRANSPORTS[] = "transports"; + const char HTTP_PAGE_LOCAL_DESTINATIONS[] = "local_destinations"; + const char HTTP_PAGE_LOCAL_DESTINATION[] = "local_destination"; + const char HTTP_PAGE_SAM_SESSIONS[] = "sam_sessions"; + const char HTTP_PAGE_SAM_SESSION[] = "sam_session"; + const char HTTP_PAGE_I2P_TUNNELS[] = "i2p_tunnels"; + const char HTTP_PAGE_JUMPSERVICES[] = "jumpservices"; + const char HTTP_PAGE_COMMANDS[] = "commands"; const char HTTP_COMMAND_START_ACCEPTING_TUNNELS[] = "start_accepting_tunnels"; const char HTTP_COMMAND_STOP_ACCEPTING_TUNNELS[] = "stop_accepting_tunnels"; + 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_LOCAL_DESTINATIONS[] = "local_destinations"; - const char HTTP_COMMAND_LOCAL_DESTINATION[] = "local_destination"; const char HTTP_PARAM_BASE32_ADDRESS[] = "b32"; - const char HTTP_COMMAND_SAM_SESSIONS[] = "sam_sessions"; - const char HTTP_COMMAND_SAM_SESSION[] = "sam_session"; const char HTTP_PARAM_SAM_SESSION_ID[] = "id"; - const char HTTP_COMMAND_I2P_TUNNELS[] = "i2p_tunnels"; - const char HTTP_COMMAND_JUMPSERVICES[] = "jumpservices="; const char HTTP_PARAM_ADDRESS[] = "address"; - - namespace misc_strings - { - - const char name_value_separator[] = { ':', ' ' }; - const char crlf[] = { '\r', '\n' }; + std::map jumpservices = { + { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, + { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, + }; - } // namespace misc_strings + void ShowUptime (std::stringstream& s, int seconds) { + int num; - std::vector HTTPConnection::reply::to_buffers(int status) - { - std::vector buffers; - if (headers.size () > 0) - { - status_string = "HTTP/1.1 "; - status_string += std::to_string (status); - status_string += " "; - switch (status) - { - case 105: status_string += "Name Not Resolved"; break; - case 200: status_string += "OK"; break; - case 400: status_string += "Bad Request"; break; - case 404: status_string += "Not Found"; break; - case 408: status_string += "Request Timeout"; break; - case 500: status_string += "Internal Server Error"; break; - case 502: status_string += "Bad Gateway"; break; - case 503: status_string += "Not Implemented"; break; - case 504: status_string += "Gateway Timeout"; break; - default: status_string += "WTF"; - } - buffers.push_back(boost::asio::buffer(status_string, status_string.size())); - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - - for (std::size_t i = 0; i < headers.size(); ++i) - { - header& h = headers[i]; - buffers.push_back(boost::asio::buffer(h.name)); - buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator)); - buffers.push_back(boost::asio::buffer(h.value)); - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); - } - buffers.push_back(boost::asio::buffer(misc_strings::crlf)); + if ((num = seconds / 86400) > 0) { + s << num << " days, "; + seconds -= num; } - buffers.push_back(boost::asio::buffer(content)); - return buffers; - } - - void HTTPConnection::Terminate () - { - if (!m_Stream) return; - m_Stream->Close (); - m_Stream = nullptr; - m_Socket->close (); - } - - 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 (!m_Stream) // new request - { - m_Buffer[bytes_transferred] = 0; - m_BufferLen = bytes_transferred; - RunRequest(); - } - else // follow-on - m_Stream->Send ((uint8_t *)m_Buffer, bytes_transferred); - Receive (); + if ((num = seconds / 3600) > 0) { + s << num << " hours, "; + seconds -= num; } - else if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - - void HTTPConnection::RunRequest () - { - auto address = ExtractAddress (); - if (address.length () > 1 && address[1] != '?') // not just '/' or '/?' - { - std::string uri ("/"), b32; - size_t pos = address.find ('/', 1); - if (pos == std::string::npos) - b32 = address.substr (1); // excluding leading '/' to end of line - else - { - b32 = address.substr (1, pos - 1); // excluding leading '/' to next '/' - uri = address.substr (pos); // rest of line - } - - HandleDestinationRequest (b32, uri); + if ((num = seconds / 60) > 0) { + s << num << " min, "; + seconds -= num; } - else - HandleRequest (address); + s << seconds << " seconds"; } - std::string HTTPConnection::ExtractAddress () + void ShowTunnelDetails (std::stringstream& s, enum i2p::tunnel::TunnelState eState, int bytes) { - char * get = strstr (m_Buffer, "GET"); - if (get) - { - char * http = strstr (get, "HTTP"); - if (http) - return std::string (get + 4, http - get - 5); + std::string state; + 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; } - return ""; + s << " " << state << ", "; + s << " " << (int) (bytes / 1024) << " KiB
\r\n"; } - void HTTPConnection::ExtractParams (const std::string& str, std::map& params) - { - if (str[0] != '&') return; - size_t pos = 1, end; - do - { - end = str.find ('&', pos); - std::string param = str.substr (pos, end - pos); - LogPrint (eLogDebug, "HTTPServer: extracted parameters: ", param); - size_t e = param.find ('='); - if (e != std::string::npos) - params[param.substr(0, e)] = param.substr(e+1); - pos = end + 1; - } - while (end != std::string::npos); - } - - void HTTPConnection::HandleWriteReply (const boost::system::error_code& ecode) + void ShowPageHead (std::stringstream& s) { - if (ecode != boost::asio::error::operation_aborted) - { - boost::system::error_code ignored_ec; - m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); - Terminate (); - } + s << + "\r\n" + "\r\n" /* TODO: Add support for locale */ + " \r\n" + " \r\n" /* TODO: Find something to parse html/template system. This is horrible. */ + " \r\n" + " Purple I2P " VERSION " Webconsole\r\n" + << cssStyles << + "\r\n"; + s << + "\r\n" + "
i2pd webconsole
\r\n" + "
\r\n" + "
\r\n" + " Main page
\r\n
\r\n" + " Router commands
\r\n" + " Local destinations
\r\n" + " Tunnels
\r\n" + " Transit tunnels
\r\n" + " Transports
\r\n" + " I2P tunnels
\r\n" + " Jump services
\r\n" + " SAM sessions
\r\n" + "
\r\n" + "
"; } - void HTTPConnection::HandleWrite (const boost::system::error_code& ecode) + void ShowPageTail (std::stringstream& s) { - if (ecode || (m_Stream && !m_Stream->IsOpen ())) - { - if (ecode != boost::asio::error::operation_aborted) - Terminate (); - } - else // data keeps coming - AsyncStreamReceive (); + s << + "
\r\n" + "\r\n" + "\r\n"; } - void HTTPConnection::HandleRequest (const std::string& address) + void ShowError(std::stringstream& s, const std::string& string) { - std::stringstream s; - // Html5 head start - s << "\r\n"; // TODO: Add support for locale. - s << "\r\n\r\n"; // TODO: Find something to parse html/template system. This is horrible. - s << "\r\n"; - s << "Purple I2P " << VERSION " Webconsole\r\n"; - s << "\r\n\r\n\r\n"; - s << "
i2pd webconsole
"; - s << "
"; - s << "
\r\n"; - s << "Main page
\r\n
\r\n"; - s << "Local destinations
\r\n"; - s << "Tunnels
\r\n"; - s << "Transit tunnels
\r\n"; - s << "Transports
\r\n
\r\n"; - s << "I2P tunnels
\r\n"; - if (i2p::client::context.GetSAMBridge ()) - s << "SAM sessions
\r\n
\r\n"; - if (i2p::context.AcceptsTunnels ()) - s << "Stop accepting tunnels
\r\n
\r\n"; - else - s << "Start accepting tunnels
\r\n
\r\n"; - s << "Run peer test
\r\n
\r\n"; - s << "Jump services
\r\n
\r\n"; - s << "
"; - if (address.length () > 1) - HandleCommand (address.substr (2), s); - else - FillContent (s); - s << "
\r\n\r\n"; - SendReply (s.str ()); + s << "ERROR: " << string << "
\r\n"; } - void HTTPConnection::FillContent (std::stringstream& s) + void ShowStatus (std::stringstream& s) { - s << "Uptime: " << boost::posix_time::to_simple_string ( - boost::posix_time::time_duration (boost::posix_time::seconds ( - i2p::context.GetUptime ()))) << "
\r\n"; + s << "Uptime: "; + ShowUptime(s, i2p::context.GetUptime ()); + s << "
\r\n"; s << "Status: "; switch (i2p::context.GetStatus ()) { @@ -420,6 +326,9 @@ namespace util default: s << "Unknown"; } s << "
\r\n"; + auto family = i2p::context.GetFamily (); + if (family.length () > 0) + s << "Family: " << family << "
\r\n"; s << "Tunnel creation success rate: " << i2p::tunnel::tunnels.GetTunnelCreationSuccessRate () << "%
\r\n"; s << "Received: "; s << std::fixed << std::setprecision(2); @@ -475,73 +384,32 @@ namespace util s << "Transit Tunnels: " << std::to_string(transitTunnelCount) << "
\r\n"; } - void HTTPConnection::HandleCommand (const std::string& command, std::stringstream& s) + void ShowJumpServices (std::stringstream& s, const std::string& address) { - size_t paramsPos = command.find('&'); - std::string cmd = command.substr (0, paramsPos); - if (cmd == HTTP_COMMAND_TRANSPORTS) - ShowTransports (s); - else if (cmd == HTTP_COMMAND_TUNNELS) - ShowTunnels (s); - else if (cmd == HTTP_COMMAND_JUMPSERVICES) - { - std::map params; - ExtractParams (command.substr (paramsPos), params); - auto address = params[HTTP_PARAM_ADDRESS]; - ShowJumpServices (address, s); - } else if (cmd == HTTP_COMMAND_TRANSIT_TUNNELS) - ShowTransitTunnels (s); - else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) - StartAcceptingTunnels (s); - else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) - StopAcceptingTunnels (s); - else if (cmd == HTTP_COMMAND_RUN_PEER_TEST) - RunPeerTest (s); - else if (cmd == HTTP_COMMAND_LOCAL_DESTINATIONS) - ShowLocalDestinations (s); - else if (cmd == HTTP_COMMAND_LOCAL_DESTINATION) - { - std::map params; - ExtractParams (command.substr (paramsPos), params); - auto b32 = params[HTTP_PARAM_BASE32_ADDRESS]; - ShowLocalDestination (b32, s); - } - else if (cmd == HTTP_COMMAND_SAM_SESSIONS) - ShowSAMSessions (s); - else if (cmd == HTTP_COMMAND_SAM_SESSION) - { - std::map params; - ExtractParams (command.substr (paramsPos), params); - auto id = params[HTTP_PARAM_SAM_SESSION_ID]; - ShowSAMSession (id, s); - } - else if (cmd == HTTP_COMMAND_I2P_TUNNELS) - ShowI2PTunnels (s); - } + s << "
"; + s << ""; + s << ""; + s << ""; + s << "

\r\n"; + s << "Jump services for " << address << "\r\n\r\n"; + } - void HTTPConnection::ShowJumpServices (const std::string& address, std::stringstream& s) - { - s << "
"; - s << ""; - s << "

\r\n"; - s << "Jump services for " << address << ""; - s << ""; - } - - void HTTPConnection::ShowLocalDestinations (std::stringstream& s) + void ShowLocalDestinations (std::stringstream& s) { s << "Local Destinations:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetDestinations ()) { auto ident = it.second->GetIdentHash ();; - s << ""; + s << ""; s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; } } - void HTTPConnection::ShowLocalDestination (const std::string& b32, std::stringstream& s) + void ShowLocalDestination (std::stringstream& s, const std::string& b32) { s << "Local Destination:
\r\n
\r\n"; i2p::data::IdentHash ident; @@ -555,28 +423,19 @@ namespace util auto pool = dest->GetTunnelPool (); if (pool) { - s << "Tunnels:
\r\n"; - for (auto it: pool->GetOutboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << "
\r\n" << std::endl; + s << "Inbound tunnels:
\r\n"; + for (auto & it : pool->GetInboundTunnels ()) { + it->Print(s); + ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ()); } - for (auto it: pool->GetInboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << "
\r\n" << std::endl; + s << "
\r\n"; + s << "Outbound tunnels:
\r\n"; + for (auto & it : pool->GetOutboundTunnels ()) { + it->Print(s); + ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ()); } } + s << "
\r\n"; s << "Tags
Incoming: " << dest->GetNumIncomingTags () << "
Outgoing:
" << std::endl; for (auto it: dest->GetSessions ()) { @@ -609,54 +468,62 @@ namespace util s << "Status"; s << ""; - for (auto it: dest->GetStreamingDestination ()->GetStreams ()) + for (auto it: dest->GetAllStreams ()) { s << ""; - s << "" << it.first << ""; - s << "" << i2p::client::context.GetAddressBook ().ToAddress(it.second->GetRemoteIdentity ()) << ""; - s << "" << it.second->GetNumSentBytes () << ""; - s << "" << it.second->GetNumReceivedBytes () << ""; - s << "" << it.second->GetSendQueueSize () << ""; - s << "" << it.second->GetReceiveQueueSize () << ""; - s << "" << it.second->GetSendBufferSize () << ""; - s << "" << it.second->GetRTT () << ""; - s << "" << it.second->GetWindowSize () << ""; - s << "" << (int)it.second->GetStatus () << ""; + s << "" << it->GetSendStreamID () << ""; + s << "" << i2p::client::context.GetAddressBook ().ToAddress(it->GetRemoteIdentity ()) << ""; + s << "" << it->GetNumSentBytes () << ""; + s << "" << it->GetNumReceivedBytes () << ""; + s << "" << it->GetSendQueueSize () << ""; + s << "" << it->GetReceiveQueueSize () << ""; + s << "" << it->GetSendBufferSize () << ""; + s << "" << it->GetRTT () << ""; + s << "" << it->GetWindowSize () << ""; + s << "" << (int)it->GetStatus () << ""; s << "
\r\n" << std::endl; } } } - void HTTPConnection::ShowTunnels (std::stringstream& s) + void ShowTunnels (std::stringstream& s) { - s << "Tunnels:
\r\n
\r\n"; s << "Queue size: " << i2p::tunnel::tunnels.GetQueueSize () << "
\r\n"; - for (auto it: i2p::tunnel::tunnels.GetOutboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << " " << (int)it->GetNumSentBytes () << "
\r\n"; - s << std::endl; - } - for (auto it: i2p::tunnel::tunnels.GetInboundTunnels ()) - { - it->Print (s); - auto state = it->GetState (); - if (state == i2p::tunnel::eTunnelStateFailed) - s << " " << "Failed"; - else if (state == i2p::tunnel::eTunnelStateExpiring) - s << " " << "Exp"; - s << " " << (int)it->GetNumReceivedBytes () << "
\r\n"; - s << std::endl; + s << "Inbound tunnels:
\r\n"; + for (auto & it : i2p::tunnel::tunnels.GetInboundTunnels ()) { + it->Print(s); + ShowTunnelDetails(s, it->GetState (), it->GetNumReceivedBytes ()); } + s << "
\r\n"; + s << "Outbound tunnels:
\r\n"; + for (auto & it : i2p::tunnel::tunnels.GetOutboundTunnels ()) { + it->Print(s); + ShowTunnelDetails(s, it->GetState (), it->GetNumSentBytes ()); + } + s << "
\r\n"; } - void HTTPConnection::ShowTransitTunnels (std::stringstream& s) + void ShowCommands (std::stringstream& s) + { + /* commands */ + s << "Router Commands
\r\n"; + s << " Run peer test
\r\n"; + if (i2p::context.AcceptsTunnels ()) + s << " Stop accepting tunnels
\r\n"; + else + s << " Start accepting tunnels
\r\n"; + if (Daemon.gracefullShutdownInterval) { + s << " Cancel gracefull shutdown ("; + s << Daemon.gracefullShutdownInterval; + s << " seconds remains)
\r\n"; + } else { + s << " Start gracefull shutdown
\r\n"; + } + s << " Force shutdown
\r\n"; + } + + void ShowTransitTunnels (std::stringstream& s) { s << "Transit tunnels:
\r\n
\r\n"; for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) @@ -671,7 +538,7 @@ namespace util } } - void HTTPConnection::ShowTransports (std::stringstream& s) + void ShowTransports (std::stringstream& s) { s << "Transports:
\r\n
\r\n"; auto ntcpServer = i2p::transport::transports.GetNTCPServer (); @@ -720,204 +587,270 @@ namespace util } } - void HTTPConnection::ShowSAMSessions (std::stringstream& s) + void ShowSAMSessions (std::stringstream& s) { - s << "SAM Sessions:
\r\n
\r\n"; auto sam = i2p::client::context.GetSAMBridge (); - if (sam) - { - for (auto& it: sam->GetSessions ()) - { - s << ""; - s << it.first << "
\r\n" << std::endl; - } + if (!sam) { + ShowError(s, "SAM disabled"); + return; + } + s << "SAM Sessions:
\r\n
\r\n"; + for (auto& it: sam->GetSessions ()) + { + s << ""; + s << it.first << "
\r\n" << std::endl; } } - void HTTPConnection::ShowSAMSession (const std::string& id, std::stringstream& s) + void ShowSAMSession (std::stringstream& s, const std::string& id) { s << "SAM Session:
\r\n
\r\n"; auto sam = i2p::client::context.GetSAMBridge (); - if (sam) + if (!sam) { + ShowError(s, "SAM disabled"); + return; + } + auto session = sam->FindSession (id); + if (!session) { + ShowError(s, "SAM session not found"); + return; + } + auto& ident = session->localDestination->GetIdentHash(); + s << ""; + s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n"; + s << "
\r\n"; + s << "Streams:
\r\n"; + for (auto it: session->ListSockets()) { - auto session = sam->FindSession (id); - if (session) + switch (it->GetSocketType ()) { - auto& ident = session->localDestination->GetIdentHash(); - s << ""; - s << i2p::client::context.GetAddressBook ().ToAddress(ident) << "
\r\n" << std::endl; - s << "Streams:
\r\n"; - for (auto it: session->ListSockets()) - { - switch (it->GetSocketType ()) - { - case i2p::client::eSAMSocketTypeSession: - s << "session"; - break; - case i2p::client::eSAMSocketTypeStream: - s << "stream"; - break; - case i2p::client::eSAMSocketTypeAcceptor: - s << "acceptor"; - break; - default: - s << "unknown"; - } - s << " [" << it->GetSocket ().remote_endpoint() << "]"; - s << "
\r\n" << std::endl; - } + case i2p::client::eSAMSocketTypeSession : s << "session"; break; + case i2p::client::eSAMSocketTypeStream : s << "stream"; break; + case i2p::client::eSAMSocketTypeAcceptor : s << "acceptor"; break; + default: s << "unknown"; break; } + s << " [" << it->GetSocket ().remote_endpoint() << "]"; + s << "
\r\n"; } } - void HTTPConnection::ShowI2PTunnels (std::stringstream& s) + void ShowI2PTunnels (std::stringstream& s) { s << "Client Tunnels:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetClientTunnels ()) { - s << it.second->GetName () << " ⇐ "; auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << ""; + s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); - s << "
\r\n"<< std::endl; + s << "
\r\n"<< std::endl; } s << "
\r\nServer Tunnels:
\r\n
\r\n"; for (auto& it: i2p::client::context.GetServerTunnels ()) { - s << it.second->GetName () << " ⇒ "; auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << ""; + s << it.second->GetName () << " ⇒ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << ":" << it.second->GetLocalPort (); s << "
\r\n"<< std::endl; } } - - void HTTPConnection::StopAcceptingTunnels (std::stringstream& s) + + HTTPConnection::HTTPConnection (std::shared_ptr socket): + m_Socket (socket), m_Timer (socket->get_io_service ()), m_BufferLen (0) { - s << "Stop Accepting Tunnels:
\r\n
\r\n"; - i2p::context.SetAcceptsTunnels (false); - s << "Accepting tunnels stopped" << std::endl; - } + /* cache options */ + i2p::config::GetOption("http.auth", needAuth); + i2p::config::GetOption("http.user", user); + i2p::config::GetOption("http.pass", pass); + }; - void HTTPConnection::StartAcceptingTunnels (std::stringstream& s) + void HTTPConnection::Receive () { - s << "Start Accepting Tunnels:
\r\n
\r\n"; - i2p::context.SetAcceptsTunnels (true); - s << "Accepting tunnels started" << std::endl; + 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::RunPeerTest (std::stringstream& s) + void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - s << "Run Peer Test:
\r\n
\r\n"; - i2p::transport::transports.PeerTest (); - s << "Peer test is running" << std::endl; + 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::HandleDestinationRequest (const std::string& address, const std::string& uri) + void HTTPConnection::RunRequest () { - std::string request = "GET " + uri + " HTTP/1.1\r\nHost:" + address + "\r\n\r\n"; - LogPrint(eLogInfo, "HTTPServer: client request: ", request); - SendToAddress (address, 80, request.c_str (), request.size ()); + 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::SendToAddress (const std::string& address, int port, const char * buf, size_t len) - { - i2p::data::IdentHash destination; - if (!i2p::client::context.GetAddressBook ().GetIdentHash (address, destination)) - { - LogPrint (eLogWarning, "HTTPServer: Unknown address ", address); - SendReply ("" + itoopieImage + "
\r\nUnknown address " + address + "", 404); + void HTTPConnection::Terminate (const boost::system::error_code& ecode) + { + if (ecode == boost::asio::error::operation_aborted) return; - } - - auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (destination); - if (leaseSet && !leaseSet->IsExpired ()) - SendToDestination (leaseSet, port, buf, len); - else - { - memcpy (m_Buffer, buf, len); - m_BufferLen = len; - i2p::client::context.GetSharedLocalDestination ()->RequestDestination (destination); - m_Timer.expires_from_now (boost::posix_time::seconds(HTTP_DESTINATION_REQUEST_TIMEOUT)); - m_Timer.async_wait (std::bind (&HTTPConnection::HandleDestinationRequestTimeout, - shared_from_this (), std::placeholders::_1, destination, port, m_Buffer, m_BufferLen)); - } + boost::system::error_code ignored_ec; + m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); + m_Socket->close (); } - - void HTTPConnection::HandleDestinationRequestTimeout (const boost::system::error_code& ecode, - i2p::data::IdentHash destination, int port, const char * buf, size_t len) - { - if (ecode != boost::asio::error::operation_aborted) - { - auto leaseSet = i2p::client::context.GetSharedLocalDestination ()->FindLeaseSet (destination); - if (leaseSet && !leaseSet->IsExpired ()) - SendToDestination (leaseSet, port, buf, len); - else - // still no LeaseSet - SendReply (leaseSet ? "" + itoopieImage + "
\r\nLeases expired" : "" + itoopieImage + "LeaseSet not found", 504); + + 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; } - } - - void HTTPConnection::SendToDestination (std::shared_ptr remote, int port, const char * buf, size_t len) - { - if (!m_Stream) - m_Stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (remote, port); - if (m_Stream) - { - m_Stream->Send ((uint8_t *)buf, len); - AsyncStreamReceive (); + /* method #2: 'Authorization' header sent */ + if (req.headers.count("Authorization") > 0) { + std::string provided = req.headers.find("Authorization")->second; + std::string expected = user + ":" + pass; + char b64_creds[64]; + std::size_t len = 0; + len = i2p::data::ByteStreamToBase64((unsigned char *)expected.c_str(), expected.length(), b64_creds, sizeof(b64_creds)); + b64_creds[len] = '\0'; + expected = "Basic "; + expected += b64_creds; + if (provided == expected) + return true; } + + LogPrint(eLogWarning, "HTTPServer: auth failure from ", m_Socket->remote_endpoint().address ()); + return false; } - void HTTPConnection::AsyncStreamReceive () + void HTTPConnection::HandleRequest (const HTTPReq & req) { - if (m_Stream) - m_Stream->AsyncReceive (boost::asio::buffer (m_StreamBuffer, 8192), - std::bind (&HTTPConnection::HandleStreamReceive, shared_from_this (), - std::placeholders::_1, std::placeholders::_2), - 45); // 45 seconds timeout + std::stringstream s; + std::string content; + HTTPRes res; + + LogPrint(eLogDebug, "HTTPServer: request: ", req.uri); + + if (needAuth && !CheckAuth(req)) { + res.code = 401; + res.headers.insert(std::pair("WWW-Authenticate", "Basic realm=\"WebAdmin\"")); + SendReply(res, content); + return; + } + + // Html5 head start + ShowPageHead (s); + if (req.uri.find("page=") != std::string::npos) + HandlePage (req, res, s); + else if (req.uri.find("cmd=") != std::string::npos) + HandleCommand (req, res, s); + else + ShowStatus (s); + ShowPageTail (s); + content = s.str (); + SendReply (res, content); } - void HTTPConnection::HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) - { - if (!ecode) - { - boost::asio::async_write (*m_Socket, boost::asio::buffer (m_StreamBuffer, bytes_transferred), - std::bind (&HTTPConnection::HandleWrite, shared_from_this (), std::placeholders::_1)); + void HTTPConnection::HandlePage (const HTTPReq& req, HTTPRes& res, std::stringstream& s) + { + std::map params; + std::string page(""); + URL url; + + url.parse(req.uri); + url.parse_query(params); + page = params["page"]; + + if (page == HTTP_PAGE_TRANSPORTS) + ShowTransports (s); + else if (page == HTTP_PAGE_TUNNELS) + ShowTunnels (s); + else if (page == HTTP_PAGE_COMMANDS) + ShowCommands (s); + else if (page == HTTP_PAGE_JUMPSERVICES) + ShowJumpServices (s, params["address"]); + 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) + ShowLocalDestination (s, params["b32"]); + else if (page == HTTP_PAGE_SAM_SESSIONS) + ShowSAMSessions (s); + else if (page == HTTP_PAGE_SAM_SESSION) + ShowSAMSession (s, params["sam_id"]); + else if (page == HTTP_PAGE_I2P_TUNNELS) + ShowI2PTunnels (s); + else { + res.code = 400; + ShowError(s, "Unknown page: " + page); + return; } - else - { - if (ecode == boost::asio::error::timed_out) - SendReply ("" + itoopieImage + "
\r\nNot responding", 504); - else if (ecode != boost::asio::error::operation_aborted) - Terminate (); + } + + void HTTPConnection::HandleCommand (const HTTPReq& req, HTTPRes& res, std::stringstream& s) + { + std::map params; + std::string cmd(""); + URL url; + + url.parse(req.uri); + url.parse_query(params); + cmd = params["cmd"]; + + if (cmd == HTTP_COMMAND_RUN_PEER_TEST) + i2p::transport::transports.PeerTest (); + else if (cmd == HTTP_COMMAND_START_ACCEPTING_TUNNELS) + i2p::context.SetAcceptsTunnels (true); + else if (cmd == HTTP_COMMAND_STOP_ACCEPTING_TUNNELS) + i2p::context.SetAcceptsTunnels (false); + else if (cmd == HTTP_COMMAND_SHUTDOWN_START) { + i2p::context.SetAcceptsTunnels (false); + Daemon.gracefullShutdownInterval = 10*60; + } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { + i2p::context.SetAcceptsTunnels (true); + Daemon.gracefullShutdownInterval = 0; + } else if (cmd == HTTP_COMMAND_SHUTDOWN_NOW) { + Daemon.running = false; + } else { + res.code = 400; + ShowError(s, "Unknown command: " + cmd); + return; } - } + s << "SUCCESS: Command accepted

\r\n"; + s << "Back to commands list"; + } - void HTTPConnection::SendReply (const std::string& content, int status) + void HTTPConnection::SendReply (HTTPRes& reply, std::string& content) { - m_Reply.content = content; - m_Reply.headers.resize(3); - // we need the date header to be complaint with http 1.1 - std::time_t time_now = std::time(nullptr); - char time_buff[128]; - if (std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now))) - { - m_Reply.headers[0].name = "Date"; - m_Reply.headers[0].value = std::string(time_buff); - m_Reply.headers[1].name = "Content-Length"; - m_Reply.headers[1].value = boost::lexical_cast(m_Reply.content.size()); - m_Reply.headers[2].name = "Content-Type"; - m_Reply.headers[2].value = "text/html"; - } - - boost::asio::async_write (*m_Socket, m_Reply.to_buffers(status), - std::bind (&HTTPConnection::HandleWriteReply, shared_from_this (), std::placeholders::_1)); + std::time_t time_now = std::time(nullptr); + char time_buff[128]; + std::strftime(time_buff, sizeof(time_buff), "%a, %d %b %Y %H:%M:%S GMT", std::gmtime(&time_now)); + reply.status = HTTPCodeToStatus(reply.code); + reply.headers.insert(std::pair("Date", time_buff)); + reply.headers.insert(std::pair("Content-Type", "text/html")); + reply.headers.insert(std::pair("Content-Length", std::to_string(content.size()))); + + std::string res = reply.to_string(); + std::vector buffers; + + buffers.push_back(boost::asio::buffer(res)); + buffers.push_back(boost::asio::buffer(content)); + + boost::asio::async_write (*m_Socket, buffers, + std::bind (&HTTPConnection::Terminate, shared_from_this (), std::placeholders::_1)); } HTTPServer::HTTPServer (const std::string& address, int port): @@ -933,6 +866,21 @@ namespace util 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 == "") { + char alnum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + pass.resize(16); + for (size_t i = 0; i < pass.size(); i++) { + pass[i] = alnum[rand() % (sizeof(alnum) - 1)]; + } + i2p::config::SetOption("http.pass", pass); + LogPrint(eLogInfo, "HTTPServer: password set to ", pass); + } m_Thread = std::unique_ptr(new std::thread (std::bind (&HTTPServer::Run, this))); m_Acceptor.listen (); Accept (); @@ -942,11 +890,10 @@ namespace util { m_Acceptor.close(); m_Service.stop (); - if (m_Thread) - { - m_Thread->join (); - m_Thread = nullptr; - } + if (m_Thread) { + m_Thread->join (); + m_Thread = nullptr; + } } void HTTPServer::Run () @@ -964,11 +911,10 @@ namespace util void HTTPServer::HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket) { - if (!ecode) - { - CreateConnection(newSocket); - Accept (); - } + if (ecode) + return; + CreateConnection(newSocket); + Accept (); } void HTTPServer::CreateConnection(std::shared_ptr newSocket) @@ -976,8 +922,5 @@ namespace util auto conn = std::make_shared (newSocket); conn->Receive (); } -} -} - - - +} // http +} // i2p diff --git a/HTTPServer.h b/HTTPServer.h index f70e27dc..2635c3be 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -1,107 +1,40 @@ #ifndef HTTP_SERVER_H__ #define HTTP_SERVER_H__ -#include -#include -#include -#include -#include -#include "LeaseSet.h" -#include "Streaming.h" - -namespace i2p -{ -namespace util -{ +namespace i2p { +namespace http { + extern const char *itoopieImage; + extern const char *itoopieFavicon; const size_t HTTP_CONNECTION_BUFFER_SIZE = 8192; - const int HTTP_DESTINATION_REQUEST_TIMEOUT = 10; // in seconds + class HTTPConnection: public std::enable_shared_from_this { - protected: - - struct header - { - std::string name; - std::string value; - }; - - struct request - { - std::string method; - std::string uri; - std::string host; - int port; - int http_version_major; - int http_version_minor; - std::vector
headers; - }; - - struct reply - { - std::vector
headers; - std::string status_string, content; - std::vector to_buffers (int status); - }; - public: - HTTPConnection (std::shared_ptr socket): - m_Socket (socket), m_Timer (socket->get_io_service ()), - m_Stream (nullptr), m_BufferLen (0) {}; + HTTPConnection (std::shared_ptr socket); void Receive (); private: - void Terminate (); void HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void AsyncStreamReceive (); - void HandleStreamReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred); - void HandleWriteReply(const boost::system::error_code& ecode); - void HandleWrite (const boost::system::error_code& ecode); - void SendReply (const std::string& content, int status = 200); + void Terminate (const boost::system::error_code& ecode); - void HandleRequest (const std::string& address); - void HandleCommand (const std::string& command, std::stringstream& s); - void ShowJumpServices (const std::string& address, std::stringstream& s); - void ShowTransports (std::stringstream& s); - void ShowTunnels (std::stringstream& s); - void ShowTransitTunnels (std::stringstream& s); - void ShowLocalDestinations (std::stringstream& s); - void ShowLocalDestination (const std::string& b32, std::stringstream& s); - void ShowSAMSessions (std::stringstream& s); - void ShowSAMSession (const std::string& id, std::stringstream& s); - void ShowI2PTunnels (std::stringstream& s); - void StartAcceptingTunnels (std::stringstream& s); - void StopAcceptingTunnels (std::stringstream& s); - void RunPeerTest (std::stringstream& s); - void FillContent (std::stringstream& s); - std::string ExtractAddress (); - void ExtractParams (const std::string& str, std::map& params); - - - protected: + void RunRequest (); + bool CheckAuth (const HTTPReq & req); + void HandleRequest (const HTTPReq & req); + 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); + + private: std::shared_ptr m_Socket; boost::asio::deadline_timer m_Timer; - std::shared_ptr m_Stream; - char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1], m_StreamBuffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; + char m_Buffer[HTTP_CONNECTION_BUFFER_SIZE + 1]; size_t m_BufferLen; - request m_Request; - reply m_Reply; - - protected: - - virtual void RunRequest (); - void HandleDestinationRequest(const std::string& address, const std::string& uri); - void SendToAddress (const std::string& address, int port, const char * buf, size_t len); - void HandleDestinationRequestTimeout (const boost::system::error_code& ecode, - i2p::data::IdentHash destination, int port, const char * buf, size_t len); - void SendToDestination (std::shared_ptr remote, int port, const char * buf, size_t len); - - public: - - static const std::string itoopieImage; - static const std::string itoopieFavicon; + bool needAuth; + std::string user; + std::string pass; }; class HTTPServer @@ -109,7 +42,7 @@ namespace util public: HTTPServer (const std::string& address, int port); - virtual ~HTTPServer (); + ~HTTPServer (); void Start (); void Stop (); @@ -120,6 +53,7 @@ namespace util void Accept (); void HandleAccept(const boost::system::error_code& ecode, std::shared_ptr newSocket); + void CreateConnection(std::shared_ptr newSocket); private: @@ -127,13 +61,8 @@ namespace util boost::asio::io_service m_Service; boost::asio::io_service::work m_Work; boost::asio::ip::tcp::acceptor m_Acceptor; - - protected: - virtual void CreateConnection(std::shared_ptr newSocket); }; -} -} - -#endif - +} // http +} // i2p +#endif /* HTTP_SERVER_H__ */ diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 7cebf96a..9674fdca 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -286,6 +286,16 @@ namespace i2p return !msg->GetPayload ()[DATABASE_STORE_TYPE_OFFSET]; // 0- RouterInfo } + static uint16_t g_MaxNumTransitTunnels = DEFAULT_MAX_NUM_TRANSIT_TUNNELS; // TODO: + void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels) + { + if (maxNumTransitTunnels > 0 && maxNumTransitTunnels <= 10000 && g_MaxNumTransitTunnels != maxNumTransitTunnels) + { + LogPrint (eLogDebug, "I2NP: Max number of transit tunnels set to ", maxNumTransitTunnels); + g_MaxNumTransitTunnels = maxNumTransitTunnels; + } + } + bool HandleBuildRequestRecords (int num, uint8_t * records, uint8_t * clearText) { for (int i = 0; i < num; i++) @@ -298,7 +308,7 @@ namespace i2p i2p::crypto::ElGamalDecrypt (i2p::context.GetEncryptionPrivateKey (), record + BUILD_REQUEST_RECORD_ENCRYPTED_OFFSET, clearText); // replace record to reply if (i2p::context.AcceptsTunnels () && - i2p::tunnel::tunnels.GetTransitTunnels ().size () <= MAX_NUM_TRANSIT_TUNNELS && + i2p::tunnel::tunnels.GetTransitTunnels ().size () <= g_MaxNumTransitTunnels && !i2p::transport::transports.IsBandwidthExceeded ()) { auto transitTunnel = i2p::tunnel::CreateTransitTunnel ( diff --git a/I2NPProtocol.h b/I2NPProtocol.h index 6450e958..cf8f4266 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -97,8 +97,6 @@ namespace i2p const uint8_t DATABASE_LOOKUP_TYPE_ROUTERINFO_LOOKUP = 0x08; // 1000 const uint8_t DATABASE_LOOKUP_TYPE_EXPLORATORY_LOOKUP = 0x0C; // 1100 - const unsigned int MAX_NUM_TRANSIT_TUNNELS = 2500; - namespace tunnel { class InboundTunnel; @@ -259,6 +257,9 @@ namespace tunnel std::vector > m_TunnelMsgs, m_TunnelGatewayMsgs; }; + + const uint16_t DEFAULT_MAX_NUM_TRANSIT_TUNNELS = 2500; + void SetMaxNumTransitTunnels (uint16_t maxNumTransitTunnels); } #endif diff --git a/I2PControl.cpp b/I2PControl.cpp index 907fa2fd..1ef56c2d 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -83,6 +83,10 @@ namespace client m_RouterManagerHandlers["Reseed"] = &I2PControlService::ReseedHandler; m_RouterManagerHandlers["Shutdown"] = &I2PControlService::ShutdownHandler; m_RouterManagerHandlers["ShutdownGraceful"] = &I2PControlService::ShutdownGracefulHandler; + + // NetworkSetting + m_NetworkSettingHandlers["i2p.router.net.bw.in"] = &I2PControlService::InboundBandwidthLimit; + m_NetworkSettingHandlers["i2p.router.net.bw.out"] = &I2PControlService::OutboundBandwidthLimit; } I2PControlService::~I2PControlService () @@ -496,6 +500,22 @@ namespace client } } + void I2PControlService::InboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.in", bw); + } + + void I2PControlService::OutboundBandwidthLimit (const std::string& value, std::ostringstream& results) + { + if (value != "null") + i2p::context.SetBandwidth (std::atoi(value.c_str())); + int bw = i2p::context.GetBandwidthLimit(); + InsertParam (results, "i2p.router.net.bw.out", bw); + } + // certificate void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) { diff --git a/I2PControl.h b/I2PControl.h index 714d3aa5..728c9925 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -94,6 +94,8 @@ namespace client // NetworkSetting typedef void (I2PControlService::*NetworkSettingRequestHandler)(const std::string& value, std::ostringstream& results); + void InboundBandwidthLimit (const std::string& value, std::ostringstream& results); + void OutboundBandwidthLimit (const std::string& value, std::ostringstream& results); private: diff --git a/I2PService.h b/I2PService.h index 251a379a..2df11909 100644 --- a/I2PService.h +++ b/I2PService.h @@ -118,6 +118,9 @@ namespace client void Start (); //If you override this make sure you call it from the children void Stop (); + + const boost::asio::ip::tcp::acceptor& GetAcceptor () const { return m_Acceptor; }; + protected: virtual std::shared_ptr CreateHandler(std::shared_ptr socket) = 0; virtual const char* GetName() { return "Generic TCP/IP accepting daemon"; } diff --git a/Identity.cpp b/Identity.cpp index 0ca9567a..f221dd04 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -311,18 +311,18 @@ namespace data switch (keyType) { case SIGNING_KEY_TYPE_DSA_SHA1: - m_Verifier.reset (new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey)); + UpdateVerifier (new i2p::crypto::DSAVerifier (m_StandardIdentity.signingKey)); break; case SIGNING_KEY_TYPE_ECDSA_SHA256_P256: { size_t padding = 128 - i2p::crypto::ECDSAP256_KEY_LENGTH; // 64 = 128 - 64 - m_Verifier.reset (new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding)); + UpdateVerifier (new i2p::crypto::ECDSAP256Verifier (m_StandardIdentity.signingKey + padding)); break; } case SIGNING_KEY_TYPE_ECDSA_SHA384_P384: { size_t padding = 128 - i2p::crypto::ECDSAP384_KEY_LENGTH; // 32 = 128 - 96 - m_Verifier.reset (new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding)); + UpdateVerifier (new i2p::crypto::ECDSAP384Verifier (m_StandardIdentity.signingKey + padding)); break; } case SIGNING_KEY_TYPE_ECDSA_SHA512_P521: @@ -331,7 +331,7 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::ECDSAP521_KEY_LENGTH - 128; // 4 = 132- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier.reset (new i2p::crypto::ECDSAP521Verifier (signingKey)); + UpdateVerifier (new i2p::crypto::ECDSAP521Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_RSA_SHA256_2048: @@ -340,7 +340,7 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::RSASHA2562048_KEY_LENGTH - 128; // 128 = 256- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier.reset (new i2p::crypto:: RSASHA2562048Verifier (signingKey)); + UpdateVerifier (new i2p::crypto:: RSASHA2562048Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_RSA_SHA384_3072: @@ -349,7 +349,7 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::RSASHA3843072_KEY_LENGTH - 128; // 256 = 384- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier.reset (new i2p::crypto:: RSASHA3843072Verifier (signingKey)); + UpdateVerifier (new i2p::crypto:: RSASHA3843072Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_RSA_SHA512_4096: @@ -358,20 +358,28 @@ namespace data memcpy (signingKey, m_StandardIdentity.signingKey, 128); size_t excessLen = i2p::crypto::RSASHA5124096_KEY_LENGTH - 128; // 384 = 512- 128 memcpy (signingKey + 128, m_ExtendedBuffer + 4, excessLen); // right after signing and crypto key types - m_Verifier.reset (new i2p::crypto:: RSASHA5124096Verifier (signingKey)); + UpdateVerifier (new i2p::crypto:: RSASHA5124096Verifier (signingKey)); break; } case SIGNING_KEY_TYPE_EDDSA_SHA512_ED25519: { size_t padding = 128 - i2p::crypto::EDDSA25519_PUBLIC_KEY_LENGTH; // 96 = 128 - 32 - m_Verifier.reset (new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding)); + UpdateVerifier (new i2p::crypto::EDDSA25519Verifier (m_StandardIdentity.signingKey + padding)); break; } default: LogPrint (eLogError, "Identity: Signing key type ", (int)keyType, " is not supported"); } } - + + void IdentityEx::UpdateVerifier (i2p::crypto::Verifier * verifier) const + { + if (!m_Verifier || !verifier) + m_Verifier.reset (verifier); + else + delete verifier; + } + void IdentityEx::DropVerifier () const { // TODO: potential race condition with Verify diff --git a/Identity.h b/Identity.h index 27da190d..d8abd6f4 100644 --- a/Identity.h +++ b/Identity.h @@ -95,6 +95,7 @@ namespace data private: void CreateVerifier () const; + void UpdateVerifier (i2p::crypto::Verifier * verifier) const; private: diff --git a/Makefile b/Makefile index ae49ae4f..e3807d93 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,11 @@ USE_STATIC := no ifeq ($(UNAME),Darwin) DAEMON_SRC += DaemonLinux.cpp - include Makefile.osx + ifeq ($(HOMEBREW),1) + include Makefile.homebrew + else + include Makefile.osx + endif else ifeq ($(shell echo $(UNAME) | $(GREP) -c FreeBSD),1) DAEMON_SRC += DaemonLinux.cpp include Makefile.bsd diff --git a/Makefile.homebrew b/Makefile.homebrew new file mode 100644 index 00000000..163b7950 --- /dev/null +++ b/Makefile.homebrew @@ -0,0 +1,29 @@ +# root directory holding homebrew +BREWROOT = /usr/local/ +BOOSTROOT = ${BREWROOT}/opt/boost +SSLROOT = ${BREWROOT}/opt/libressl +CXX = clang++ +CXXFLAGS = -g -Wall -std=c++11 -DMAC_OSX +INCFLAGS = -I${SSLROOT}/include -I${BOOSTROOT}/include +LDFLAGS = -L${SSLROOT}/lib -L${BOOSTROOT}/lib +LDLIBS = -lz -lcrypto -lssl -lboost_system -lboost_date_time -lboost_filesystem -lboost_regex -lboost_program_options -lpthread + +ifeq ($(USE_UPNP),1) + LDFLAGS += -ldl + CXXFLAGS += -DUSE_UPNP +endif + +# OSX Notes +# http://www.hutsby.net/2011/08/macs-with-aes-ni.html +# Seems like all recent Mac's have AES-NI, after firmware upgrade 2.2 +# Found no good way to detect it from command line. TODO: Might be some osx sysinfo magic +# note from psi: 2009 macbook does not have aesni +#ifeq ($(USE_AESNI),yes) +# CXXFLAGS += -maes -DAESNI +#endif + +# Disabled, since it will be the default make rule. I think its better +# to define the default rule in Makefile and not Makefile. - torkel +#install: all +# test -d ${PREFIX} || mkdir -p ${PREFIX}/ +# cp -r i2p ${PREFIX}/ diff --git a/README.md b/README.md index 4f167754..b985abf4 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,11 @@ Donations BTC: 1K7Ds6KUeR8ya287UC4rYTjvC96vXyZbDY LTC: LKQirrYrDeTuAPnpYq5y7LVKtywfkkHi59 ANC: AQJYweYYUqM1nVfLqfoSMpUMfzxvS4Xd7z +DOGE: DNXLQKziRPAsD9H3DFNjk4fLQrdaSX893Y -Downloads ------------- - -Official binary releases could be found at: -http://i2pd.website/releases/ -older releases -http://download.i2p.io/purplei2p/i2pd/releases/ +Documentation: +-------------- +http://i2pd.readthedocs.org Supported OS ------------ diff --git a/RouterContext.cpp b/RouterContext.cpp index e50681d9..5fa32a13 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -149,6 +149,11 @@ namespace i2p UpdateRouterInfo (); } + std::string RouterContext::GetFamily () const + { + return m_RouterInfo.GetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY); + } + void RouterContext::SetFamily (const std::string& family) { std::string signature; @@ -356,16 +361,24 @@ namespace i2p delete[] buf; } - i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); // TODO m_RouterInfo.SetRouterIdentity (GetIdentity ()); - m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); - m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); - m_RouterInfo.SetProperty ("router.version", I2P_VERSION); + i2p::data::RouterInfo routerInfo(i2p::fs::DataDirPath (ROUTER_INFO)); + if (!routerInfo.IsUnreachable ()) // router.info looks good + { + m_RouterInfo.Update (routerInfo.GetBuffer (), routerInfo.GetBufferLen ()); + m_RouterInfo.SetProperty ("coreVersion", I2P_VERSION); + m_RouterInfo.SetProperty ("router.version", I2P_VERSION); + + // Migration to 0.9.24. TODO: remove later + m_RouterInfo.DeleteProperty ("coreVersion"); + m_RouterInfo.DeleteProperty ("stat_uptime"); + } + else + { + LogPrint (eLogError, ROUTER_INFO, " is malformed. Creating new"); + NewRouterInfo (); + } - // Migration to 0.9.24. TODO: remove later - m_RouterInfo.DeleteProperty ("coreVersion"); - m_RouterInfo.DeleteProperty ("stat_uptime"); - if (IsUnreachable ()) SetReachable (); // we assume reachable until we discover firewall through peer tests diff --git a/RouterContext.h b/RouterContext.h index 9766c66e..5a72ad58 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -59,6 +59,7 @@ namespace i2p bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); void SetFamily (const std::string& family); + std::string GetFamily () const; void SetBandwidth (int limit); /* in kilobytes */ void SetBandwidth (char L); /* by letter */ bool AcceptsTunnels () const { return m_AcceptsTunnels; }; diff --git a/RouterInfo.cpp b/RouterInfo.cpp index dfd54059..2e76127c 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -104,6 +104,8 @@ namespace data { if (LoadFile ()) ReadFromBuffer (false); + else + m_IsUnreachable = true; } void RouterInfo::ReadFromBuffer (bool verifySignature) @@ -514,19 +516,20 @@ namespace data m_BufferLen += privateKeys.GetPublic ()->GetSignatureLen (); } - void RouterInfo::SaveToFile (const std::string& fullPath) + bool RouterInfo::SaveToFile (const std::string& fullPath) { m_FullPath = fullPath; - if (m_Buffer) - { - std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); - if (f.is_open ()) - f.write ((char *)m_Buffer, m_BufferLen); - else - LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); - } - else + if (!m_Buffer) { LogPrint (eLogError, "RouterInfo: Can't save, m_Buffer == NULL"); + return false; + } + std::ofstream f (fullPath, std::ofstream::binary | std::ofstream::out); + if (!f.is_open ()) { + LogPrint(eLogError, "RouterInfo: Can't save to ", fullPath); + return false; + } + f.write ((char *)m_Buffer, m_BufferLen); + return true; } size_t RouterInfo::ReadString (char * str, std::istream& s) @@ -633,6 +636,14 @@ namespace data m_Properties.erase (key); } + std::string RouterInfo::GetProperty (const std::string& key) const + { + auto it = m_Properties.find (key); + if (it != m_Properties.end ()) + return it->second; + return ""; + } + bool RouterInfo::IsNTCP (bool v4only) const { if (v4only) diff --git a/RouterInfo.h b/RouterInfo.h index 7648c6ba..a55924a8 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -128,6 +128,7 @@ namespace data bool RemoveIntroducer (const boost::asio::ip::udp::endpoint& e); void SetProperty (const std::string& key, const std::string& value); // called from RouterContext only void DeleteProperty (const std::string& key); // called from RouterContext only + std::string GetProperty (const std::string& key) const; // called from RouterContext only void ClearProperties () { m_Properties.clear (); }; bool IsFloodfill () const { return m_Caps & Caps::eFloodfill; }; bool IsReachable () const { return m_Caps & Caps::eReachable; }; @@ -161,7 +162,7 @@ namespace data bool IsUpdated () const { return m_IsUpdated; }; void SetUpdated (bool updated) { m_IsUpdated = updated; }; - void SaveToFile (const std::string& fullPath); + bool SaveToFile (const std::string& fullPath); std::shared_ptr GetProfile () const; void SaveProfile () { if (m_Profile) m_Profile->Save (); }; diff --git a/SAM.cpp b/SAM.cpp index bcb4623a..55eae222 100644 --- a/SAM.cpp +++ b/SAM.cpp @@ -47,15 +47,14 @@ namespace client break; case eSAMSocketTypeStream: { - if (m_Session) { + if (m_Session) m_Session->DelSocket (shared_from_this ()); - } break; } case eSAMSocketTypeAcceptor: { if (m_Session) - { + { m_Session->DelSocket (shared_from_this ()); if (m_Session->localDestination) m_Session->localDestination->StopAcceptingStreams (); @@ -67,6 +66,7 @@ namespace client } m_SocketType = eSAMSocketTypeTerminated; if (m_Socket.is_open()) m_Socket.close (); + m_Session = nullptr; } void SAMSocket::ReceiveHandshake () @@ -720,7 +720,7 @@ namespace client m_IsRunning = false; m_Acceptor.cancel (); for (auto it: m_Sessions) - delete it.second; + it.second->CloseStreams (); m_Sessions.clear (); m_Service.stop (); if (m_Thread) @@ -774,7 +774,7 @@ namespace client Accept (); } - SAMSession * SAMBridge::CreateSession (const std::string& id, const std::string& destination, + std::shared_ptr SAMBridge::CreateSession (const std::string& id, const std::string& destination, const std::map * params) { std::shared_ptr localDestination = nullptr; @@ -799,8 +799,9 @@ namespace client } if (localDestination) { + auto session = std::make_shared(localDestination); std::unique_lock l(m_SessionsMutex); - auto ret = m_Sessions.insert (std::pair(id, new SAMSession (localDestination))); + auto ret = m_Sessions.insert (std::make_pair(id, session)); if (!ret.second) LogPrint (eLogWarning, "SAM: Session ", id, " already exists"); return ret.first->second; @@ -810,19 +811,24 @@ namespace client void SAMBridge::CloseSession (const std::string& id) { - std::unique_lock l(m_SessionsMutex); - auto it = m_Sessions.find (id); - if (it != m_Sessions.end ()) + std::shared_ptr session; { - auto session = it->second; + std::unique_lock l(m_SessionsMutex); + auto it = m_Sessions.find (id); + if (it != m_Sessions.end ()) + { + session = it->second; + m_Sessions.erase (it); + } + } + if (session) + { session->localDestination->StopAcceptingStreams (); session->CloseStreams (); - m_Sessions.erase (it); - delete session; } } - SAMSession * SAMBridge::FindSession (const std::string& id) const + std::shared_ptr SAMBridge::FindSession (const std::string& id) const { std::unique_lock l(m_SessionsMutex); auto it = m_Sessions.find (id); diff --git a/SAM.h b/SAM.h index 4cdea686..ef36f285 100644 --- a/SAM.h +++ b/SAM.h @@ -128,7 +128,7 @@ namespace client std::string m_ID; // nickname bool m_IsSilent; std::shared_ptr m_Stream; - SAMSession * m_Session; + std::shared_ptr m_Session; }; struct SAMSession @@ -176,10 +176,10 @@ namespace client void Stop (); boost::asio::io_service& GetService () { return m_Service; }; - SAMSession * CreateSession (const std::string& id, const std::string& destination, // empty string means transient + std::shared_ptr CreateSession (const std::string& id, const std::string& destination, // empty string means transient const std::map * params); void CloseSession (const std::string& id); - SAMSession * FindSession (const std::string& id) const; + std::shared_ptr FindSession (const std::string& id) const; private: @@ -200,7 +200,7 @@ namespace client boost::asio::ip::udp::endpoint m_DatagramEndpoint, m_SenderEndpoint; boost::asio::ip::udp::socket m_DatagramSocket; mutable std::mutex m_SessionsMutex; - std::map m_Sessions; + std::map > m_Sessions; uint8_t m_DatagramReceiveBuffer[i2p::datagram::MAX_DATAGRAM_SIZE+1]; public: diff --git a/SSUSession.cpp b/SSUSession.cpp index aa534c56..a1bbf4d9 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -265,8 +265,6 @@ namespace transport uint16_t ourPort = bufbe16toh (payload); s.Insert (payload, 2); // our port payload += 2; // port - LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); - i2p::context.UpdateAddress (ourIP); if (m_RemoteEndpoint.address ().is_v4 ()) s.Insert (m_RemoteEndpoint.address ().to_v4 ().to_bytes ().data (), 4); // remote IP v4 else @@ -283,11 +281,18 @@ namespace transport //TODO: since we are accessing a uint8_t this is unlikely to crash due to alignment but should be improved m_SessionKeyDecryption.SetIV (((SSUHeader *)buf)->iv); m_SessionKeyDecryption.Decrypt (payload, signatureLen, payload); // TODO: non-const payload - // verify - if (!s.Verify (m_RemoteIdentity, payload)) + // verify signature + if (s.Verify (m_RemoteIdentity, payload)) + { + LogPrint (eLogInfo, "SSU: Our external address is ", ourIP.to_string (), ":", ourPort); + i2p::context.UpdateAddress (ourIP); + SendSessionConfirmed (y, ourAddress, addressSize + 2); + } + else + { LogPrint (eLogError, "SSU: message 'created' signature verification failed"); - - SendSessionConfirmed (y, ourAddress, addressSize + 2); + Failed (); + } } void SSUSession::ProcessSessionConfirmed (const uint8_t * buf, size_t len) @@ -313,11 +318,17 @@ namespace transport paddingSize &= 0x0F; // %16 if (paddingSize > 0) paddingSize = 16 - paddingSize; payload += paddingSize; - // verify - if (m_SignedData && !m_SignedData->Verify (m_RemoteIdentity, payload)) + // verify signature + if (m_SignedData && m_SignedData->Verify (m_RemoteIdentity, payload)) + { + m_Data.Send (CreateDeliveryStatusMsg (0)); + Established (); + } + else + { LogPrint (eLogError, "SSU message 'confirmed' signature verification failed"); - m_Data.Send (CreateDeliveryStatusMsg (0)); - Established (); + Failed (); + } } void SSUSession::SendSessionRequest () diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index a3907ce5..842b624f 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -119,7 +119,7 @@ namespace tunnel if (ret.second) HandleOutOfSequenceFragment (msgID, ret.first->second); else - LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, "already exists"); + LogPrint (eLogError, "TunnelMessage: Incomplete message ", msgID, " already exists"); } else { diff --git a/Win32/Resource.rc b/Win32/Resource.rc index bdc532e9..c885c044 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -52,8 +52,8 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -//MAINICON ICON "ictoopie.ico" -MAINICON ICON "anke.ico" +MAINICON ICON "ictoopie.ico" +//MAINICON ICON "anke.ico" MASCOT BITMAP "Anke_700px.bmp" diff --git a/api.cpp b/api.cpp index 64648743..1828901b 100644 --- a/api.cpp +++ b/api.cpp @@ -28,7 +28,11 @@ namespace api i2p::fs::DetectDataDir(datadir, false); i2p::fs::Init(); - i2p::crypto::InitCrypto (); +#if defined(__x86_64__) + i2p::crypto::InitCrypto (false); +#else + i2p::crypto::InitCrypto (true); +#endif i2p::context.Init (); } diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 76963026..22029e85 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -74,6 +74,7 @@ set (CLIENT_SRC "${CMAKE_SOURCE_DIR}/I2PService.cpp" "${CMAKE_SOURCE_DIR}/SAM.cpp" "${CMAKE_SOURCE_DIR}/SOCKS.cpp" + "${CMAKE_SOURCE_DIR}/HTTP.cpp" "${CMAKE_SOURCE_DIR}/HTTPProxy.cpp" ) diff --git a/contrib/certificates/family/volatile.crt b/contrib/certificates/family/volatile.crt new file mode 100644 index 00000000..928c7f39 --- /dev/null +++ b/contrib/certificates/family/volatile.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBxDCCAWmgAwIBAgIJAJnJIdKHYwWcMAoGCCqGSM49BAMCMGcxCzAJBgNVBAYT +AkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn +aXRzIFB0eSBMdGQxIDAeBgNVBAMMF3ZvbGF0aWxlLmZhbWlseS5pMnAubmV0MB4X +DTE2MDQyNjE1MjAyNloXDTI2MDQyNDE1MjAyNlowZzELMAkGA1UEBhMCQVUxEzAR +BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 +IEx0ZDEgMB4GA1UEAwwXdm9sYXRpbGUuZmFtaWx5LmkycC5uZXQwWTATBgcqhkjO +PQIBBggqhkjOPQMBBwNCAARf6LBfbbfL6HInvC/4wAGaN3rj0eeLE/OdBpA93R3L +s8EUp0YTEJHWPo9APiKMmAwQSsMJfjhNrbp+UWEnnx2LMAoGCCqGSM49BAMCA0kA +MEYCIQDpQu2KPV5G1JOFLoZvdj+rcvEnjxM/FxkaqikwkVx8FAIhANP7DkUal+GT +SuiCtcqM4QyIBsfsCJBWEMzovft164Bo +-----END CERTIFICATE----- diff --git a/debian/copyright b/debian/copyright index 117fcffd..606d059b 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,9 +3,9 @@ Upstream-Name: i2pd Source: https://github.com/PurpleI2P Files: * -Copyright: 2013-2015 PurpleI2P +Copyright: 2013-2016 PurpleI2P License: BSD-3-clause - Copyright (c) 2013-2015, The PurpleI2P Project + Copyright (c) 2013-2016, The PurpleI2P Project . All rights reserved. . @@ -34,7 +34,7 @@ License: BSD-3-clause SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Files: debian/* -Copyright: 2014-2015 hagen +Copyright: 2014-2016 hagen 2013-2015 Kill Your TV License: GPL-2.0+ This package is free software; you can redistribute it and/or modify diff --git a/debian/i2pd.1 b/debian/i2pd.1 index 2a1e7f88..f61e243e 100644 --- a/debian/i2pd.1 +++ b/debian/i2pd.1 @@ -5,7 +5,7 @@ i2pd \- Load-balanced unspoofable packet switching network .SH SYNOPSIS .B i2pd -[\fIOPTION1\fR) [\fIOPTION2\fR]... +[\fIOPTION1\fR] [\fIOPTION2\fR]... .SH DESCRIPTION i2pd @@ -18,59 +18,58 @@ network is both distributed and dynamic, with no trusted parties. Any of the configuration options below can be used in the \fBDAEMON_ARGS\fR variable in \fI/etc/default/i2pd\fR. .BR .TP -\fB\-\-host=\fR -The external IP (deprecated) -.TP -\fB\-\-port=\fR -The external port to listen on +\fB\-\-help\fR +Show available options. .TP -\fB\-\-httpport=\fR -The HTTP port to listen on +\fB\-\-conf=\fR +Config file (default: \fI~/.i2pd/i2pd.conf\fR or \fI/var/lib/i2pd/i2pd.conf\fR) +.BR +This parameter will be silently ignored if the specified config file does not exist. +Options specified on the command line take precedence over those in the config file. .TP -\fB\-\-log=\fR[\fI1\fR|\fI0\fR] -.br -Enable of disable logging to a file. \fI1\fR for yes, \fI0\fR for no. (default: \fI0\fR, off) +\fB\-\-tunconf=\fR +Tunnels config file (default: \fI~/.i2pd/tunnels.conf\fR or \fI/var/lib/i2pd/tunnels.conf\fR) .TP -\fB\-\-daemon=\fR[\fI1\fR|\fI0\fR] -Enable or disable daemon mode. Daemon mode is enabled with \fI1\fR and disabled with \fI0\fR. (default: \fI0\fR, off) +\fB\-\-pidfile=\fR +Where to write pidfile (don\'t write by default) .TP -\fB\-\-service=\fR[\fI1\fR|\fI0\fR] -If enabled, system folders (\fB/var/run/i2pd.pid\fR, \fB/var/log/i2pd.log\fR, \fB/var/lib/i2pd\fR) will be used. If off, \fB$HOME/.i2pd\fR will be used instead. (default: \fI0\fR, off). +\fB\-\-log=\fR +Logs destination: \fIstdout\fR, \fIfile\fR, \fIsyslog\fR (\fIstdout\fR if not set, \fIfile\fR - otherwise, for compatibility) .TP -\fB\-\-unreachable=\fR[\fI1\fR|\fI0\fR] -\fI1\fR if router is declared as unreachable and works through introducers. (default: \fI0\fR, off) +\fB\-\-loglevel=\fR +Log messages above this level (\fIdebug\fR, \fBinfo\fR, \fIwarn\fR, \fIerror\fR) .TP -\fB\-\-v6=\fR[\fI1\fR|\fI0\fR] -\fI1\fR if \fBi2pd\fR should communicate via IPv6. (default: \fI0\fR, off) +\fB\-\-datadir=\fR +Path to storage of i2pd data (RI, keys, peer profiles, ...) .TP -\fB\-\-floodfill=\fR[\fI1\fR|\fI0\fR] -\fI1\fR if \fBi2pd\fR should become a floodfill. (default: \fI0\fR, off) +\fB\-\-host=\fR +The external IP address .TP -\fB\-\-bandwidth=\fR[\fI1\fR|\fI0\fR] -\fIL\fR if \fBi2pd\fR should be limited to 32KiB/s. Enabling floodfill will automatically set this to \fI0\fR (default: \fI0\fR, no limit) +\fB\-\-port=\fR +The port to listen on for incoming connections .TP -\fB\-\-httpproxyport=\fR -The local port for the HTTP Proxy to listen on (default: \fI4446\fR) +\fB\-\-daemon\fR +Router will go to background after start .TP -\fB\-\-socksproxyport=\fR -The local port for the SOCKS proxy to listen on (default: \fI4447\fR) +\fB\-\-service\fR +Router will use system folders like \fI/var/lib/i2pd\fR .TP -\fB\-\-proxykeys=\fR -An optional keys file for tunnel local destination (both HTTP and SOCKS) +\fB\-\-ipv6\fR +Enable communication through ipv6. false by default .TP -\fB\-\-samport=\fR -Port of SAM bridge. Usually \fI7656\fR. SAM will not be enabled if this is not set. (default: unset) +\fB\-\-notransit\fR +Router will not accept transit tunnels at startup .TP -\fB\-\-bobport=\fR -Port of BOB command channel. Usually \fI2827\fR. BOB will not be enabled if this is not set. (default: unset) +\fB\-\-floodfill\fR +Router will be floodfill .TP -\fB\-\-i2pcontrolport=\fR -Port of I2P control service. Usually \fI7650\fR. I2PControl will not be enabled if this is not set. (default: unset) +\fB\-\-bandwidth=\fR +Bandwidth limit: integer in KBps or letter aliases: \fIL (32KBps)\fR, O (256), P (2048), X (>9000) .TP -\fB\-\-conf=\fR -Config file (default: \fI~/.i2pd/i2pd.conf\fR or \fI/var/lib/i2pd/i2pd.conf\fR) -This parameter will be silently ignored if the specified config file does not exist. -Options specified on the command line take precedence over those in the config file. +\fB\-\-family=\fR +Name of a family, router belongs to. +.PP +See service-specific parameters in page \fIdocs/configuration.md\fR or in example config file \fIdocs/i2pd.conf\fR .SH FILES .PP @@ -82,10 +81,10 @@ i2pd configuration files (when running as a system service) .PP /var/lib/i2pd/ .RS 4 -i2pd profile directory (when running as a system service, see \fB\-\-service=\fR above) +i2pd profile directory (when running as a system service, see \fB\-\-service\fR above) .RE .PP -$HOME/.i2pd +$HOME/.i2pd/ .RS 4 i2pd profile directory (when running as a normal user) .RE @@ -95,7 +94,9 @@ i2pd profile directory (when running as a normal user) default I2P hosts file .SH AUTHOR This manual page was written by kytv for the Debian system (but may be used by others). -.BR +.PP +Updated by hagen in 2016. +.PP Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 2 or any later version published by the Free Software Foundation .BR -On Debian systems, the complete text of the GNU General Public License can be found in /usr/share/common-licenses/GPL +On Debian systems, the complete text of the GNU General Public License can be found in \fI/usr/share/common-licenses/GPL\fR diff --git a/debian/i2pd.conf b/debian/i2pd.conf deleted file mode 100644 index 4a518916..00000000 --- a/debian/i2pd.conf +++ /dev/null @@ -1,19 +0,0 @@ -ipv6 - -[httpproxy] -address = 127.0.0.1 -port = 4444 - -# other services (disabled by default) -# -#[sam] -#address = 127.0.0.1 -#port = 7656 -# -#[bob] -#address = 127.0.0.1 -#port = 2827 -# -#[i2pcontrol] -#address = 127.0.0.1 -#port = 7650 diff --git a/debian/i2pd.install b/debian/i2pd.install index f4e7e4f1..2e3c93cd 100644 --- a/debian/i2pd.install +++ b/debian/i2pd.install @@ -1,5 +1,5 @@ i2pd usr/sbin/ -debian/i2pd.conf etc/i2pd/ +docs/i2pd.conf etc/i2pd/ debian/tunnels.conf etc/i2pd/ debian/subscriptions.txt etc/i2pd/ contrib/certificates/ usr/share/i2pd/ diff --git a/docs/build_notes_unix.md b/docs/build_notes_unix.md index d02eb5df..05605343 100644 --- a/docs/build_notes_unix.md +++ b/docs/build_notes_unix.md @@ -93,6 +93,21 @@ If you need UPnP support (don't forget to run CMake with `WITH_UPNP=ON`) miniupn miniupnpc-devel ``` +MAC OS X +-------- + +Requires homebrew + +```bash +brew install libressl boost +``` + +Then build: +```bash +make HOMEBREW=1 +``` + + FreeBSD ------- diff --git a/docs/configuration.md b/docs/configuration.md index f3e9f98c..11e8b4a8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -16,8 +16,8 @@ If you are upgrading your very old router (< 2.3.0) see also [this](config_opts_ * --logfile= - Path to logfile (default - autodetect) * --loglevel= - Log messages above this level (debug, *info, warn, error) * --datadir= - Path to storage of i2pd data (RI, keys, peer profiles, ...) -* --host= - The external IP -* --port= - The port to listen on +* --host= - Router external IP for incoming connections +* --port= - Port to listen for incoming connections (default: auto) * --daemon - Router will go to background after start * --service - Router will use system folders like '/var/lib/i2pd' * --ipv6 - Enable communication through ipv6. false by default @@ -36,6 +36,9 @@ All options below still possible in cmdline, but better write it in config file: * --http.address= - The address to listen on (HTTP server) * --http.port= - The port to listen on (HTTP server) +* --http.auth - Enable basic HTTP auth for webconsole +* --http.user= - Username for basic auth (default: i2pd) +* --http.pass= - Password for basic auth (default: random, see logs) * --httpproxy.address= - The address to listen on (HTTP Proxy) * --httpproxy.port= - The port to listen on (HTTP Proxy) 4446 by default @@ -59,7 +62,11 @@ All options below still possible in cmdline, but better write it in config file: * --i2pcontrol.address= - The address to listen on (I2P control service) * --i2pcontrol.port= - Port of I2P control service. Usually 7650. I2PControl is off if not specified -* --i2pcontrol.enabled= - If I2P control is enabled. false by default +* --i2pcontrol.enabled= - If I2P control is enabled. false by default + +* --precomputation.elgamal= - Use ElGamal precomputated tables. false for x64 and true for other platforms by default + +* --limits.transittunnels= - Override maximum number of transit tunnels. 2500 by default Config files ------------ diff --git a/docs/i2pd.conf b/docs/i2pd.conf index e85eaa17..d4ea226a 100644 --- a/docs/i2pd.conf +++ b/docs/i2pd.conf @@ -69,8 +69,8 @@ port = 7070 ## Uncomment and set to 'false' to disable HTTP Proxy # enabled = true ## Address and port service will listen on -# address = 127.0.0.1 -# port = 4444 +address = 127.0.0.1 +port = 4444 ## Optional keys file for proxy local destination # keys = http-proxy-keys.dat diff --git a/filelist.mk b/filelist.mk index c35edcf0..e2a3da36 100644 --- a/filelist.mk +++ b/filelist.mk @@ -9,7 +9,7 @@ LIB_SRC = \ LIB_CLIENT_SRC = \ AddressBook.cpp BOB.cpp ClientContext.cpp I2PTunnel.cpp I2PService.cpp \ - SAM.cpp SOCKS.cpp HTTPProxy.cpp + SAM.cpp SOCKS.cpp HTTP.cpp HTTPProxy.cpp # also: Daemon{Linux,Win32}.cpp will be added later DAEMON_SRC = \ diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 00000000..199b7353 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,14 @@ +CXXFLAGS += -Wall -Wextra -pedantic -O0 -g -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 + +TESTS = test-http-url test-http-req test-http-res test-http-url_decode + +all: $(TESTS) run + +test-http-%: test-http-%.cpp ../HTTP.cpp + $(CXX) $(CXXFLAGS) $(NEEDED_CXXFLAGS) $(INCFLAGS) -o $@ $^ + +run: $(TESTS) + @for TEST in $(TESTS); do ./$$TEST ; done + +clean: + rm -f $(TESTS) diff --git a/tests/test-http-req.cpp b/tests/test-http-req.cpp new file mode 100644 index 00000000..484a7ad6 --- /dev/null +++ b/tests/test-http-req.cpp @@ -0,0 +1,82 @@ +#include +#include "../HTTP.h" + +using namespace i2p::http; + +int main(int argc, char *argv[]) { + HTTPReq *req; + int ret = 0, len = 0; + const char *buf; + + buf = + "GET / HTTP/1.0\r\n" + "User-Agent: curl/7.26.0\r\n" + "Host: inr.i2p\r\n" + "Accept: */*\r\n" + "\r\n" + "test"; + len = strlen(buf); + req = new HTTPReq; + assert((ret = req->parse(buf, len)) == len - 4); + assert(req->version == "HTTP/1.0"); + assert(req->method == "GET"); + assert(req->uri == "/"); + assert(req->host == "inr.i2p"); + assert(req->headers.size() == 3); + assert(req->headers.count("Host") == 1); + assert(req->headers.count("Accept") == 1); + assert(req->headers.count("User-Agent") == 1); + assert(req->headers.find("Host")->second == "inr.i2p"); + assert(req->headers.find("Accept")->second == "*/*"); + assert(req->headers.find("User-Agent")->second == "curl/7.26.0"); + delete req; + + buf = + "GET / HTTP/1.0\r\n" + "\r\n"; + len = strlen(buf); + req = new HTTPReq; + assert((ret = req->parse(buf, len)) == len); + assert(req->version == "HTTP/1.0"); + assert(req->method == "GET"); + assert(req->uri == "/"); + assert(req->host == ""); + assert(req->headers.size() == 0); + delete req; + + buf = + "GET / HTTP/1.1\r\n" + "\r\n"; + len = strlen(buf); + req = new HTTPReq; + assert((ret = req->parse(buf, len)) == -1); /* no host header */ + delete req; + + buf = + "GET / HTTP/1.0\r\n" + ""; + len = strlen(buf); + req = new HTTPReq; + assert((ret = req->parse(buf, len)) == 0); /* request not completed */ + delete req; + + buf = + "GET http://inr.i2p HTTP/1.1\r\n" + "Host: stats.i2p\r\n" + "Accept: */*\r\n" + "\r\n"; + len = strlen(buf); + req = new HTTPReq; + assert((ret = req->parse(buf, len)) == len); /* no host header */ + assert(req->method == "GET"); + assert(req->uri == "http://inr.i2p"); + assert(req->host == "stats.i2p"); + assert(req->headers.size() == 2); + assert(req->headers.count("Host") == 1); + assert(req->headers.count("Accept") == 1); + delete req; + + return 0; +} + +/* vim: expandtab:ts=2 */ diff --git a/tests/test-http-res.cpp b/tests/test-http-res.cpp new file mode 100644 index 00000000..6188a68d --- /dev/null +++ b/tests/test-http-res.cpp @@ -0,0 +1,37 @@ +#include +#include "../HTTP.h" + +using namespace i2p::http; + +int main(int argc, char *argv[]) { + HTTPRes *res; + int ret = 0, len = 0; + const char *buf; + + buf = + "HTTP/1.1 304 Not Modified\r\n" + "Date: Thu, 14 Apr 2016 00:00:00 GMT\r\n" + "Server: nginx/1.2.1\r\n" + "Content-Length: 536\r\n" + "\r\n"; + len = strlen(buf); + res = new HTTPRes; + assert((ret = res->parse(buf, len)) == len); + assert(res->version == "HTTP/1.1"); + assert(res->status == "Not Modified"); + assert(res->code == 304); + assert(res->headers.size() == 3); + assert(res->headers.count("Date") == 1); + assert(res->headers.count("Server") == 1); + assert(res->headers.count("Content-Length") == 1); + assert(res->headers.find("Date")->second == "Thu, 14 Apr 2016 00:00:00 GMT"); + assert(res->headers.find("Server")->second == "nginx/1.2.1"); + assert(res->headers.find("Content-Length")->second == "536"); + assert(res->is_chunked() == false); + assert(res->length() == 536); + delete res; + + return 0; +} + +/* vim: expandtab:ts=2 */ diff --git a/tests/test-http-url.cpp b/tests/test-http-url.cpp new file mode 100644 index 00000000..71b2f703 --- /dev/null +++ b/tests/test-http-url.cpp @@ -0,0 +1,110 @@ +#include +#include "../HTTP.h" + +using namespace i2p::http; + +int main(int argc, char *argv[]) { + std::map params; + URL *url; + + url = new URL; + assert(url->parse("https://127.0.0.1:7070/asdasd?12345") == true); + assert(url->schema == "https"); + assert(url->user == ""); + assert(url->pass == ""); + assert(url->host == "127.0.0.1"); + assert(url->port == 7070); + assert(url->path == "/asdasd"); + assert(url->query == "12345"); + assert(url->to_string() == "https://127.0.0.1:7070/asdasd?12345"); + delete url; + + url = new URL; + assert(url->parse("http://user:password@site.com:8080/asdasd?123456") == true); + assert(url->schema == "http"); + assert(url->user == "user"); + assert(url->pass == "password"); + assert(url->host == "site.com"); + assert(url->port == 8080); + assert(url->path == "/asdasd"); + assert(url->query == "123456"); + delete url; + + url = new URL; + assert(url->parse("http://user:password@site.com/asdasd?name=value") == true); + assert(url->schema == "http"); + assert(url->user == "user"); + assert(url->pass == "password"); + assert(url->host == "site.com"); + assert(url->port == 0); + assert(url->path == "/asdasd"); + assert(url->query == "name=value"); + delete url; + + url = new URL; + assert(url->parse("http://user:@site.com/asdasd?name=value1&name=value2") == true); + assert(url->schema == "http"); + assert(url->user == "user"); + assert(url->pass == ""); + assert(url->host == "site.com"); + assert(url->port == 0); + assert(url->path == "/asdasd"); + assert(url->query == "name=value1&name=value2"); + delete url; + + url = new URL; + assert(url->parse("http://user@site.com/asdasd?name1=value1&name2&name3=value2") == true); + assert(url->schema == "http"); + assert(url->user == "user"); + assert(url->pass == ""); + assert(url->host == "site.com"); + assert(url->port == 0); + assert(url->path == "/asdasd"); + assert(url->query == "name1=value1&name2&name3=value2"); + assert(url->parse_query(params)); + assert(params.size() == 3); + assert(params.count("name1") == 1); + assert(params.count("name2") == 1); + assert(params.count("name3") == 1); + assert(params.find("name1")->second == "value1"); + assert(params.find("name2")->second == ""); + assert(params.find("name3")->second == "value2"); + delete url; + + url = new URL; + assert(url->parse("http://@site.com:800/asdasd?") == true); + assert(url->schema == "http"); + assert(url->user == ""); + assert(url->pass == ""); + assert(url->host == "site.com"); + assert(url->port == 800); + assert(url->path == "/asdasd"); + assert(url->query == ""); + delete url; + + url = new URL; + assert(url->parse("http://@site.com:17") == true); + assert(url->schema == "http"); + assert(url->user == ""); + assert(url->pass == ""); + assert(url->host == "site.com"); + assert(url->port == 17); + assert(url->path == ""); + assert(url->query == ""); + delete url; + + url = new URL; + assert(url->parse("http://user:password@site.com:err_port/asdasd") == false); + assert(url->schema == "http"); + assert(url->user == "user"); + assert(url->pass == "password"); + assert(url->host == "site.com"); + assert(url->port == 0); + assert(url->path == ""); + assert(url->query == ""); + delete url; + + return 0; +} + +/* vim: expandtab:ts=2 */ diff --git a/tests/test-http-url_decode.cpp b/tests/test-http-url_decode.cpp new file mode 100644 index 00000000..1c548e6f --- /dev/null +++ b/tests/test-http-url_decode.cpp @@ -0,0 +1,19 @@ +#include +#include "../HTTP.h" + +using namespace i2p::http; + +int main(int argc, char *argv[]) { + std::string in("/%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0/"); + std::string out = UrlDecode(in); + + assert(strcmp(out.c_str(), "/страница/") == 0); + + in = "/%00/"; + out = UrlDecode(in, false); + assert(strcmp(out.c_str(), "/%00/") == 0); + out = UrlDecode(in, true); + assert(strcmp(out.c_str(), "/\0/") == 0); + + return 0; +} diff --git a/util.h b/util.h index 13200591..f5dbc9aa 100644 --- a/util.h +++ b/util.h @@ -5,11 +5,25 @@ #include #include #include +#include namespace i2p { namespace util { + + /** + wrapper arround boost::lexical_cast that "never" fails + */ + template + T lexical_cast(const std::string & str, const T fallback) { + try { + return boost::lexical_cast(str); + } catch ( ... ) { + return fallback; + } + } + namespace http { // in (lower case)