зеркало из https://github.com/mozilla/pjs.git
Bug 572570 - make ssltunnel recognize WebSocket traffic via header inspection. r=ted
This commit is contained in:
Родитель
bac547a636
Коммит
33af957293
|
@ -60,7 +60,6 @@ _DEFAULT_WEB_SERVER = "127.0.0.1"
|
|||
_DEFAULT_HTTP_PORT = 8888
|
||||
_DEFAULT_SSL_PORT = 4443
|
||||
_DEFAULT_WEBSOCKET_PORT = 9988
|
||||
_DEFAULT_WEBSOCKET_PROXY_PORT = 7777
|
||||
|
||||
#expand _DIST_BIN = __XPC_BIN_PATH__
|
||||
#expand _IS_WIN32 = len("__WIN32__") != 0
|
||||
|
@ -155,7 +154,6 @@ class Automation(object):
|
|||
DEFAULT_HTTP_PORT = _DEFAULT_HTTP_PORT
|
||||
DEFAULT_SSL_PORT = _DEFAULT_SSL_PORT
|
||||
DEFAULT_WEBSOCKET_PORT = _DEFAULT_WEBSOCKET_PORT
|
||||
DEFAULT_WEBSOCKET_PROXY_PORT = _DEFAULT_WEBSOCKET_PROXY_PORT
|
||||
|
||||
def __init__(self):
|
||||
self.log = _log
|
||||
|
@ -165,13 +163,11 @@ class Automation(object):
|
|||
webServer = _DEFAULT_WEB_SERVER,
|
||||
httpPort = _DEFAULT_HTTP_PORT,
|
||||
sslPort = _DEFAULT_SSL_PORT,
|
||||
webSocketPort = _DEFAULT_WEBSOCKET_PORT,
|
||||
webSocketProxyPort = _DEFAULT_WEBSOCKET_PROXY_PORT):
|
||||
webSocketPort = _DEFAULT_WEBSOCKET_PORT):
|
||||
self.webServer = webServer
|
||||
self.httpPort = httpPort
|
||||
self.sslPort = sslPort
|
||||
self.webSocketPort = webSocketPort
|
||||
self.webSocketProxyPort = webSocketProxyPort
|
||||
|
||||
@property
|
||||
def __all__(self):
|
||||
|
@ -406,16 +402,13 @@ function FindProxyForURL(url, host)
|
|||
return 'DIRECT';
|
||||
if (isHttp)
|
||||
return 'PROXY %(remote)s:%(httpport)s';
|
||||
if (isHttps)
|
||||
if (isHttps || isWebSocket)
|
||||
return 'PROXY %(remote)s:%(sslport)s';
|
||||
if (isWebSocket)
|
||||
return 'PROXY %(remote)s:%(websocketproxyport)s';
|
||||
return 'DIRECT';
|
||||
}""" % { "origins": origins,
|
||||
"remote": self.webServer,
|
||||
"httpport":self.httpPort,
|
||||
"sslport": self.sslPort,
|
||||
"websocketproxyport": self.webSocketProxyPort }
|
||||
"sslport": self.sslPort }
|
||||
pacURL = "".join(pacURL.splitlines())
|
||||
|
||||
part += """
|
||||
|
@ -462,8 +455,7 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t
|
|||
sslTunnelConfig.write("httpproxy:1\n")
|
||||
sslTunnelConfig.write("certdbdir:%s\n" % certPath)
|
||||
sslTunnelConfig.write("forward:127.0.0.1:%s\n" % self.httpPort)
|
||||
sslTunnelConfig.write("proxy:%s:%s:%s\n" %
|
||||
(self.webSocketProxyPort, self.webServer, self.webSocketPort))
|
||||
sslTunnelConfig.write("websocketserver:%s:%s\n" % (self.webServer, self.webSocketPort))
|
||||
sslTunnelConfig.write("listen:*:%s:pgo server certificate\n" % self.sslPort)
|
||||
|
||||
# Configure automatic certificate and bind custom certificates, client authentication
|
||||
|
|
|
@ -267,7 +267,6 @@ See <http://mochikit.com/doc/html/MochiKit/Logging.html> for details on the logg
|
|||
options.httpPort = self._automation.DEFAULT_HTTP_PORT
|
||||
options.sslPort = self._automation.DEFAULT_SSL_PORT
|
||||
options.webSocketPort = self._automation.DEFAULT_WEBSOCKET_PORT
|
||||
options.webSocketProxyPort = self._automation.DEFAULT_WEBSOCKET_PROXY_PORT
|
||||
|
||||
if options.vmwareRecording:
|
||||
if not self._automation.IS_WIN32:
|
||||
|
@ -724,8 +723,7 @@ def main():
|
|||
automation.setServerInfo(options.webServer,
|
||||
options.httpPort,
|
||||
options.sslPort,
|
||||
options.webSocketPort,
|
||||
options.webSocketProxyPort)
|
||||
options.webSocketPort)
|
||||
sys.exit(mochitest.runTests(options))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -121,26 +121,29 @@ typedef struct {
|
|||
string cert_nickname;
|
||||
PLHashTable* host_cert_table;
|
||||
PLHashTable* host_clientauth_table;
|
||||
// If not empty, and this server is using HTTP CONNECT, connections
|
||||
// will be proxied to this address.
|
||||
PRNetAddr remote_addr;
|
||||
// True if no SSL should be used for this server's connections.
|
||||
bool http_proxy_only;
|
||||
// The original host in the Host: header for the initial connection is
|
||||
// stored here, for proxied connections.
|
||||
string original_host;
|
||||
} server_info_t;
|
||||
|
||||
typedef struct {
|
||||
PRFileDesc* client_sock;
|
||||
PRNetAddr client_addr;
|
||||
server_info_t* server_info;
|
||||
// the original host in the Host: header for this connection is
|
||||
// stored here, for proxied connections
|
||||
string original_host;
|
||||
// true if no SSL should be used for this connection
|
||||
bool http_proxy_only;
|
||||
// true if this connection is for a WebSocket
|
||||
bool iswebsocket;
|
||||
} connection_info_t;
|
||||
|
||||
typedef struct {
|
||||
string fullHost;
|
||||
bool matched;
|
||||
} server_match_t;
|
||||
|
||||
const PRInt32 BUF_SIZE = 16384;
|
||||
const PRInt32 BUF_MARGIN = 1024;
|
||||
const PRInt32 BUF_TOTAL = BUF_SIZE + BUF_MARGIN;
|
||||
const char HEADER_HOST[] = "Host:";
|
||||
|
||||
struct relayBuffer
|
||||
{
|
||||
|
@ -217,6 +220,7 @@ const PRUint32 DEFAULT_STACKSIZE = (512 * 1024);
|
|||
string nssconfigdir;
|
||||
vector<server_info_t> servers;
|
||||
PRNetAddr remote_addr;
|
||||
PRNetAddr websocket_server;
|
||||
PRThreadPool* threads = NULL;
|
||||
PRLock* shutdown_lock = NULL;
|
||||
PRCondVar* shutdown_condvar = NULL;
|
||||
|
@ -237,6 +241,14 @@ PR_CALLBACK PRIntn ClientAuthValueComparator(const void *v1, const void *v2)
|
|||
return -1;
|
||||
}
|
||||
|
||||
static PRIntn match_hostname(PLHashEntry *he, PRIntn index, void* arg)
|
||||
{
|
||||
server_match_t *match = (server_match_t*)arg;
|
||||
if (match->fullHost.find((char*)he->key) != string::npos)
|
||||
match->matched = true;
|
||||
return HT_ENUMERATE_NEXT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Signal the main thread that the application should shut down.
|
||||
*/
|
||||
|
@ -354,15 +366,15 @@ bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, string &cer
|
|||
* This function examines the buffer for a S5ec-WebSocket-Location: field,
|
||||
* and if it's present, it replaces the hostname in that field with the
|
||||
* value in the server's original_host field. This function works
|
||||
* in the reverse direction as AdjustHost(), replacing the real hostname
|
||||
* of a response with the potentially fake hostname that is expected
|
||||
* in the reverse direction as AdjustWebSocketHost(), replacing the real
|
||||
* hostname of a response with the potentially fake hostname that is expected
|
||||
* by the browser (e.g., mochi.test).
|
||||
*
|
||||
* @return true if the header was adjusted successfully, or not found, false
|
||||
* if the header is present but the url is not, which should indicate
|
||||
* that more data needs to be read from the socket
|
||||
*/
|
||||
bool AdjustWebSocketLocation(relayBuffer& buffer, server_info_t *si)
|
||||
bool AdjustWebSocketLocation(relayBuffer& buffer, connection_info_t *ci)
|
||||
{
|
||||
assert(buffer.margin());
|
||||
buffer.buffertail[1] = '\0';
|
||||
|
@ -382,16 +394,16 @@ bool AdjustWebSocketLocation(relayBuffer& buffer, server_info_t *si)
|
|||
char *crlf = strstr(wsloc, "\r\n");
|
||||
if (!crlf)
|
||||
return false;
|
||||
if (si->original_host.empty())
|
||||
if (ci->original_host.empty())
|
||||
return true;
|
||||
|
||||
int diff = si->original_host.length() - (wslocend-wsloc);
|
||||
int diff = ci->original_host.length() - (wslocend-wsloc);
|
||||
if (diff > 0)
|
||||
assert(size_t(diff) <= buffer.margin());
|
||||
memmove(wslocend + diff, wslocend, buffer.buffertail - wsloc - diff);
|
||||
buffer.buffertail += diff;
|
||||
|
||||
memcpy(wsloc, si->original_host.c_str(), si->original_host.length());
|
||||
memcpy(wsloc, ci->original_host.c_str(), ci->original_host.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -403,10 +415,13 @@ bool AdjustWebSocketLocation(relayBuffer& buffer, server_info_t *si)
|
|||
* replaced with the host that the destination server is actually running
|
||||
* on.
|
||||
*/
|
||||
bool AdjustHost(relayBuffer& buffer, server_info_t *si)
|
||||
bool AdjustWebSocketHost(relayBuffer& buffer, connection_info_t *ci)
|
||||
{
|
||||
if (!si->remote_addr.inet.port)
|
||||
return false;
|
||||
const char HEADER_UPGRADE[] = "Upgrade:";
|
||||
const char HEADER_HOST[] = "Host:";
|
||||
|
||||
PRNetAddr inet_addr = (websocket_server.inet.port ? websocket_server :
|
||||
remote_addr);
|
||||
|
||||
assert(buffer.margin());
|
||||
|
||||
|
@ -414,6 +429,16 @@ bool AdjustHost(relayBuffer& buffer, server_info_t *si)
|
|||
// space left because we preserve a margin.
|
||||
buffer.buffertail[1] = '\0';
|
||||
|
||||
// Verify this is a WebSocket header.
|
||||
char* h1 = strstr(buffer.bufferhead, HEADER_UPGRADE);
|
||||
if (!h1)
|
||||
return false;
|
||||
h1 += strlen(HEADER_UPGRADE);
|
||||
h1 += strspn(h1, " \t");
|
||||
char* h2 = strstr(h1, "WebSocket\r\n");
|
||||
if (!h2)
|
||||
return false;
|
||||
|
||||
char* host = strstr(buffer.bufferhead, HEADER_HOST);
|
||||
if (!host)
|
||||
return false;
|
||||
|
@ -427,12 +452,12 @@ bool AdjustHost(relayBuffer& buffer, server_info_t *si)
|
|||
|
||||
// Save the original host, so we can use it later on responses from the
|
||||
// server.
|
||||
si->original_host.assign(host, endhost-host);
|
||||
ci->original_host.assign(host, endhost-host);
|
||||
|
||||
char newhost[40];
|
||||
PR_NetAddrToString(&si->remote_addr, newhost, sizeof(newhost));
|
||||
PR_NetAddrToString(&inet_addr, newhost, sizeof(newhost));
|
||||
assert(strlen(newhost) < sizeof(newhost) - 7);
|
||||
sprintf(newhost, "%s:%d", newhost, PR_ntohs(si->remote_addr.inet.port));
|
||||
sprintf(newhost, "%s:%d", newhost, PR_ntohs(inet_addr.inet.port));
|
||||
|
||||
int diff = strlen(newhost) - (endhost-host);
|
||||
if (diff > 0)
|
||||
|
@ -532,7 +557,7 @@ void HandleConnection(void* data)
|
|||
|
||||
if (!do_http_proxy)
|
||||
{
|
||||
if (!ci->server_info->http_proxy_only &&
|
||||
if (!ci->http_proxy_only &&
|
||||
!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone))
|
||||
client_error = true;
|
||||
else if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
|
||||
|
@ -652,12 +677,33 @@ void HandleConnection(void* data)
|
|||
if (!connect_accepted && ReadConnectRequest(ci->server_info, buffers[s],
|
||||
&response, certificateToUse, &clientAuth, fullHost))
|
||||
{
|
||||
// Mark this as a proxy-only connection (no SSL) if the CONNECT
|
||||
// request didn't come for port 443 or from any of the server's
|
||||
// cert or clientauth hostnames.
|
||||
if (fullHost.find(":443") == string::npos)
|
||||
{
|
||||
server_match_t match;
|
||||
match.fullHost = fullHost;
|
||||
match.matched = false;
|
||||
PL_HashTableEnumerateEntries(ci->server_info->host_cert_table,
|
||||
match_hostname,
|
||||
&match);
|
||||
PL_HashTableEnumerateEntries(ci->server_info->host_clientauth_table,
|
||||
match_hostname,
|
||||
&match);
|
||||
ci->http_proxy_only = !match.matched;
|
||||
}
|
||||
else
|
||||
{
|
||||
ci->http_proxy_only = false;
|
||||
}
|
||||
|
||||
// Clean the request as it would be read
|
||||
buffers[s].bufferhead = buffers[s].buffertail = buffers[s].buffer;
|
||||
in_flags |= PR_POLL_WRITE;
|
||||
connect_accepted = true;
|
||||
|
||||
// Store response to the oposite buffer
|
||||
// Store response to the opposite buffer
|
||||
if (response != 200)
|
||||
{
|
||||
printf(" could not read the connect request, closing connection with %d", response);
|
||||
|
@ -670,16 +716,6 @@ void HandleConnection(void* data)
|
|||
strcpy(buffers[s2].buffer, "HTTP/1.1 200 Connected\r\nConnection: keep-alive\r\n\r\n");
|
||||
buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer);
|
||||
|
||||
PRNetAddr* addr = &remote_addr;
|
||||
if (ci->server_info->remote_addr.inet.port > 0)
|
||||
addr = &ci->server_info->remote_addr;
|
||||
if (!ConnectSocket(other_sock, addr, connect_timeout))
|
||||
{
|
||||
printf(" could not open connection to the real server\n");
|
||||
client_error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
printf(" accepted CONNECT request, connected to the server, sending OK to the client\n");
|
||||
// Send the response to the client socket
|
||||
break;
|
||||
|
@ -696,14 +732,31 @@ void HandleConnection(void* data)
|
|||
{
|
||||
if (s == 0 && expect_request_start)
|
||||
{
|
||||
if (ci->server_info->http_proxy_only)
|
||||
expect_request_start = !AdjustHost(buffers[s], ci->server_info);
|
||||
if (!strstr(buffers[s].bufferhead, "\r\n\r\n"))
|
||||
{
|
||||
// We haven't received the complete header yet, so wait.
|
||||
continue;
|
||||
}
|
||||
else
|
||||
expect_request_start = !AdjustRequestURI(buffers[s], &fullHost);
|
||||
{
|
||||
ci->iswebsocket = AdjustWebSocketHost(buffers[s], ci);
|
||||
expect_request_start = !(ci->iswebsocket ||
|
||||
AdjustRequestURI(buffers[s], &fullHost));
|
||||
PRNetAddr* addr = &remote_addr;
|
||||
if (ci->iswebsocket && websocket_server.inet.port)
|
||||
addr = &websocket_server;
|
||||
if (!ConnectSocket(other_sock, addr, connect_timeout))
|
||||
{
|
||||
printf(" could not open connection to the real server\n");
|
||||
client_error = true;
|
||||
break;
|
||||
}
|
||||
printf("\n connected to remote server\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (s == 1 && ci->iswebsocket)
|
||||
{
|
||||
if (!AdjustWebSocketLocation(buffers[s], ci->server_info))
|
||||
if (!AdjustWebSocketLocation(buffers[s], ci))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -717,7 +770,7 @@ void HandleConnection(void* data)
|
|||
|
||||
if (out_flags & PR_POLL_WRITE)
|
||||
{
|
||||
printf(" :writting");
|
||||
printf(" :writing");
|
||||
PRInt32 bytesWrite = PR_Send(sockets[s].fd, buffers[s2].bufferhead,
|
||||
buffers[s2].present(), 0, PR_INTERVAL_NO_TIMEOUT);
|
||||
|
||||
|
@ -753,7 +806,7 @@ void HandleConnection(void* data)
|
|||
printf(" proxy response sent to the client");
|
||||
// Proxy response has just been writen, update to ssl
|
||||
ssl_updated = true;
|
||||
if (!ci->server_info->http_proxy_only &&
|
||||
if (!ci->http_proxy_only &&
|
||||
!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, clientAuth))
|
||||
{
|
||||
printf(" but failed to config server socket\n");
|
||||
|
@ -892,6 +945,23 @@ int processConfigLine(char* configLine)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(keyword, "websocketserver"))
|
||||
{
|
||||
char* ipstring = strtok2(_caret, ":", &_caret);
|
||||
if (PR_StringToNetAddr(ipstring, &websocket_server) != PR_SUCCESS) {
|
||||
fprintf(stderr, "Invalid IP address in proxy config: %s\n", ipstring);
|
||||
return 1;
|
||||
}
|
||||
char* remoteport = strtok2(_caret, ":", &_caret);
|
||||
int port = atoi(remoteport);
|
||||
if (port <= 0) {
|
||||
fprintf(stderr, "Invalid remote port in proxy config: %s\n", remoteport);
|
||||
return 1;
|
||||
}
|
||||
websocket_server.inet.port = PR_htons(port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Configure the forward address of the target server
|
||||
if (!strcmp(keyword, "forward"))
|
||||
{
|
||||
|
@ -911,35 +981,6 @@ int processConfigLine(char* configLine)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(keyword, "proxy"))
|
||||
{
|
||||
server_info_t server;
|
||||
server.host_cert_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, PL_CompareStrings, NULL, NULL);
|
||||
server.host_clientauth_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, ClientAuthValueComparator, NULL, NULL);
|
||||
server.http_proxy_only = true;
|
||||
|
||||
char* listenport = strtok2(_caret, ":", &_caret);
|
||||
server.listen_port = atoi(listenport);
|
||||
if (server.listen_port <= 0) {
|
||||
fprintf(stderr, "Invalid listen port in proxy config: %s\n", listenport);
|
||||
return 1;
|
||||
}
|
||||
char* ipstring = strtok2(_caret, ":", &_caret);
|
||||
if (PR_StringToNetAddr(ipstring, &server.remote_addr) != PR_SUCCESS) {
|
||||
fprintf(stderr, "Invalid IP address in proxy config: %s\n", ipstring);
|
||||
return 1;
|
||||
}
|
||||
char* remoteport = strtok2(_caret, ":", &_caret);
|
||||
int port = atoi(remoteport);
|
||||
if (port <= 0) {
|
||||
fprintf(stderr, "Invalid remote port in proxy config: %s\n", remoteport);
|
||||
return 1;
|
||||
}
|
||||
server.remote_addr.inet.port = PR_htons(port);
|
||||
servers.push_back(server);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Configure all listen sockets and port+certificate bindings
|
||||
if (!strcmp(keyword, "listen"))
|
||||
{
|
||||
|
@ -979,10 +1020,8 @@ int processConfigLine(char* configLine)
|
|||
else
|
||||
{
|
||||
server_info_t server;
|
||||
memset(&server.remote_addr, 0, sizeof(PRNetAddr));
|
||||
server.cert_nickname = certnick;
|
||||
server.listen_port = port;
|
||||
server.http_proxy_only = false;
|
||||
server.host_cert_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, PL_CompareStrings, NULL, NULL);
|
||||
if (!server.host_cert_table)
|
||||
{
|
||||
|
@ -1136,6 +1175,8 @@ int main(int argc, char** argv)
|
|||
else
|
||||
configFilePath = argv[1];
|
||||
|
||||
memset(&websocket_server, 0, sizeof(PRNetAddr));
|
||||
|
||||
if (parseConfigFile(configFilePath)) {
|
||||
fprintf(stderr, "Error: config file \"%s\" missing or formating incorrect\n"
|
||||
"Specify path to the config file as parameter to ssltunnel or \n"
|
||||
|
@ -1163,10 +1204,9 @@ int main(int argc, char** argv)
|
|||
" # specified. You also have to specify the tunnel listen port.\n"
|
||||
" clientauth:requesting-client-cert.host.com:443:4443:request\n"
|
||||
" clientauth:requiring-client-cert.host.com:443:4443:require\n"
|
||||
" # Act as a simple proxy for incoming connections on port 7777,\n"
|
||||
" # tunneling them to the server at 127.0.0.1:9999. Not affected\n"
|
||||
" # by the 'forward' option.\n"
|
||||
" proxy:7777:127.0.0.1:9999\n",
|
||||
" # Proxy WebSocket traffic to the server at 127.0.0.1:9999,\n"
|
||||
" # instead of the server specified in the 'forward' option.\n"
|
||||
" websocketserver:127.0.0.1:9999\n",
|
||||
configFilePath);
|
||||
return 1;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче