From 2cb5e1a6c21d0d28eecb3c846bd15c457d97d8fa Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 1/6] * HTTPProxy.cpp : kill ExtractRequest() --- HTTPProxy.cpp | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index db58d1dc..9dd2cc87 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -52,7 +51,6 @@ namespace proxy { void AsyncSockRead(); void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); - void ExtractRequest(); bool ValidateHTTPRequest(); void HandleJumpServices(); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); @@ -142,19 +140,6 @@ namespace proxy { m_state = nstate; } - void HTTPReqHandler::ExtractRequest() - { - LogPrint(eLogDebug, "HTTPProxy: request: ", m_method, " ", m_url); - i2p::http::URL url; - url.parse (m_url); - m_address = url.host; - m_port = url.port; - m_path = url.path; - if (url.query.length () > 0) m_path += "?" + url.query; - if (!m_port) m_port = 80; - LogPrint(eLogDebug, "HTTPProxy: server: ", m_address, ", port: ", m_port, ", path: ", m_path); - } - bool HTTPReqHandler::ValidateHTTPRequest() { if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) @@ -200,7 +185,16 @@ namespace proxy { bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { - ExtractRequest(); //TODO: parse earlier + i2p::http::URL url; + url.parse(m_url); + m_address = url.host; /* < compatibility */ + m_port = url.port; /* < compatibility */ + if (!m_port) { + m_port = (url.schema == "https") ? 443 : 80; + } + url.schema = ""; + url.host = ""; + m_path = url.to_string(); /* < compatibility */ if (!ValidateHTTPRequest()) return false; HandleJumpServices(); From 66c09fc44c9526d4f8d9e347e6899f3b72413de7 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 2/6] * HTTPProxy.cpp : HandleJumpServices() -> ExtractAddressHelper() --- HTTPProxy.cpp | 57 +++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 9dd2cc87..a2ed965d 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -52,7 +52,7 @@ namespace proxy { void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); bool ValidateHTTPRequest(); - void HandleJumpServices(); + bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); @@ -123,12 +123,14 @@ namespace proxy { /* TODO: don't redirect to webconsole, it's not always work, handle jumpservices here */ i2p::config::GetOption("http.address", url.host); i2p::config::GetOption("http.port", url.port); + url.schema = "http"; url.path = "/"; url.query = "page=jumpservices&address="; url.query += host; res.code = 302; /* redirect */ res.add_header("Location", url.to_string().c_str()); + res.add_header("Connection", "close"); std::string response = res.to_string(); boost::asio::async_write(*m_sock, boost::asio::buffer(response), @@ -151,40 +153,28 @@ namespace proxy { return true; } - void HTTPReqHandler::HandleJumpServices() + bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64) { - static const char * helpermark1 = "?i2paddresshelper="; - static const char * helpermark2 = "&i2paddresshelper="; - size_t addressHelperPos1 = m_path.rfind (helpermark1); - size_t addressHelperPos2 = m_path.rfind (helpermark2); - size_t addressHelperPos; - if (addressHelperPos1 == std::string::npos) - { - if (addressHelperPos2 == std::string::npos) - return; //Not a jump service - else - addressHelperPos = addressHelperPos2; - } - else - { - if (addressHelperPos2 == std::string::npos) - addressHelperPos = addressHelperPos1; - else if ( addressHelperPos1 > addressHelperPos2 ) - addressHelperPos = addressHelperPos1; - else - addressHelperPos = addressHelperPos2; - } - auto base64 = m_path.substr (addressHelperPos + strlen(helpermark1)); - base64 = i2p::util::http::urlDecode(base64); //Some of the symbols may be urlencoded - LogPrint (eLogInfo, "HTTPProxy: jump service for ", m_address, ", inserting to address book"); - //TODO: this is very dangerous and broken. We should ask the user before doing anything see http://pastethis.i2p/raw/pn5fL4YNJL7OSWj3Sc6N/ - //TODO: we could redirect the user again to avoid dirtiness in the browser - i2p::client::context.GetAddressBook ().InsertAddress (m_address, base64); - m_path.erase(addressHelperPos); + const char *param = "i2paddresshelper="; + std::size_t pos = url.query.find(param); + std::size_t len = std::strlen(param); + std::map params; + + if (pos == std::string::npos) + return false; /* not found */ + if (!url.parse_query(params)) + return false; + + std::string value = params["i2paddresshelper"]; + len += value.length(); + b64 = i2p::http::UrlDecode(value); + url.query.replace(pos, len, ""); + return true; } bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { + std::string b64; i2p::http::URL url; url.parse(m_url); m_address = url.host; /* < compatibility */ @@ -196,7 +186,12 @@ namespace proxy { url.host = ""; m_path = url.to_string(); /* < compatibility */ if (!ValidateHTTPRequest()) return false; - HandleJumpServices(); + + /* TODO: notify user */ + if (ExtractAddressHelper(url, b64)) { + i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64); + LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", url.host, " to address book"); + } i2p::data::IdentHash identHash; if (str_rmatch(m_address, ".i2p")) From 9fd78b1eb1bb01f8be9139abe2b4d82c5c0275c0 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 3/6] * HTTPProxy.cpp : rename variable --- HTTPProxy.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index a2ed965d..c5a355c7 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -31,7 +31,6 @@ namespace proxy { return false; } - static const size_t http_buffer_size = 8192; class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: @@ -57,9 +56,9 @@ namespace proxy { void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); - uint8_t m_http_buff[http_buffer_size]; + uint8_t m_recv_buf[8192]; + std::string m_request; //Data left to be sent. TODO: rename to m_send_buf std::shared_ptr m_sock; - std::string m_request; //Data left to be sent std::string m_url; //URL std::string m_method; //Method std::string m_version; //HTTP version @@ -84,7 +83,7 @@ namespace proxy { LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } - m_sock->async_receive(boost::asio::buffer(m_http_buff, http_buffer_size), + m_sock->async_receive(boost::asio::buffer(m_recv_buf, sizeof(m_recv_buf)), std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } @@ -302,7 +301,7 @@ namespace proxy { return; } - if (HandleData(m_http_buff, len)) + if (HandleData(m_recv_buf, len)) { if (m_state == DONE) { From 642b01bf0d42159dde7fcc32a87f7ba393868edf Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 4/6] * HTTPProxy.cpp : add SanitizeHTTPRequest() --- HTTPProxy.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index c5a355c7..43806b86 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -52,6 +52,7 @@ namespace proxy { void RedirectToJumpService(std::string & host); bool ValidateHTTPRequest(); bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); + void SanitizeHTTPRequest(i2p::http::HTTPReq & req); bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); @@ -171,6 +172,31 @@ namespace proxy { return true; } + void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) + { + /* drop common headers */ + req.del_header("Referer"); + req.del_header("Via"); + req.del_header("Forwarded"); + /* drop proxy-disclosing headers */ + std::vector toErase; + for (auto it : req.headers) { + if (it.first.compare(0, 12, "X-Forwarded-") == 0) { + toErase.push_back(it.first); + } else if (it.first.compare(0, 6, "Proxy-") == 0) { + toErase.push_back(it.first); + } else { + /* allow */ + } + } + for (auto header : toErase) { + req.headers.erase(header); + } + /* replace headers */ + req.add_header("Connection", "close", true); /* keep-alive conns not supported yet */ + req.add_header("User-Agent", "MYOB/6.66 (AN/ON)", true); /* privacy */ + } + bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) { std::string b64; From 8deb327b3b0a10d76f3cc55a21f52422d4061e1b Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 5/6] * HTTPProxy.cpp : * migrate to HTTPReq * change work with buffers * code cleanup --- HTTPProxy.cpp | 262 +++++++++++++++++++------------------------------- 1 file changed, 101 insertions(+), 161 deletions(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 43806b86..15e9f539 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -34,47 +34,29 @@ namespace proxy { class HTTPReqHandler: public i2p::client::I2PServiceHandler, public std::enable_shared_from_this { private: - enum state - { - GET_METHOD, - GET_HOSTNAME, - GET_HTTPV, - GET_HTTPVNL, //TODO: fallback to finding HOst: header if needed - DONE - }; - - void EnterState(state nstate); - bool HandleData(uint8_t *http_buff, std::size_t len); + + bool HandleRequest(); void HandleSockRecv(const boost::system::error_code & ecode, std::size_t bytes_transfered); void Terminate(); void AsyncSockRead(); void HTTPRequestFailed(const char *message); void RedirectToJumpService(std::string & host); - bool ValidateHTTPRequest(); bool ExtractAddressHelper(i2p::http::URL & url, std::string & b64); void SanitizeHTTPRequest(i2p::http::HTTPReq & req); - bool CreateHTTPRequest(uint8_t *http_buff, std::size_t len); void SentHTTPFailed(const boost::system::error_code & ecode); void HandleStreamRequestComplete (std::shared_ptr stream); - uint8_t m_recv_buf[8192]; - std::string m_request; //Data left to be sent. TODO: rename to m_send_buf + uint8_t m_recv_chunk[8192]; + std::string m_recv_buf; // from client + std::string m_send_buf; // to upstream std::shared_ptr m_sock; - std::string m_url; //URL - std::string m_method; //Method - std::string m_version; //HTTP version - std::string m_address; //Address - std::string m_path; //Path - int m_port; //Port - state m_state;//Parsing state public: HTTPReqHandler(HTTPProxy * parent, std::shared_ptr sock) : - I2PServiceHandler(parent), m_sock(sock) - { EnterState(GET_METHOD); } + I2PServiceHandler(parent), m_sock(sock) {} ~HTTPReqHandler() { Terminate(); } - void Handle () { AsyncSockRead(); } + void Handle () { AsyncSockRead(); } /* overload */ }; void HTTPReqHandler::AsyncSockRead() @@ -84,7 +66,7 @@ namespace proxy { LogPrint(eLogError, "HTTPProxy: no socket for read"); return; } - m_sock->async_receive(boost::asio::buffer(m_recv_buf, sizeof(m_recv_buf)), + m_sock->async_read_some(boost::asio::buffer(m_recv_chunk, sizeof(m_recv_chunk)), std::bind(&HTTPReqHandler::HandleSockRecv, shared_from_this(), std::placeholders::_1, std::placeholders::_2)); } @@ -100,8 +82,6 @@ namespace proxy { Done(shared_from_this()); } - /* All hope is lost beyond this point */ - //TODO: handle this apropriately void HTTPReqHandler::HTTPRequestFailed(const char *message) { i2p::http::HTTPRes res; @@ -137,22 +117,6 @@ namespace proxy { std::bind(&HTTPReqHandler::SentHTTPFailed, shared_from_this(), std::placeholders::_1)); } - void HTTPReqHandler::EnterState(HTTPReqHandler::state nstate) - { - m_state = nstate; - } - - bool HTTPReqHandler::ValidateHTTPRequest() - { - if ( m_version != "HTTP/1.0" && m_version != "HTTP/1.1" ) - { - LogPrint(eLogError, "HTTPProxy: unsupported version: ", m_version); - HTTPRequestFailed("unsupported HTTP version"); - return false; - } - return true; - } - bool HTTPReqHandler::ExtractAddressHelper(i2p::http::URL & url, std::string & b64) { const char *param = "i2paddresshelper="; @@ -197,129 +161,110 @@ namespace proxy { req.add_header("User-Agent", "MYOB/6.66 (AN/ON)", true); /* privacy */ } - bool HTTPReqHandler::CreateHTTPRequest(uint8_t *http_buff, std::size_t len) + /** + * @brief Try to parse request from @a m_recv_buf + * If parsing success, rebuild request and store to @a m_send_buf + * with remaining data tail + * @return true on processed request or false if more data needed + */ + bool HTTPReqHandler::HandleRequest() { - std::string b64; + i2p::http::HTTPReq req; i2p::http::URL url; - url.parse(m_url); - m_address = url.host; /* < compatibility */ - m_port = url.port; /* < compatibility */ - if (!m_port) { - m_port = (url.schema == "https") ? 443 : 80; + std::string b64; + int req_len = 0; + + req_len = req.parse(m_recv_buf); + + if (req_len == 0) + return false; /* need more data */ + + if (req_len < 0) { + LogPrint(eLogError, "HTTPProxy: unable to parse request"); + HTTPRequestFailed("invalid request"); + return true; /* parse error */ } - url.schema = ""; - url.host = ""; - m_path = url.to_string(); /* < compatibility */ - if (!ValidateHTTPRequest()) return false; - /* TODO: notify user */ + /* parsing success, now let's look inside request */ + LogPrint(eLogDebug, "HTTPProxy: requested: ", req.uri); + url.parse(req.uri); + + /* TODO: show notice page with original link */ if (ExtractAddressHelper(url, b64)) { i2p::client::context.GetAddressBook ().InsertAddress (url.host, b64); - LogPrint (eLogInfo, "HTTPProxy: added b64 from addresshelper for ", url.host, " to address book"); + std::string message = "added b64 from addresshelper for " + url.host + " to address book"; + LogPrint (eLogInfo, "HTTPProxy: ", message); + message += ", please reload page"; + HTTPRequestFailed(message.c_str()); + return true; /* request processed */ + } + + SanitizeHTTPRequest(req); + + std::string dest_host = url.host; + uint16_t dest_port = url.port; + /* always set port, even if missing in request */ + if (!dest_port) { + dest_port = (url.schema == "https") ? 443 : 80; + } + /* detect dest_host, set proper 'Host' header in upstream request */ + auto h = req.headers.find("Host"); + if (dest_host != "") { + /* absolute url, replace 'Host' header */ + std::string h = dest_host; + if (dest_port != 0 && dest_port != 80) + h += ":" + std::to_string(dest_port); + req.add_header("Host", h, true); + } else if (h != req.headers.end()) { + /* relative url and 'Host' header provided. transparent proxy mode? */ + i2p::http::URL u; + std::string t = "http://" + h->second; + u.parse(t); + dest_host = u.host; + dest_port = u.port; + } else { + /* relative url and missing 'Host' header */ + const char *message = "Can't detect destination host from request"; + HTTPRequestFailed(message); + return true; } + /* check dest_host really exists and inside I2P network */ i2p::data::IdentHash identHash; - if (str_rmatch(m_address, ".i2p")) - { - if (!i2p::client::context.GetAddressBook ().GetIdentHash (m_address, identHash)){ - RedirectToJumpService(m_address); - return false; + if (str_rmatch(dest_host, ".i2p")) { + if (!i2p::client::context.GetAddressBook ().GetIdentHash (dest_host, identHash)) { + RedirectToJumpService(dest_host); /* unknown host */ + return true; /* request processed */ } + /* TODO: outproxy handler here */ + } else { + std::string message = "Host " + url.host + " not inside i2p network, but outproxy support still missing"; + HTTPRequestFailed(message.c_str()); + LogPrint (eLogWarning, "HTTPProxy: ", message); + return true; } - m_request = m_method; - m_request.push_back(' '); - m_request += m_path; - m_request.push_back(' '); - m_request += m_version; - m_request.push_back('\r'); - m_request.push_back('\n'); - m_request.append("Connection: close\r\n"); - // TODO: temporary shortcut. Must be implemented properly - uint8_t * eol = nullptr; - bool isEndOfHeader = false; - while (!isEndOfHeader && len && (eol = (uint8_t *)memchr (http_buff, '\r', len))) - { - if (eol) - { - *eol = 0; eol++; - if (strncmp ((const char *)http_buff, "Referer", 7) && strncmp ((const char *)http_buff, "Connection", 10)) // strip out referer and connection - { - if (!strncmp ((const char *)http_buff, "User-Agent", 10)) // replace UserAgent - m_request.append("User-Agent: MYOB/6.66 (AN/ON)"); - else - m_request.append ((const char *)http_buff); - m_request.append ("\r\n"); - } - isEndOfHeader = !http_buff[0]; - auto l = eol - http_buff; - http_buff = eol; - len -= l; - if (len > 0) // \r - { - http_buff++; - len--; - } - } - } - m_request.append(reinterpret_cast(http_buff),len); - return true; - } + /* make relative url */ + url.schema = ""; + url.host = ""; + req.uri = url.to_string(); - bool HTTPReqHandler::HandleData(uint8_t *http_buff, std::size_t len) - { - while (len > 0) - { - //TODO: fallback to finding HOst: header if needed - switch (m_state) - { - case GET_METHOD: - switch (*http_buff) - { - case ' ': EnterState(GET_HOSTNAME); break; - default: m_method.push_back(*http_buff); break; - } - break; - case GET_HOSTNAME: - switch (*http_buff) - { - case ' ': EnterState(GET_HTTPV); break; - default: m_url.push_back(*http_buff); break; - } - break; - case GET_HTTPV: - switch (*http_buff) - { - case '\r': EnterState(GET_HTTPVNL); break; - default: m_version.push_back(*http_buff); break; - } - break; - case GET_HTTPVNL: - switch (*http_buff) - { - case '\n': EnterState(DONE); break; - default: - LogPrint(eLogError, "HTTPProxy: rejected invalid request ending with: ", ((int)*http_buff)); - HTTPRequestFailed("rejected invalid request"); - return false; - } - break; - default: - LogPrint(eLogError, "HTTPProxy: invalid state: ", m_state); - HTTPRequestFailed("invalid parser state"); - return false; - } - http_buff++; - len--; - if (m_state == DONE) - return CreateHTTPRequest(http_buff,len); - } + /* drop original request from recv buffer */ + m_recv_buf.erase(0, req_len); + /* build new buffer from modified request and data from original request */ + m_send_buf = req.to_string(); + m_send_buf.append(m_recv_buf); + /* connect to destination */ + LogPrint(eLogDebug, "HTTPProxy: connecting to host ", dest_host, ":", dest_port); + GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, + shared_from_this(), std::placeholders::_1), dest_host, dest_port); return true; } + /* will be called after some data received from client */ void HTTPReqHandler::HandleSockRecv(const boost::system::error_code & ecode, std::size_t len) { - LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes"); + LogPrint(eLogDebug, "HTTPProxy: sock recv: ", len, " bytes, recv buf: ", m_recv_buf.length(), ", send buf: ", m_send_buf.length()); if(ecode) { LogPrint(eLogWarning, "HTTPProxy: sock recv got error: ", ecode); @@ -327,17 +272,12 @@ namespace proxy { return; } - if (HandleData(m_recv_buf, len)) - { - if (m_state == DONE) - { - LogPrint(eLogDebug, "HTTPProxy: requested: ", m_url); - GetOwner()->CreateStream (std::bind (&HTTPReqHandler::HandleStreamRequestComplete, - shared_from_this(), std::placeholders::_1), m_address, m_port); - } - else - AsyncSockRead(); + m_recv_buf.append(reinterpret_cast(m_recv_chunk), len); + if (HandleRequest()) { + m_recv_buf.clear(); + return; } + AsyncSockRead(); } void HTTPReqHandler::SentHTTPFailed(const boost::system::error_code & ecode) @@ -356,10 +296,10 @@ namespace proxy { } if (Kill()) return; - LogPrint (eLogDebug, "HTTPProxy: New I2PTunnel connection"); + LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream"); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); - connection->I2PConnect (reinterpret_cast(m_request.data()), m_request.size()); + connection->I2PConnect (reinterpret_cast(m_send_buf.data()), m_send_buf.length()); Done (shared_from_this()); } From da2c04f6815e76eb8c74d8b3d5709374deb188c7 Mon Sep 17 00:00:00 2001 From: hagen Date: Fri, 1 Jul 2016 00:00:00 +0000 Subject: [PATCH 6/6] * HTTPProxy.cpp : show created stream IDs in log --- HTTPProxy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HTTPProxy.cpp b/HTTPProxy.cpp index 15e9f539..e70eb8ab 100644 --- a/HTTPProxy.cpp +++ b/HTTPProxy.cpp @@ -296,7 +296,7 @@ namespace proxy { } if (Kill()) return; - LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream"); + LogPrint (eLogDebug, "HTTPProxy: Created new I2PTunnel stream, sSID=", stream->GetSendStreamID(), ", rSID=", stream->GetRecvStreamID()); auto connection = std::make_shared(GetOwner(), m_sock, stream); GetOwner()->AddHandler (connection); connection->I2PConnect (reinterpret_cast(m_send_buf.data()), m_send_buf.length());