diff --git a/ClientContext.cpp b/ClientContext.cpp index 59f11f21..df0de4e5 100644 --- a/ClientContext.cpp +++ b/ClientContext.cpp @@ -16,7 +16,7 @@ namespace client ClientContext::ClientContext (): m_SharedLocalDestination (nullptr), m_HttpProxy (nullptr), m_SocksProxy (nullptr), m_SamBridge (nullptr), - m_BOBCommandChannel (nullptr) + m_BOBCommandChannel (nullptr), m_I2CPServer (nullptr) { } @@ -26,6 +26,7 @@ namespace client delete m_SocksProxy; delete m_SamBridge; delete m_BOBCommandChannel; + delete m_I2CPServer; } void ClientContext::Start () @@ -171,6 +172,11 @@ namespace client m_Destinations.clear (); m_SharedLocalDestination = nullptr; } + + void ClientContext::ReloadConfig () + { + ReadTunnels (); // TODO: it reads new tunnels only, should be implemented better + } void ClientContext::LoadPrivateKeys (i2p::data::PrivateKeys& keys, const std::string& filename, i2p::data::SigningKeyType sigType) { @@ -328,11 +334,14 @@ 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 (); + numClientTunnels++; + } else - LogPrint (eLogError, "Clients: I2P client tunnel with port ", port, " already exists"); - numClientTunnels++; + LogPrint (eLogError, "Clients: I2P client tunnel for endpoint ", clientTunnel->GetAcceptor ().local_endpoint (), " already exists"); } else if (type == I2P_TUNNELS_SECTION_TYPE_SERVER || type == I2P_TUNNELS_SECTION_TYPE_HTTP || type == I2P_TUNNELS_SECTION_TYPE_IRC) { @@ -382,12 +391,15 @@ 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 (); + numServerTunnels++; + } else - LogPrint (eLogError, "Clients: I2P server tunnel for destination ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), " already exists"); - numServerTunnels++; + LogPrint (eLogError, "Clients: I2P server tunnel for destination/port ", m_AddressBook.ToAddress(localDestination->GetIdentHash ()), "/", inPort, " already exists"); + } else LogPrint (eLogWarning, "Clients: Unknown section type=", type, " of ", name, " in ", tunConf); diff --git a/ClientContext.h b/ClientContext.h index a05c2161..cfbc039f 100644 --- a/ClientContext.h +++ b/ClientContext.h @@ -2,15 +2,16 @@ #define CLIENT_CONTEXT_H__ #include -#include #include #include +#include #include "Destination.h" #include "HTTPProxy.h" #include "SOCKS.h" #include "I2PTunnel.h" #include "SAM.h" #include "BOB.h" +#include "I2CP.h" #include "AddressBook.h" namespace i2p @@ -48,6 +49,8 @@ namespace client void Start (); void Stop (); + void ReloadConfig (); + std::shared_ptr GetSharedLocalDestination () const { return m_SharedLocalDestination; }; std::shared_ptr CreateNewLocalDestination (bool isPublic = false, i2p::data::SigningKeyType sigType = i2p::data::SIGNING_KEY_TYPE_DSA_SHA1, const std::map * params = nullptr); // transient @@ -78,10 +81,11 @@ 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; + I2CPServer * m_I2CPServer; public: // for HTTP diff --git a/Config.cpp b/Config.cpp index d7cef879..44dec286 100644 --- a/Config.cpp +++ b/Config.cpp @@ -141,6 +141,9 @@ namespace config { ("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"); diff --git a/Crypto.cpp b/Crypto.cpp index 742296f5..90b74179 100644 --- a/Crypto.cpp +++ b/Crypto.cpp @@ -3,10 +3,8 @@ #include #include #include -#include #include #include -#include #include #include "TunnelBase.h" #include diff --git a/Crypto.h b/Crypto.h index e333940e..7ce202ce 100644 --- a/Crypto.h +++ b/Crypto.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "Base.h" namespace i2p diff --git a/CryptoConst.cpp b/CryptoConst.cpp deleted file mode 100644 index a8868988..00000000 --- a/CryptoConst.cpp +++ /dev/null @@ -1,73 +0,0 @@ -#include -#include "CryptoConst.h" - -namespace i2p -{ -namespace crypto -{ - const uint8_t elgp_[256]= - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, - 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, - 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, - 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, - 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, - 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, - 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, - 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, - 0x98, 0xDA, 0x48, 0x36, 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, - 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, 0x20, 0x85, 0x52, 0xBB, - 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, - 0xF1, 0x74, 0x6C, 0x08, 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, - 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, 0xEC, 0x07, 0xA2, 0x8F, - 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, - 0x39, 0x95, 0x49, 0x7C, 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, - 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - }; - - const uint8_t dsap_[128]= - { - 0x9c, 0x05, 0xb2, 0xaa, 0x96, 0x0d, 0x9b, 0x97, 0xb8, 0x93, 0x19, 0x63, 0xc9, 0xcc, 0x9e, 0x8c, - 0x30, 0x26, 0xe9, 0xb8, 0xed, 0x92, 0xfa, 0xd0, 0xa6, 0x9c, 0xc8, 0x86, 0xd5, 0xbf, 0x80, 0x15, - 0xfc, 0xad, 0xae, 0x31, 0xa0, 0xad, 0x18, 0xfa, 0xb3, 0xf0, 0x1b, 0x00, 0xa3, 0x58, 0xde, 0x23, - 0x76, 0x55, 0xc4, 0x96, 0x4a, 0xfa, 0xa2, 0xb3, 0x37, 0xe9, 0x6a, 0xd3, 0x16, 0xb9, 0xfb, 0x1c, - 0xc5, 0x64, 0xb5, 0xae, 0xc5, 0xb6, 0x9a, 0x9f, 0xf6, 0xc3, 0xe4, 0x54, 0x87, 0x07, 0xfe, 0xf8, - 0x50, 0x3d, 0x91, 0xdd, 0x86, 0x02, 0xe8, 0x67, 0xe6, 0xd3, 0x5d, 0x22, 0x35, 0xc1, 0x86, 0x9c, - 0xe2, 0x47, 0x9c, 0x3b, 0x9d, 0x54, 0x01, 0xde, 0x04, 0xe0, 0x72, 0x7f, 0xb3, 0x3d, 0x65, 0x11, - 0x28, 0x5d, 0x4c, 0xf2, 0x95, 0x38, 0xd9, 0xe3, 0xb6, 0x05, 0x1f, 0x5b, 0x22, 0xcc, 0x1c, 0x93 - }; - - const uint8_t dsaq_[20]= - { - 0xa5, 0xdf, 0xc2, 0x8f, 0xef, 0x4c, 0xa1, 0xe2, 0x86, 0x74, 0x4c, 0xd8, 0xee, 0xd9, 0xd2, 0x9d, - 0x68, 0x40, 0x46, 0xb7 - }; - - const uint8_t dsag_[128]= - { - 0x0c, 0x1f, 0x4d, 0x27, 0xd4, 0x00, 0x93, 0xb4, 0x29, 0xe9, 0x62, 0xd7, 0x22, 0x38, 0x24, 0xe0, - 0xbb, 0xc4, 0x7e, 0x7c, 0x83, 0x2a, 0x39, 0x23, 0x6f, 0xc6, 0x83, 0xaf, 0x84, 0x88, 0x95, 0x81, - 0x07, 0x5f, 0xf9, 0x08, 0x2e, 0xd3, 0x23, 0x53, 0xd4, 0x37, 0x4d, 0x73, 0x01, 0xcd, 0xa1, 0xd2, - 0x3c, 0x43, 0x1f, 0x46, 0x98, 0x59, 0x9d, 0xda, 0x02, 0x45, 0x18, 0x24, 0xff, 0x36, 0x97, 0x52, - 0x59, 0x36, 0x47, 0xcc, 0x3d, 0xdc, 0x19, 0x7d, 0xe9, 0x85, 0xe4, 0x3d, 0x13, 0x6c, 0xdc, 0xfc, - 0x6b, 0xd5, 0x40, 0x9c, 0xd2, 0xf4, 0x50, 0x82, 0x11, 0x42, 0xa5, 0xe6, 0xf8, 0xeb, 0x1c, 0x3a, - 0xb5, 0xd0, 0x48, 0x4b, 0x81, 0x29, 0xfc, 0xf1, 0x7b, 0xce, 0x4f, 0x7f, 0x33, 0x32, 0x1c, 0x3c, - 0xb3, 0xdb, 0xb1, 0x4a, 0x90, 0x5e, 0x7b, 0x2b, 0x3e, 0x93, 0xbe, 0x47, 0x08, 0xcb, 0xcc, 0x82 - }; - - const CryptoConstants& GetCryptoConstants () - { - static CryptoConstants cryptoConstants = - { - {elgp_, 256}, // elgp - {2}, // elgg - {dsap_, 128}, // dsap - {dsaq_, 20}, // dsaq - {dsag_, 128} // dsag - }; - return cryptoConstants; - } - -} -} - diff --git a/CryptoConst.h b/CryptoConst.h deleted file mode 100644 index ba48a35d..00000000 --- a/CryptoConst.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef CRYPTO_CONST_H__ -#define CRYPTO_CONST_H__ - -#include - -namespace i2p -{ -namespace crypto -{ - struct CryptoConstants - { - // DH/ElGamal - const CryptoPP::Integer elgp; - const CryptoPP::Integer elgg; - - // DSA - const CryptoPP::Integer dsap; - const CryptoPP::Integer dsaq; - const CryptoPP::Integer dsag; - }; - - const CryptoConstants& GetCryptoConstants (); - - // DH/ElGamal - #define elgp GetCryptoConstants ().elgp - #define elgg GetCryptoConstants ().elgg - - // DSA - #define dsap GetCryptoConstants ().dsap - #define dsaq GetCryptoConstants ().dsaq - #define dsag GetCryptoConstants ().dsag - - // RSA - const int rsae = 65537; -} -} - -#endif diff --git a/Daemon.cpp b/Daemon.cpp index 81bbcdd5..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 @@ -202,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/Datagram.cpp b/Datagram.cpp index 9221824d..2015622c 100644 --- a/Datagram.cpp +++ b/Datagram.cpp @@ -1,7 +1,6 @@ #include #include -#include -#include +#include "Crypto.h" #include "Log.h" #include "TunnelBase.h" #include "RouterContext.h" diff --git a/Destination.cpp b/Destination.cpp index 5893caff..f487b9ad 100644 --- a/Destination.cpp +++ b/Destination.cpp @@ -1,14 +1,13 @@ #include #include #include -#include - +#include "Crypto.h" #include "Log.h" #include "FS.h" -#include "Crypto.h" #include "Timestamp.h" #include "NetDb.h" #include "Destination.h" +#include "util.h" namespace i2p { @@ -35,28 +34,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 +67,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 +77,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); diff --git a/Family.cpp b/Family.cpp index 34406faa..90c5ccd0 100644 --- a/Family.cpp +++ b/Family.cpp @@ -1,9 +1,9 @@ #include #include #include +#include "Crypto.h" #include "FS.h" #include "Log.h" -#include "Crypto.h" #include "Family.h" namespace i2p diff --git a/Garlic.cpp b/Garlic.cpp index e11f8ec8..c1100f64 100644 --- a/Garlic.cpp +++ b/Garlic.cpp @@ -2,8 +2,7 @@ #include "I2PEndian.h" #include #include -#include -#include +#include "Crypto.h" #include "RouterContext.h" #include "I2NPProtocol.h" #include "Tunnel.h" diff --git a/HTTP.cpp b/HTTP.cpp new file mode 100644 index 00000000..ef43d55f --- /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" +#include + +namespace i2p { +namespace http { + const std::vector HTTP_METHODS = { + "GET", "HEAD", "POST", "PUT", "PATCH", + "DELETE", "OPTIONS", "CONNECT" + }; + const std::vector HTTP_VERSIONS = { + "HTTP/1.0", "HTTP/1.1" + }; + + inline bool is_http_version(const std::string & str) { + return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); + } + + inline bool is_http_method(const std::string & str) { + return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); + } + + 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 (!is_http_method(tokens[0])) + return -1; + if (!is_http_version(tokens[2])) + 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 (!is_http_version(tokens[0])) + 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..9bd31c75 --- /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 std::vector HTTP_METHODS; /**< list of valid HTTP methods */ + extern const std::vector 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/HTTPProxy.cpp b/HTTPProxy.cpp index c252614b..6104e15e 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -104,7 +104,7 @@ namespace proxy std::string httpAddr; i2p::config::GetOption("http.address", httpAddr); uint16_t httpPort; i2p::config::GetOption("http.port", httpPort); - response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?jumpservices=&address=" << m_address << "\r\n\r\n"; + response << "HTTP/1.1 302 Found\r\nLocation: http://" << httpAddr << ":" << httpPort << "/?page=jumpservices&address=" << m_address << "\r\n\r\n"; boost::asio::async_write(*m_sock, boost::asio::buffer(response.str (),response.str ().length ()), std::bind(&HTTPProxyHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } diff --git a/HTTPServer.cpp b/HTTPServer.cpp index 6c2c6112..19ccecb5 100644 --- a/HTTPServer.cpp +++ b/HTTPServer.cpp @@ -1,29 +1,34 @@ #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" @@ -188,226 +193,131 @@ 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_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_COMMAND_SHUTDOWN_START[] = "shutdown_start"; + const char HTTP_COMMAND_SHUTDOWN_CANCEL[] = "shutdown_cancel"; + const char HTTP_COMMAND_SHUTDOWN_NOW[] = "terminate"; + const char HTTP_COMMAND_RUN_PEER_TEST[] = "run_peer_test"; + const char HTTP_COMMAND_RELOAD_CONFIG[] = "reload_config"; const char HTTP_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' }; - - } // namespace misc_strings - - 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)); - } - 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 (); - } + std::map jumpservices = { + { "inr.i2p", "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/search/?q=" }, + { "stats.i2p", "http://7tbay5p4kzeekxvyvbf6v7eauazemsnnl2aoyqhg5jzpr5eke7tq.b32.i2p/cgi-bin/jump.cgi?a=" }, + }; - 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 ShowUptime (std::stringstream& s, int seconds) { + int num; - 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 / 86400) > 0) { + s << num << " days, "; + 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 / 3600) > 0) { + s << num << " hours, "; + seconds -= num; } - else - HandleRequest (address); + if ((num = seconds / 60) > 0) { + s << num << " min, "; + seconds -= num; + } + 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 ()) { @@ -417,6 +327,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); @@ -472,73 +385,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; @@ -552,28 +424,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 ()) { @@ -624,36 +487,47 @@ namespace util } } - 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"; + s << " Reload config
\r\n"; + if (i2p::context.AcceptsTunnels ()) + s << " Stop accepting tunnels
\r\n"; + else + s << " Start accepting tunnels
\r\n"; +#ifndef WIN32 + 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"; +#endif + } + + void ShowTransitTunnels (std::stringstream& s) { s << "Transit tunnels:
\r\n
\r\n"; for (auto it: i2p::tunnel::tunnels.GetTransitTunnels ()) @@ -668,7 +542,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 (); @@ -717,66 +591,60 @@ 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 ()) { auto& ident = it.second->GetLocalDestination ()->GetIdentHash(); - s << ""; + s << ""; s << it.second->GetName () << " ⇐ "; s << i2p::client::context.GetAddressBook ().ToAddress(ident); s << "
\r\n"<< std::endl; @@ -785,143 +653,214 @@ namespace util for (auto& it: i2p::client::context.GetServerTunnels ()) { 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) - { - s << "Stop Accepting Tunnels:
\r\n
\r\n"; - i2p::context.SetAcceptsTunnels (false); - s << "Accepting tunnels stopped" << std::endl; - } - void HTTPConnection::StartAcceptingTunnels (std::stringstream& s) + HTTPConnection::HTTPConnection (std::shared_ptr socket): + m_Socket (socket), m_Timer (socket->get_io_service ()), m_BufferLen (0) { - s << "Start Accepting Tunnels:
\r\n
\r\n"; - i2p::context.SetAcceptsTunnels (true); - s << "Accepting tunnels started" << std::endl; - } + /* cache options */ + i2p::config::GetOption("http.auth", needAuth); + i2p::config::GetOption("http.user", user); + i2p::config::GetOption("http.pass", pass); + }; - void HTTPConnection::RunPeerTest (std::stringstream& s) + void HTTPConnection::Receive () { - s << "Run Peer Test:
\r\n
\r\n"; - i2p::transport::transports.PeerTest (); - s << "Peer test is running" << 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::HandleDestinationRequest (const std::string& address, const std::string& uri) + void HTTPConnection::HandleReceive (const boost::system::error_code& ecode, std::size_t bytes_transferred) { - 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 ()); - } - - 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); - SendError ("Unknown address " + address); + if (ecode) { + if (ecode != boost::asio::error::operation_aborted) + Terminate (ecode); 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)); } + m_Buffer[bytes_transferred] = '\0'; + m_BufferLen = bytes_transferred; + RunRequest(); + Receive (); } - - 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 if (leaseSet) { - SendError ("LeaseSet expired"); - } else { - SendError ("LeaseSet not found"); - } - } - } - - void HTTPConnection::SendToDestination (std::shared_ptr remote, int port, const char * buf, size_t len) + + void HTTPConnection::RunRequest () { - if (!m_Stream) - m_Stream = i2p::client::context.GetSharedLocalDestination ()->CreateStream (remote, port); - if (m_Stream) - { - m_Stream->Send ((uint8_t *)buf, len); - AsyncStreamReceive (); + 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::AsyncStreamReceive () + void HTTPConnection::Terminate (const boost::system::error_code& ecode) { - 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 + if (ecode == boost::asio::error::operation_aborted) + return; + boost::system::error_code ignored_ec; + m_Socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); + m_Socket->close (); } - 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)); + 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; } - else - { - if (ecode == boost::asio::error::timed_out) - SendError ("Host not responding"); - else if (ecode != boost::asio::error::operation_aborted) - Terminate (); + /* 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::SendReply (const std::string& content, int status) + void HTTPConnection::HandleRequest (const HTTPReq & req) { - 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 = std::to_string(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::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::SendError(const std::string& content) + 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; + } + } + + 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_RELOAD_CONFIG) + i2p::client::context.ReloadConfig (); + 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); +#ifndef WIN32 + Daemon.gracefullShutdownInterval = 10*60; +#endif + } else if (cmd == HTTP_COMMAND_SHUTDOWN_CANCEL) { + i2p::context.SetAcceptsTunnels (true); +#ifndef WIN32 + Daemon.gracefullShutdownInterval = 0; +#endif + } 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 (HTTPRes& reply, std::string& content) { - SendReply ("" + itoopieImage + "
\r\n" + content + "", 504); + 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): @@ -937,6 +876,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 (); @@ -946,11 +900,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 () @@ -968,11 +921,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) @@ -980,5 +932,5 @@ namespace util auto conn = std::make_shared (newSocket); conn->Receive (); } -} -} +} // http +} // i2p diff --git a/HTTPServer.h b/HTTPServer.h index 66083d85..2635c3be 100644 --- a/HTTPServer.h +++ b/HTTPServer.h @@ -1,108 +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 SendError (const std::string& message); + 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 @@ -110,7 +42,7 @@ namespace util public: HTTPServer (const std::string& address, int port); - virtual ~HTTPServer (); + ~HTTPServer (); void Start (); void Stop (); @@ -121,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: @@ -128,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/I2CP.cpp b/I2CP.cpp new file mode 100644 index 00000000..d6ba4e42 --- /dev/null +++ b/I2CP.cpp @@ -0,0 +1,112 @@ +#include +#include "I2PEndian.h" +#include "Log.h" +#include "I2CP.h" + + +namespace i2p +{ +namespace client +{ + I2CPSession::I2CPSession (I2CPServer& owner, std::shared_ptr socket): + m_Owner (owner), m_Socket (socket), + m_NextMessage (nullptr), m_NextMessageLen (0), m_NextMessageOffset (0) + { + ReadProtocolByte (); + } + + I2CPSession::~I2CPSession () + { + delete[] m_NextMessage; + } + + void I2CPSession::ReadProtocolByte () + { + if (m_Socket) + { + auto s = shared_from_this (); + m_Socket->async_read_some (boost::asio::buffer (m_Buffer, 1), + [s](const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (!ecode && bytes_transferred > 0 && s->m_Buffer[0] == I2CP_PRTOCOL_BYTE) + s->Receive (); + else + s->Terminate (); + }); + } + } + + void I2CPSession::Receive () + { + m_Socket->async_read_some (boost::asio::buffer (m_Buffer, I2CP_SESSION_BUFFER_SIZE), + std::bind (&I2CPSession::HandleReceived, shared_from_this (), std::placeholders::_1, std::placeholders::_2)); + } + + void I2CPSession::HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred) + { + if (ecode) + Terminate (); + else + { + size_t offset = 0; + if (m_NextMessage) + { + if (m_NextMessageOffset + bytes_transferred <= m_NextMessageLen) + { + memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, bytes_transferred); + m_NextMessageOffset += bytes_transferred; + } + else + { + offset = m_NextMessageLen - m_NextMessageOffset; + memcpy (m_NextMessage + m_NextMessageOffset, m_Buffer, offset); + HandleNextMessage (m_NextMessage); + delete[] m_NextMessage; + } + } + while (offset < bytes_transferred) + { + auto msgLen = bufbe32toh (m_Buffer + offset + I2CP_HEADER_LENGTH_OFFSET) + I2CP_HEADER_SIZE; + if (msgLen <= bytes_transferred - offset) + { + HandleNextMessage (m_Buffer + offset); + offset += msgLen; + } + else + { + m_NextMessageLen = msgLen; + m_NextMessageOffset = bytes_transferred - offset; + m_NextMessage = new uint8_t[m_NextMessageLen]; + memcpy (m_NextMessage, m_Buffer + offset, m_NextMessageOffset); + offset = bytes_transferred; + } + } + Receive (); + } + } + + void I2CPSession::HandleNextMessage (const uint8_t * buf) + { + auto handler = m_Owner.GetMessagesHandlers ()[buf[I2CP_HEADER_TYPE_OFFSET]]; + if (handler) + (this->*handler)(buf + I2CP_HEADER_SIZE, bufbe32toh (buf + I2CP_HEADER_LENGTH_OFFSET)); + else + LogPrint (eLogError, "I2CP: Unknown I2CP messsage ", (int)buf[I2CP_HEADER_TYPE_OFFSET]); + } + + void I2CPSession::Terminate () + { + } + + void I2CPSession::GetDateMessageHandler (const uint8_t * buf, size_t len) + { + } + + I2CPServer::I2CPServer (const std::string& interface, int port) + { + memset (m_MessagesHandlers, 0, sizeof (m_MessagesHandlers)); + m_MessagesHandlers[I2CP_GET_DATE_MESSAGE] = &I2CPSession::GetDateMessageHandler; + } +} +} + diff --git a/I2CP.h b/I2CP.h new file mode 100644 index 00000000..cb17626d --- /dev/null +++ b/I2CP.h @@ -0,0 +1,68 @@ +#ifndef I2CP_H__ +#define I2CP_H__ + +#include +#include +#include +#include + +namespace i2p +{ +namespace client +{ + const uint8_t I2CP_PRTOCOL_BYTE = 0x2A; + const size_t I2CP_SESSION_BUFFER_SIZE = 4096; + + const size_t I2CP_HEADER_LENGTH_OFFSET = 0; + const size_t I2CP_HEADER_TYPE_OFFSET = I2CP_HEADER_LENGTH_OFFSET + 4; + const size_t I2CP_HEADER_SIZE = I2CP_HEADER_TYPE_OFFSET + 1; + + const uint8_t I2CP_GET_DATE_MESSAGE = 32; + + class I2CPServer; + class I2CPSession: public std::enable_shared_from_this + { + public: + + I2CPSession (I2CPServer& owner, std::shared_ptr socket); + ~I2CPSession (); + + // message handlers + void GetDateMessageHandler (const uint8_t * buf, size_t len); + + private: + + void ReadProtocolByte (); + void Receive (); + void HandleReceived (const boost::system::error_code& ecode, std::size_t bytes_transferred); + void HandleNextMessage (const uint8_t * buf); + void Terminate (); + + private: + + I2CPServer& m_Owner; + std::shared_ptr m_Socket; + uint8_t m_Buffer[I2CP_SESSION_BUFFER_SIZE], * m_NextMessage; + size_t m_NextMessageLen, m_NextMessageOffset; + }; + typedef void (I2CPSession::*I2CPMessageHandler)(const uint8_t * buf, size_t len); + + class I2CPServer + { + public: + + I2CPServer (const std::string& interface, int port); + + private: + + I2CPMessageHandler m_MessagesHandlers[256]; + + public: + + const decltype(m_MessagesHandlers)& GetMessagesHandlers () const { return m_MessagesHandlers; }; + }; +} +} + +#endif + diff --git a/I2NPProtocol.cpp b/I2NPProtocol.cpp index 9674fdca..e2451f68 100644 --- a/I2NPProtocol.cpp +++ b/I2NPProtocol.cpp @@ -1,7 +1,5 @@ #include #include -#include -#include #include "Base.h" #include "Log.h" #include "Crypto.h" diff --git a/I2NPProtocol.h b/I2NPProtocol.h index cf8f4266..113e8eb8 100644 --- a/I2NPProtocol.h +++ b/I2NPProtocol.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include "Crypto.h" #include "I2PEndian.h" #include "Identity.h" #include "RouterInfo.h" diff --git a/I2PControl.cpp b/I2PControl.cpp index 1ef56c2d..ab9feeed 100644 --- a/I2PControl.cpp +++ b/I2PControl.cpp @@ -13,6 +13,7 @@ #include #endif +#include "Crypto.h" #include "FS.h" #include "Log.h" #include "Config.h" diff --git a/I2PControl.h b/I2PControl.h index 728c9925..f2e82254 100644 --- a/I2PControl.h +++ b/I2PControl.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include namespace i2p 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 f221dd04..71ca007f 100644 --- a/Identity.cpp +++ b/Identity.cpp @@ -1,8 +1,5 @@ #include #include -#include -#include -#include #include "Crypto.h" #include "I2PEndian.h" #include "Log.h" 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/Makefile.linux b/Makefile.linux index 24816770..791382c6 100644 --- a/Makefile.linux +++ b/Makefile.linux @@ -19,7 +19,7 @@ else ifeq ($(shell expr match ${CXXVER} "4\.[7-9]"),3) # >= 4.7 NEEDED_CXXFLAGS += -std=c++11 -D_GLIBCXX_USE_NANOSLEEP=1 else ifeq ($(shell expr match ${CXXVER} "4\.6"),3) # = 4.6 NEEDED_CXXFLAGS += -std=c++0x -else ifeq ($(shell expr match ${CXXVER} "5\.[0-9]"),3) # gcc >= 5.0 +else ifeq ($(shell expr match ${CXXVER} "[5-6]\.[0-9]"),3) # gcc >= 5.0 NEEDED_CXXFLAGS += -std=c++11 else # not supported $(error Compiler too old) diff --git a/NTCPSession.cpp b/NTCPSession.cpp index a7832335..ae020f5f 100644 --- a/NTCPSession.cpp +++ b/NTCPSession.cpp @@ -1,13 +1,11 @@ #include #include -#include -#include #include #include "I2PEndian.h" #include "Base.h" +#include "Crypto.h" #include "Log.h" #include "Timestamp.h" -#include "Crypto.h" #include "I2NPProtocol.h" #include "RouterContext.h" #include "Transports.h" diff --git a/NetDb.cpp b/NetDb.cpp index 857381d8..3aeff92f 100644 --- a/NetDb.cpp +++ b/NetDb.cpp @@ -2,10 +2,10 @@ #include #include #include -#include #include #include "I2PEndian.h" #include "Base.h" +#include "Crypto.h" #include "Log.h" #include "Timestamp.h" #include "I2NPProtocol.h" diff --git a/Reseed.cpp b/Reseed.cpp index caac8071..ddefc460 100644 --- a/Reseed.cpp +++ b/Reseed.cpp @@ -3,18 +3,17 @@ #include #include #include -#include -#include +#include #include #include #include +#include "Crypto.h" #include "I2PEndian.h" #include "Reseed.h" #include "FS.h" #include "Log.h" #include "Identity.h" -#include "Crypto.h" #include "NetDb.h" #include "util.h" diff --git a/RouterContext.cpp b/RouterContext.cpp index f35d8426..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; 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 c27f8754..2e76127c 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -636,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 c9881dd2..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; }; diff --git a/SSUSession.cpp b/SSUSession.cpp index aa534c56..cf56ca15 100644 --- a/SSUSession.cpp +++ b/SSUSession.cpp @@ -1,7 +1,5 @@ #include -#include -#include -#include +#include "Crypto.h" #include "Log.h" #include "Timestamp.h" #include "RouterContext.h" @@ -265,8 +263,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 +279,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 +316,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/Signature.h b/Signature.h index a0b54468..5934f8d2 100644 --- a/Signature.h +++ b/Signature.h @@ -3,12 +3,10 @@ #include #include -#include #include #include #include #include -#include #include #include "Crypto.h" diff --git a/Streaming.cpp b/Streaming.cpp index e425ce27..ab0a6df0 100644 --- a/Streaming.cpp +++ b/Streaming.cpp @@ -1,4 +1,4 @@ -#include +#include "Crypto.h" #include "Log.h" #include "RouterInfo.h" #include "RouterContext.h" diff --git a/Transports.cpp b/Transports.cpp index 0fdfce3d..057e2472 100644 --- a/Transports.cpp +++ b/Transports.cpp @@ -1,4 +1,3 @@ -#include #include "Log.h" #include "Crypto.h" #include "RouterContext.h" diff --git a/Tunnel.cpp b/Tunnel.cpp index 5e237d87..bf81dc5e 100644 --- a/Tunnel.cpp +++ b/Tunnel.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include "Crypto.h" #include "RouterContext.h" #include "Log.h" #include "Timestamp.h" diff --git a/TunnelConfig.h b/TunnelConfig.h index c089b13c..0340254e 100644 --- a/TunnelConfig.h +++ b/TunnelConfig.h @@ -5,7 +5,6 @@ #include #include #include -#include #include "Crypto.h" #include "Identity.h" #include "RouterContext.h" diff --git a/TunnelEndpoint.cpp b/TunnelEndpoint.cpp index 842b624f..1bc8a937 100644 --- a/TunnelEndpoint.cpp +++ b/TunnelEndpoint.cpp @@ -1,6 +1,6 @@ #include "I2PEndian.h" #include -#include +#include "Crypto.h" #include "Log.h" #include "NetDb.h" #include "I2NPProtocol.h" diff --git a/TunnelGateway.cpp b/TunnelGateway.cpp index 4f517eb8..3383010b 100644 --- a/TunnelGateway.cpp +++ b/TunnelGateway.cpp @@ -1,6 +1,5 @@ #include -#include -#include +#include "Crypto.h" #include "I2PEndian.h" #include "Log.h" #include "RouterContext.h" diff --git a/TunnelPool.cpp b/TunnelPool.cpp index c74ff475..5e7e8ec4 100644 --- a/TunnelPool.cpp +++ b/TunnelPool.cpp @@ -1,5 +1,4 @@ #include -#include #include "I2PEndian.h" #include "Crypto.h" #include "Tunnel.h" diff --git a/Win32/Anke_2200px.jpg b/Win32/Anke_2200px.jpg deleted file mode 100644 index 1c5b0f31..00000000 Binary files a/Win32/Anke_2200px.jpg and /dev/null differ diff --git a/Win32/Anke_700px.bmp b/Win32/Anke_700px.bmp deleted file mode 100644 index f63cccbb..00000000 Binary files a/Win32/Anke_700px.bmp and /dev/null differ diff --git a/Win32/Resource.rc b/Win32/Resource.rc index c885c044..dc0f82fa 100644 --- a/Win32/Resource.rc +++ b/Win32/Resource.rc @@ -55,8 +55,6 @@ END MAINICON ICON "ictoopie.ico" //MAINICON ICON "anke.ico" -MASCOT BITMAP "Anke_700px.bmp" - #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/Win32/Win32App.cpp b/Win32/Win32App.cpp index 81a3ba48..01432381 100644 --- a/Win32/Win32App.cpp +++ b/Win32/Win32App.cpp @@ -167,19 +167,6 @@ namespace win32 } break; } - case WM_PAINT: - { - PAINTSTRUCT ps; - auto hDC = BeginPaint (hWnd, &ps); - auto mascot = LoadBitmap (GetModuleHandle(NULL), MAKEINTRESOURCE (MASCOT)); - auto mascotDC = CreateCompatibleDC (hDC); - SelectObject (mascotDC, mascot); - BitBlt (hDC, 0,0, 533, 700, mascotDC, 0, 0, SRCCOPY); - DeleteDC (mascotDC); - DeleteObject (mascot); - EndPaint (hWnd, &ps); - break; - } } return DefWindowProc( hWnd, uMsg, wParam, lParam); } diff --git a/Win32/anke.ico b/Win32/anke.ico deleted file mode 100644 index 509177bb..00000000 Binary files a/Win32/anke.ico and /dev/null differ diff --git a/Win32/resource.h b/Win32/resource.h index f37fce46..a8309c8b 100644 --- a/Win32/resource.h +++ b/Win32/resource.h @@ -3,7 +3,6 @@ // Used by Resource.rc // #define MAINICON 101 -#define MASCOT 201 // Next default values for new objects // diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 76963026..7944e2ec 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -74,7 +74,9 @@ 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" + "${CMAKE_SOURCE_DIR}/I2CP.cpp" ) add_library(i2pdclient ${CLIENT_SRC}) 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/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 2a639be3..11e8b4a8 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -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 diff --git a/filelist.mk b/filelist.mk index c35edcf0..8abf0b4b 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 I2CP.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)