summaryrefslogtreecommitdiff
path: root/methods
diff options
context:
space:
mode:
authorDavid Kalnischkies <david@kalnischkies.de>2016-08-04 08:45:38 +0200
committerDavid Kalnischkies <david@kalnischkies.de>2016-08-10 23:19:44 +0200
commit61db48241f2d46697a291bfedaf398a1ca9a70e3 (patch)
tree56ce3677cf02984cb87f490275299c614b22663e /methods
parent30060442025824c491f58887ca7369f3c572fa57 (diff)
implement socks5h proxy support for http method
Socks support is a requested feature in sofar that the internet is actually believing Acquire::socks::Proxy would exist. It doesn't and this commit isn't adding it as that isn't how our configuration works, but it allows Acquire::http::Proxy="socks5h://…". The HTTPS method was changed already to support socks proxies (all versions) via curl. This commit implements only SOCKS5 (RFC1928) with no auth or pass&user auth (RFC1929), but not GSSAPI which is required by the RFC. The 'h' in the protocol name further indicates that DNS resolution is delegated to the socks proxy rather than performed locally. The implementation works and was tested with Tor as socks proxy for which implementing socks5h only can actually be considered a feature. Closes: 744934
Diffstat (limited to 'methods')
-rw-r--r--methods/http.cc191
-rw-r--r--methods/http.h2
2 files changed, 170 insertions, 23 deletions
diff --git a/methods/http.cc b/methods/http.cc
index 0378d7c60..1ed2e3629 100644
--- a/methods/http.cc
+++ b/methods/http.cc
@@ -44,6 +44,7 @@
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
+#include <arpa/inet.h>
#include <iostream>
#include <sstream>
@@ -151,9 +152,9 @@ bool CircleBuf::Read(int Fd)
// CircleBuf::Read - Put the string into the buffer /*{{{*/
// ---------------------------------------------------------------------
/* This will hold the string in and fill the buffer with it as it empties */
-bool CircleBuf::Read(string Data)
+bool CircleBuf::Read(string const &Data)
{
- OutQueue += Data;
+ OutQueue.append(Data);
FillOut();
return true;
}
@@ -296,6 +297,24 @@ HttpServerState::HttpServerState(URI Srv,HttpMethod *Owner) : ServerState(Srv, O
// HttpServerState::Open - Open a connection to the server /*{{{*/
// ---------------------------------------------------------------------
/* This opens a connection to the server. */
+static bool TalkToSocksProxy(int const ServerFd, std::string const &Proxy,
+ char const * const type, bool const ReadWrite, uint8_t * const ToFrom,
+ unsigned int const Size, unsigned int const Timeout)
+{
+ if (WaitFd(ServerFd, ReadWrite, Timeout) == false)
+ return _error->Error("Waiting for the SOCKS proxy %s to %s timed out", URI::SiteOnly(Proxy).c_str(), type);
+ if (ReadWrite == false)
+ {
+ if (FileFd::Read(ServerFd, ToFrom, Size) == false)
+ return _error->Error("Reading the %s from SOCKS proxy %s failed", type, URI::SiteOnly(Proxy).c_str());
+ }
+ else
+ {
+ if (FileFd::Write(ServerFd, ToFrom, Size) == false)
+ return _error->Error("Writing the %s to SOCKS proxy %s failed", type, URI::SiteOnly(Proxy).c_str());
+ }
+ return true;
+}
bool HttpServerState::Open()
{
// Use the already open connection if possible.
@@ -337,29 +356,156 @@ bool HttpServerState::Open()
if (CheckDomainList(ServerName.Host,getenv("no_proxy")) == true)
Proxy = "";
}
-
- // Determine what host and port to use based on the proxy settings
- int Port = 0;
- string Host;
- if (Proxy.empty() == true || Proxy.Host.empty() == true)
+
+ if (Proxy.Access == "socks5h")
{
- if (ServerName.Port != 0)
- Port = ServerName.Port;
- Host = ServerName.Host;
+ if (Connect(Proxy.Host, Proxy.Port, "socks", 1080, ServerFd, TimeOut, Owner) == false)
+ return false;
+
+ /* We implement a very basic SOCKS5 client here complying mostly to RFC1928 expect
+ * for not offering GSSAPI auth which is a must (we only do no or user/pass auth).
+ * We also expect the SOCKS5 server to do hostname lookup (aka socks5h) */
+ std::string const ProxyInfo = URI::SiteOnly(Proxy);
+ Owner->Status(_("Connecting to %s (%s)"),"SOCKS5h proxy",ProxyInfo.c_str());
+ auto const Timeout = Owner->ConfigFindI("TimeOut", 120);
+ #define APT_WriteOrFail(TYPE, DATA, LENGTH) if (TalkToSocksProxy(ServerFd, ProxyInfo, TYPE, true, DATA, LENGTH, Timeout) == false) return false
+ #define APT_ReadOrFail(TYPE, DATA, LENGTH) if (TalkToSocksProxy(ServerFd, ProxyInfo, TYPE, false, DATA, LENGTH, Timeout) == false) return false
+ if (ServerName.Host.length() > 255)
+ return _error->Error("Can't use SOCKS5h as hostname %s is too long!", ServerName.Host.c_str());
+ if (Proxy.User.length() > 255 || Proxy.Password.length() > 255)
+ return _error->Error("Can't use user&pass auth as they are too long (%lu and %lu) for the SOCKS5!", Proxy.User.length(), Proxy.Password.length());
+ if (Proxy.User.empty())
+ {
+ uint8_t greeting[] = { 0x05, 0x01, 0x00 };
+ APT_WriteOrFail("greet-1", greeting, sizeof(greeting));
+ }
+ else
+ {
+ uint8_t greeting[] = { 0x05, 0x02, 0x00, 0x02 };
+ APT_WriteOrFail("greet-2", greeting, sizeof(greeting));
+ }
+ uint8_t greeting[2];
+ APT_ReadOrFail("greet back", greeting, sizeof(greeting));
+ if (greeting[0] != 0x05)
+ return _error->Error("SOCKS proxy %s greets back with wrong version: %d", ProxyInfo.c_str(), greeting[0]);
+ if (greeting[1] == 0x00)
+ ; // no auth has no method-dependent sub-negotiations
+ else if (greeting[1] == 0x02)
+ {
+ if (Proxy.User.empty())
+ return _error->Error("SOCKS proxy %s negotiated user&pass auth, but we had not offered it!", ProxyInfo.c_str());
+ // user&pass auth sub-negotiations are defined by RFC1929
+ std::vector<uint8_t> auth = {{ 0x01, static_cast<uint8_t>(Proxy.User.length()) }};
+ std::copy(Proxy.User.begin(), Proxy.User.end(), std::back_inserter(auth));
+ auth.push_back(static_cast<uint8_t>(Proxy.Password.length()));
+ std::copy(Proxy.Password.begin(), Proxy.Password.end(), std::back_inserter(auth));
+ APT_WriteOrFail("user&pass auth", auth.data(), auth.size());
+ uint8_t authstatus[2];
+ APT_ReadOrFail("auth report", authstatus, sizeof(authstatus));
+ if (authstatus[0] != 0x01)
+ return _error->Error("SOCKS proxy %s auth status response with wrong version: %d", ProxyInfo.c_str(), authstatus[0]);
+ if (authstatus[1] != 0x00)
+ return _error->Error("SOCKS proxy %s reported authorization failure: username or password incorrect? (%d)", ProxyInfo.c_str(), authstatus[1]);
+ }
+ else
+ return _error->Error("SOCKS proxy %s greets back having not found a common authorization method: %d", ProxyInfo.c_str(), greeting[1]);
+ union { uint16_t * i; uint8_t * b; } portu;
+ uint16_t port = htons(static_cast<uint16_t>(ServerName.Port == 0 ? 80 : ServerName.Port));
+ portu.i = &port;
+ std::vector<uint8_t> request = {{ 0x05, 0x01, 0x00, 0x03, static_cast<uint8_t>(ServerName.Host.length()) }};
+ std::copy(ServerName.Host.begin(), ServerName.Host.end(), std::back_inserter(request));
+ request.push_back(portu.b[0]);
+ request.push_back(portu.b[1]);
+ APT_WriteOrFail("request", request.data(), request.size());
+ uint8_t response[4];
+ APT_ReadOrFail("first part of response", response, sizeof(response));
+ if (response[0] != 0x05)
+ return _error->Error("SOCKS proxy %s response with wrong version: %d", ProxyInfo.c_str(), response[0]);
+ if (response[2] != 0x00)
+ return _error->Error("SOCKS proxy %s has unexpected non-zero reserved field value: %d", ProxyInfo.c_str(), response[2]);
+ std::string bindaddr;
+ if (response[3] == 0x01) // IPv4 address
+ {
+ uint8_t ip4port[6];
+ APT_ReadOrFail("IPv4+Port of response", ip4port, sizeof(ip4port));
+ portu.b[0] = ip4port[4];
+ portu.b[1] = ip4port[5];
+ port = ntohs(*portu.i);
+ strprintf(bindaddr, "%d.%d.%d.%d:%d", ip4port[0], ip4port[1], ip4port[2], ip4port[3], port);
+ }
+ else if (response[3] == 0x03) // hostname
+ {
+ uint8_t namelength;
+ APT_ReadOrFail("hostname length of response", &namelength, 1);
+ uint8_t hostname[namelength + 2];
+ APT_ReadOrFail("hostname of response", hostname, sizeof(hostname));
+ portu.b[0] = hostname[namelength];
+ portu.b[1] = hostname[namelength + 1];
+ port = ntohs(*portu.i);
+ hostname[namelength] = '\0';
+ strprintf(bindaddr, "%s:%d", hostname, port);
+ }
+ else if (response[3] == 0x04) // IPv6 address
+ {
+ uint8_t ip6port[18];
+ APT_ReadOrFail("IPv6+port of response", ip6port, sizeof(ip6port));
+ portu.b[0] = ip6port[16];
+ portu.b[1] = ip6port[17];
+ port = ntohs(*portu.i);
+ strprintf(bindaddr, "[%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X]:%d",
+ ip6port[0], ip6port[1], ip6port[2], ip6port[3], ip6port[4], ip6port[5], ip6port[6], ip6port[7],
+ ip6port[8], ip6port[9], ip6port[10], ip6port[11], ip6port[12], ip6port[13], ip6port[14], ip6port[15],
+ port);
+ }
+ else
+ return _error->Error("SOCKS proxy %s destination address is of unknown type: %d",
+ ProxyInfo.c_str(), response[3]);
+ if (response[1] != 0x00)
+ {
+ char const * errstr;
+ switch (response[1])
+ {
+ case 0x01: errstr = "general SOCKS server failure"; Owner->SetFailReason("SOCKS"); break;
+ case 0x02: errstr = "connection not allowed by ruleset"; Owner->SetFailReason("SOCKS"); break;
+ case 0x03: errstr = "Network unreachable"; Owner->SetFailReason("ConnectionTimedOut"); break;
+ case 0x04: errstr = "Host unreachable"; Owner->SetFailReason("ConnectionTimedOut"); break;
+ case 0x05: errstr = "Connection refused"; Owner->SetFailReason("ConnectionRefused"); break;
+ case 0x06: errstr = "TTL expired"; Owner->SetFailReason("Timeout"); break;
+ case 0x07: errstr = "Command not supported"; Owner->SetFailReason("SOCKS"); break;
+ case 0x08: errstr = "Address type not supported"; Owner->SetFailReason("SOCKS"); break;
+ default: errstr = "Unknown error"; Owner->SetFailReason("SOCKS"); break;
+ }
+ return _error->Error("SOCKS proxy %s didn't grant the connect to %s due to: %s (%d)", ProxyInfo.c_str(), bindaddr.c_str(), errstr, response[1]);
+ }
+ else if (Owner->DebugEnabled())
+ ioprintf(std::clog, "http: SOCKS proxy %s connection established to %s\n", ProxyInfo.c_str(), bindaddr.c_str());
+
+ if (WaitFd(ServerFd, true, Timeout) == false)
+ return _error->Error("SOCKS proxy %s reported connection, but timed out", ProxyInfo.c_str());
+ #undef APT_ReadOrFail
+ #undef APT_WriteOrFail
}
- else if (Proxy.Access != "http")
- return _error->Error("Unsupported proxy configured: %s", URI::SiteOnly(Proxy).c_str());
else
{
- if (Proxy.Port != 0)
- Port = Proxy.Port;
- Host = Proxy.Host;
+ // Determine what host and port to use based on the proxy settings
+ int Port = 0;
+ string Host;
+ if (Proxy.empty() == true || Proxy.Host.empty() == true)
+ {
+ if (ServerName.Port != 0)
+ Port = ServerName.Port;
+ Host = ServerName.Host;
+ }
+ else if (Proxy.Access != "http")
+ return _error->Error("Unsupported proxy configured: %s", URI::SiteOnly(Proxy).c_str());
+ else
+ {
+ if (Proxy.Port != 0)
+ Port = Proxy.Port;
+ Host = Proxy.Host;
+ }
+ return Connect(Host,Port,"http",80,ServerFd,TimeOut,Owner);
}
-
- // Connect to the remote server
- if (Connect(Host,Port,"http",80,ServerFd,TimeOut,Owner) == false)
- return false;
-
return true;
}
/*}}}*/
@@ -707,7 +853,7 @@ void HttpMethod::SendReq(FetchItem *Itm)
but while its a must for all servers to accept absolute URIs,
it is assumed clients will sent an absolute path for non-proxies */
std::string requesturi;
- if (Server->Proxy.empty() == true || Server->Proxy.Host.empty())
+ if (Server->Proxy.Access != "http" || Server->Proxy.empty() == true || Server->Proxy.Host.empty())
requesturi = Uri.Path;
else
requesturi = Uri;
@@ -756,7 +902,8 @@ void HttpMethod::SendReq(FetchItem *Itm)
else if (Itm->LastModified != 0)
Req << "If-Modified-Since: " << TimeRFC1123(Itm->LastModified, false).c_str() << "\r\n";
- if (Server->Proxy.User.empty() == false || Server->Proxy.Password.empty() == false)
+ if (Server->Proxy.Access == "http" &&
+ (Server->Proxy.User.empty() == false || Server->Proxy.Password.empty() == false))
Req << "Proxy-Authorization: Basic "
<< Base64Encode(Server->Proxy.User + ":" + Server->Proxy.Password) << "\r\n";
diff --git a/methods/http.h b/methods/http.h
index 4d22c88f9..644e576f0 100644
--- a/methods/http.h
+++ b/methods/http.h
@@ -67,7 +67,7 @@ class CircleBuf
// Read data in
bool Read(int Fd);
- bool Read(std::string Data);
+ bool Read(std::string const &Data);
// Write data out
bool Write(int Fd);