Bug 1877642 - Disable browser_fullscreen-tab-close-race.js on apple_silicon !debug...
[gecko.git] / testing / mochitest / ssltunnel / ssltunnel.cpp
blob4e61b96d3e552ed8d3c59d7311e71553e15d33df
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /*
7 * WARNING: DO NOT USE THIS CODE IN PRODUCTION SYSTEMS. It is highly likely to
8 * be plagued with the usual problems endemic to C (buffer overflows
9 * and the like). We don't especially care here (but would accept
10 * patches!) because this is only intended for use in our test
11 * harnesses in controlled situations where input is guaranteed not to
12 * be malicious.
15 #include "ScopedNSSTypes.h"
16 #include <assert.h>
17 #include <stdio.h>
18 #include <string>
19 #include <vector>
20 #include <algorithm>
21 #include <stdarg.h>
22 #include "prinit.h"
23 #include "prerror.h"
24 #include "prenv.h"
25 #include "prnetdb.h"
26 #include "prtpool.h"
27 #include "nsAlgorithm.h"
28 #include "nss.h"
29 #include "keyhi.h"
30 #include "ssl.h"
31 #include "sslproto.h"
32 #include "plhash.h"
33 #include "mozilla/Sprintf.h"
34 #include "mozilla/Unused.h"
36 using namespace mozilla;
37 using namespace mozilla::psm;
38 using std::string;
39 using std::vector;
41 #define IS_DELIM(m, c) ((m)[(c) >> 3] & (1 << ((c) & 7)))
42 #define SET_DELIM(m, c) ((m)[(c) >> 3] |= (1 << ((c) & 7)))
43 #define DELIM_TABLE_SIZE 32
45 // You can set the level of logging by env var SSLTUNNEL_LOG_LEVEL=n, where n
46 // is 0 through 3. The default is 1, INFO level logging.
47 enum LogLevel {
48 LEVEL_DEBUG = 0,
49 LEVEL_INFO = 1,
50 LEVEL_ERROR = 2,
51 LEVEL_SILENT = 3
52 } gLogLevel,
53 gLastLogLevel;
55 #define _LOG_OUTPUT(level, func, params) \
56 PR_BEGIN_MACRO \
57 if (level >= gLogLevel) { \
58 gLastLogLevel = level; \
59 func params; \
60 } \
61 PR_END_MACRO
63 // The most verbose output
64 #define LOG_DEBUG(params) _LOG_OUTPUT(LEVEL_DEBUG, printf, params)
66 // Top level informative messages
67 #define LOG_INFO(params) _LOG_OUTPUT(LEVEL_INFO, printf, params)
69 // Serious errors that must be logged always until completely gag
70 #define LOG_ERROR(params) _LOG_OUTPUT(LEVEL_ERROR, eprintf, params)
72 // Same as LOG_ERROR, but when logging is set to LEVEL_DEBUG, the message
73 // will be put to the stdout instead of stderr to keep continuity with other
74 // LOG_DEBUG message output
75 #define LOG_ERRORD(params) \
76 PR_BEGIN_MACRO \
77 if (gLogLevel == LEVEL_DEBUG) \
78 _LOG_OUTPUT(LEVEL_ERROR, printf, params); \
79 else \
80 _LOG_OUTPUT(LEVEL_ERROR, eprintf, params); \
81 PR_END_MACRO
83 // If there is any output written between LOG_BEGIN_BLOCK() and
84 // LOG_END_BLOCK() then a new line will be put to the proper output (out/err)
85 #define LOG_BEGIN_BLOCK() gLastLogLevel = LEVEL_SILENT;
87 #define LOG_END_BLOCK() \
88 PR_BEGIN_MACRO \
89 if (gLastLogLevel == LEVEL_ERROR) LOG_ERROR(("\n")); \
90 if (gLastLogLevel < LEVEL_ERROR) _LOG_OUTPUT(gLastLogLevel, printf, ("\n")); \
91 PR_END_MACRO
93 int eprintf(const char* str, ...) {
94 va_list ap;
95 va_start(ap, str);
96 int result = vfprintf(stderr, str, ap);
97 va_end(ap);
98 return result;
101 // Copied from nsCRT
102 char* strtok2(char* string, const char* delims, char** newStr) {
103 PR_ASSERT(string);
105 char delimTable[DELIM_TABLE_SIZE];
106 uint32_t i;
107 char* result;
108 char* str = string;
110 for (i = 0; i < DELIM_TABLE_SIZE; i++) delimTable[i] = '\0';
112 for (i = 0; delims[i]; i++) {
113 SET_DELIM(delimTable, static_cast<uint8_t>(delims[i]));
116 // skip to beginning
117 while (*str && IS_DELIM(delimTable, static_cast<uint8_t>(*str))) {
118 str++;
120 result = str;
122 // fix up the end of the token
123 while (*str) {
124 if (IS_DELIM(delimTable, static_cast<uint8_t>(*str))) {
125 *str++ = '\0';
126 break;
128 str++;
130 *newStr = str;
132 return str == result ? nullptr : result;
135 enum client_auth_option { caNone = 0, caRequire = 1, caRequest = 2 };
137 // Structs for passing data into jobs on the thread pool
138 struct server_info_t {
139 int32_t listen_port;
140 string cert_nickname;
141 PLHashTable* host_cert_table;
142 PLHashTable* host_clientauth_table;
143 PLHashTable* host_redir_table;
144 PLHashTable* host_ssl3_table;
145 PLHashTable* host_tls1_table;
146 PLHashTable* host_tls11_table;
147 PLHashTable* host_tls12_table;
148 PLHashTable* host_tls13_table;
149 PLHashTable* host_3des_table;
150 PLHashTable* host_failhandshake_table;
153 struct connection_info_t {
154 PRFileDesc* client_sock;
155 PRNetAddr client_addr;
156 server_info_t* server_info;
157 // the original host in the Host: header for this connection is
158 // stored here, for proxied connections
159 string original_host;
160 // true if no SSL should be used for this connection
161 bool http_proxy_only;
162 // true if this connection is for a WebSocket
163 bool iswebsocket;
166 struct server_match_t {
167 string fullHost;
168 bool matched;
171 const int32_t BUF_SIZE = 16384;
172 const int32_t BUF_MARGIN = 1024;
173 const int32_t BUF_TOTAL = BUF_SIZE + BUF_MARGIN;
175 struct relayBuffer {
176 char *buffer, *bufferhead, *buffertail, *bufferend;
178 relayBuffer() {
179 // Leave 1024 bytes more for request line manipulations
180 bufferhead = buffertail = buffer = new char[BUF_TOTAL];
181 bufferend = buffer + BUF_SIZE;
184 ~relayBuffer() { delete[] buffer; }
186 void compact() {
187 if (buffertail == bufferhead) buffertail = bufferhead = buffer;
190 bool empty() { return bufferhead == buffertail; }
191 size_t areafree() { return bufferend - buffertail; }
192 size_t margin() { return areafree() + BUF_MARGIN; }
193 size_t present() { return buffertail - bufferhead; }
196 // These numbers are multiplied by the number of listening ports (actual
197 // servers running). According the thread pool implementation there is no
198 // need to limit the number of threads initially, threads are allocated
199 // dynamically and stored in a linked list. Initial number of 2 is chosen
200 // to allocate a thread for socket accept and preallocate one for the first
201 // connection that is with high probability expected to come.
202 const uint32_t INITIAL_THREADS = 2;
203 const uint32_t MAX_THREADS = 100;
204 const uint32_t DEFAULT_STACKSIZE = (512 * 1024);
206 // global data
207 string nssconfigdir;
208 vector<server_info_t> servers;
209 PRNetAddr remote_addr;
210 PRNetAddr websocket_server;
211 PRThreadPool* threads = nullptr;
212 PRLock* shutdown_lock = nullptr;
213 PRCondVar* shutdown_condvar = nullptr;
214 // Not really used, unless something fails to start
215 bool shutdown_server = false;
216 bool do_http_proxy = false;
217 bool any_host_spec_config = false;
218 bool listen_public = false;
220 int ClientAuthValueComparator(const void* v1, const void* v2) {
221 int a = *static_cast<const client_auth_option*>(v1) -
222 *static_cast<const client_auth_option*>(v2);
223 if (a == 0) return 0;
224 if (a > 0) return 1;
225 // (a < 0)
226 return -1;
229 static int match_hostname(PLHashEntry* he, int index, void* arg) {
230 server_match_t* match = (server_match_t*)arg;
231 if (match->fullHost.find((char*)he->key) != string::npos)
232 match->matched = true;
233 return HT_ENUMERATE_NEXT;
237 * Signal the main thread that the application should shut down.
239 void SignalShutdown() {
240 PR_Lock(shutdown_lock);
241 PR_NotifyCondVar(shutdown_condvar);
242 PR_Unlock(shutdown_lock);
245 // available flags
246 enum {
247 USE_SSL3 = 1 << 0,
248 USE_3DES = 1 << 1,
249 FAIL_HANDSHAKE = 1 << 2,
250 USE_TLS1 = 1 << 3,
251 USE_TLS1_1 = 1 << 4,
252 USE_TLS1_2 = 1 << 5,
253 USE_TLS1_3 = 1 << 6
256 bool ReadConnectRequest(server_info_t* server_info, relayBuffer& buffer,
257 int32_t* result, string& certificate,
258 client_auth_option* clientauth, string& host,
259 string& location, int32_t* flags) {
260 if (buffer.present() < 4) {
261 LOG_DEBUG(
262 (" !! only %d bytes present in the buffer", (int)buffer.present()));
263 return false;
265 if (strncmp(buffer.buffertail - 4, "\r\n\r\n", 4)) {
266 LOG_ERRORD((" !! request is not tailed with CRLFCRLF but with %x %x %x %x",
267 *(buffer.buffertail - 4), *(buffer.buffertail - 3),
268 *(buffer.buffertail - 2), *(buffer.buffertail - 1)));
269 return false;
272 LOG_DEBUG((" parsing initial connect request, dump:\n%.*s\n",
273 (int)buffer.present(), buffer.bufferhead));
275 *result = 400;
277 char* token;
278 char* _caret;
279 token = strtok2(buffer.bufferhead, " ", &_caret);
280 if (!token) {
281 LOG_ERRORD((" no space found"));
282 return true;
284 if (strcmp(token, "CONNECT")) {
285 LOG_ERRORD((" not CONNECT request but %s", token));
286 return true;
289 token = strtok2(_caret, " ", &_caret);
290 void* c = PL_HashTableLookup(server_info->host_cert_table, token);
291 if (c) certificate = static_cast<char*>(c);
293 host = "https://";
294 host += token;
296 c = PL_HashTableLookup(server_info->host_clientauth_table, token);
297 if (c)
298 *clientauth = *static_cast<client_auth_option*>(c);
299 else
300 *clientauth = caNone;
302 void* redir = PL_HashTableLookup(server_info->host_redir_table, token);
303 if (redir) location = static_cast<char*>(redir);
305 if (PL_HashTableLookup(server_info->host_ssl3_table, token)) {
306 *flags |= USE_SSL3;
309 if (PL_HashTableLookup(server_info->host_3des_table, token)) {
310 *flags |= USE_3DES;
313 if (PL_HashTableLookup(server_info->host_tls1_table, token)) {
314 *flags |= USE_TLS1;
317 if (PL_HashTableLookup(server_info->host_tls11_table, token)) {
318 *flags |= USE_TLS1_1;
321 if (PL_HashTableLookup(server_info->host_tls12_table, token)) {
322 *flags |= USE_TLS1_2;
325 if (PL_HashTableLookup(server_info->host_tls13_table, token)) {
326 *flags |= USE_TLS1_3;
329 if (PL_HashTableLookup(server_info->host_failhandshake_table, token)) {
330 *flags |= FAIL_HANDSHAKE;
333 token = strtok2(_caret, "/", &_caret);
334 if (strcmp(token, "HTTP")) {
335 LOG_ERRORD((" not tailed with HTTP but with %s", token));
336 return true;
339 *result = (redir) ? 302 : 200;
340 return true;
343 bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si,
344 const string& certificate,
345 const client_auth_option clientAuth,
346 int32_t flags) {
347 const char* certnick =
348 certificate.empty() ? si->cert_nickname.c_str() : certificate.c_str();
350 UniqueCERTCertificate cert(PK11_FindCertFromNickname(certnick, nullptr));
351 if (!cert) {
352 LOG_ERROR(("Failed to find cert %s\n", certnick));
353 return false;
356 UniqueSECKEYPrivateKey privKey(PK11_FindKeyByAnyCert(cert.get(), nullptr));
357 if (!privKey) {
358 LOG_ERROR(("Failed to find private key\n"));
359 return false;
362 PRFileDesc* ssl_socket = SSL_ImportFD(nullptr, socket);
363 if (!ssl_socket) {
364 LOG_ERROR(("Error importing SSL socket\n"));
365 return false;
368 if (flags & FAIL_HANDSHAKE) {
369 // deliberately cause handshake to fail by sending the client a client hello
370 SSL_ResetHandshake(ssl_socket, false);
371 return true;
374 SSLKEAType certKEA = NSS_FindCertKEAType(cert.get());
375 if (SSL_ConfigSecureServer(ssl_socket, cert.get(), privKey.get(), certKEA) !=
376 SECSuccess) {
377 LOG_ERROR(("Error configuring SSL server socket\n"));
378 return false;
381 SSL_OptionSet(ssl_socket, SSL_SECURITY, true);
382 SSL_OptionSet(ssl_socket, SSL_HANDSHAKE_AS_CLIENT, false);
383 SSL_OptionSet(ssl_socket, SSL_HANDSHAKE_AS_SERVER, true);
384 SSL_OptionSet(ssl_socket, SSL_ENABLE_SESSION_TICKETS, true);
386 if (clientAuth != caNone) {
387 // If we're requesting or requiring a client certificate, we should
388 // configure NSS to include the "certificate_authorities" field in the
389 // certificate request message. That way we can test that gecko properly
390 // takes note of it.
391 UniqueCERTCertificate issuer(
392 CERT_FindCertIssuer(cert.get(), PR_Now(), certUsageAnyCA));
393 if (!issuer) {
394 LOG_DEBUG(("Failed to find issuer for %s\n", certnick));
395 return false;
397 UniqueCERTCertList issuerList(CERT_NewCertList());
398 if (!issuerList) {
399 LOG_ERROR(("Failed to allocate new CERTCertList\n"));
400 return false;
402 if (CERT_AddCertToListTail(issuerList.get(), issuer.get()) != SECSuccess) {
403 LOG_ERROR(("Failed to add issuer to issuerList\n"));
404 return false;
406 Unused << issuer.release(); // Ownership transferred to issuerList.
407 if (SSL_SetTrustAnchors(ssl_socket, issuerList.get()) != SECSuccess) {
408 LOG_ERROR(
409 ("Failed to set certificate_authorities list for client "
410 "authentication\n"));
411 return false;
413 SSL_OptionSet(ssl_socket, SSL_REQUEST_CERTIFICATE, true);
414 SSL_OptionSet(ssl_socket, SSL_REQUIRE_CERTIFICATE, clientAuth == caRequire);
417 SSLVersionRange range = {SSL_LIBRARY_VERSION_TLS_1_3,
418 SSL_LIBRARY_VERSION_3_0};
419 if (flags & USE_SSL3) {
420 range.min = PR_MIN(range.min, SSL_LIBRARY_VERSION_3_0);
421 range.max = PR_MAX(range.max, SSL_LIBRARY_VERSION_3_0);
423 if (flags & USE_TLS1) {
424 range.min = PR_MIN(range.min, SSL_LIBRARY_VERSION_TLS_1_0);
425 range.max = PR_MAX(range.max, SSL_LIBRARY_VERSION_TLS_1_0);
427 if (flags & USE_TLS1_1) {
428 range.min = PR_MIN(range.min, SSL_LIBRARY_VERSION_TLS_1_1);
429 range.max = PR_MAX(range.max, SSL_LIBRARY_VERSION_TLS_1_1);
431 if (flags & USE_TLS1_2) {
432 range.min = PR_MIN(range.min, SSL_LIBRARY_VERSION_TLS_1_2);
433 range.max = PR_MAX(range.max, SSL_LIBRARY_VERSION_TLS_1_2);
435 if (flags & USE_TLS1_3) {
436 range.min = PR_MIN(range.min, SSL_LIBRARY_VERSION_TLS_1_3);
437 range.max = PR_MAX(range.max, SSL_LIBRARY_VERSION_TLS_1_3);
439 // Set the valid range, if any were specified (if not, skip
440 // when the default range is invalid, i.e. max > min)
441 if (range.min <= range.max &&
442 SSL_VersionRangeSet(ssl_socket, &range) != SECSuccess) {
443 LOG_ERROR(("Error configuring SSL socket version range\n"));
444 return false;
447 if (flags & USE_3DES) {
448 for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) {
449 uint16_t cipher_id = SSL_ImplementedCiphers[i];
450 if (cipher_id == TLS_RSA_WITH_3DES_EDE_CBC_SHA) {
451 SSL_CipherPrefSet(ssl_socket, cipher_id, true);
452 } else {
453 SSL_CipherPrefSet(ssl_socket, cipher_id, false);
458 SSL_ResetHandshake(ssl_socket, true);
460 return true;
464 * This function examines the buffer for a Sec-WebSocket-Location: field,
465 * and if it's present, it replaces the hostname in that field with the
466 * value in the server's original_host field. This function works
467 * in the reverse direction as AdjustWebSocketHost(), replacing the real
468 * hostname of a response with the potentially fake hostname that is expected
469 * by the browser (e.g., mochi.test).
471 * @return true if the header was adjusted successfully, or not found, false
472 * if the header is present but the url is not, which should indicate
473 * that more data needs to be read from the socket
475 bool AdjustWebSocketLocation(relayBuffer& buffer, connection_info_t* ci) {
476 assert(buffer.margin());
477 buffer.buffertail[1] = '\0';
479 char* wsloc_header = strstr(buffer.bufferhead, "Sec-WebSocket-Location:");
480 if (!wsloc_header) {
481 return true;
483 // advance pointer to the start of the hostname
484 char* wsloc = strstr(wsloc_header, "ws://");
485 if (!wsloc) {
486 wsloc = strstr(wsloc_header, "wss://");
488 if (!wsloc) return false;
489 wsloc += 5;
490 // find the end of the hostname
491 char* wslocend = strchr(wsloc + 1, '/');
492 if (!wslocend) return false;
493 char* crlf = strstr(wsloc, "\r\n");
494 if (!crlf) return false;
495 if (ci->original_host.empty()) return true;
497 int diff = ci->original_host.length() - (wslocend - wsloc);
498 if (diff > 0) assert(size_t(diff) <= buffer.margin());
499 memmove(wslocend + diff, wslocend, buffer.buffertail - wsloc - diff);
500 buffer.buffertail += diff;
502 memcpy(wsloc, ci->original_host.c_str(), ci->original_host.length());
503 return true;
507 * This function examines the buffer for a Host: field, and if it's present,
508 * it replaces the hostname in that field with the hostname in the server's
509 * remote_addr field. This is needed because proxy requests may be coming
510 * from mochitest with fake hosts, like mochi.test, and these need to be
511 * replaced with the host that the destination server is actually running
512 * on.
514 bool AdjustWebSocketHost(relayBuffer& buffer, connection_info_t* ci) {
515 const char HEADER_UPGRADE[] = "Upgrade:";
516 const char HEADER_HOST[] = "Host:";
518 PRNetAddr inet_addr =
519 (websocket_server.inet.port ? websocket_server : remote_addr);
521 assert(buffer.margin());
523 // Cannot use strnchr so add a null char at the end. There is always some
524 // space left because we preserve a margin.
525 buffer.buffertail[1] = '\0';
527 // Verify this is a WebSocket header.
528 char* h1 = strstr(buffer.bufferhead, HEADER_UPGRADE);
529 if (!h1) return false;
530 h1 += strlen(HEADER_UPGRADE);
531 h1 += strspn(h1, " \t");
532 char* h2 = strstr(h1, "WebSocket\r\n");
533 if (!h2) h2 = strstr(h1, "websocket\r\n");
534 if (!h2) h2 = strstr(h1, "Websocket\r\n");
535 if (!h2) return false;
537 char* host = strstr(buffer.bufferhead, HEADER_HOST);
538 if (!host) return false;
539 // advance pointer to beginning of hostname
540 host += strlen(HEADER_HOST);
541 host += strspn(host, " \t");
543 char* endhost = strstr(host, "\r\n");
544 if (!endhost) return false;
546 // Save the original host, so we can use it later on responses from the
547 // server.
548 ci->original_host.assign(host, endhost - host);
550 char newhost[40];
551 PR_NetAddrToString(&inet_addr, newhost, sizeof(newhost));
552 assert(strlen(newhost) < sizeof(newhost) - 7);
553 SprintfLiteral(newhost, "%s:%d", newhost, PR_ntohs(inet_addr.inet.port));
555 int diff = strlen(newhost) - (endhost - host);
556 if (diff > 0) assert(size_t(diff) <= buffer.margin());
557 memmove(endhost + diff, endhost, buffer.buffertail - host - diff);
558 buffer.buffertail += diff;
560 memcpy(host, newhost, strlen(newhost));
561 return true;
565 * This function prefixes Request-URI path with a full scheme-host-port
566 * string.
568 bool AdjustRequestURI(relayBuffer& buffer, string* host) {
569 assert(buffer.margin());
571 // Cannot use strnchr so add a null char at the end. There is always some
572 // space left because we preserve a margin.
573 buffer.buffertail[1] = '\0';
574 LOG_DEBUG((" incoming request to adjust:\n%s\n", buffer.bufferhead));
576 char *token, *path;
577 path = strchr(buffer.bufferhead, ' ') + 1;
578 if (!path) return false;
580 // If the path doesn't start with a slash don't change it, it is probably '*'
581 // or a full path already. Return true, we are done with this request
582 // adjustment.
583 if (*path != '/') return true;
585 token = strchr(path, ' ') + 1;
586 if (!token) return false;
588 if (strncmp(token, "HTTP/", 5)) return false;
590 size_t hostlength = host->length();
591 assert(hostlength <= buffer.margin());
593 memmove(path + hostlength, path, buffer.buffertail - path);
594 memcpy(path, host->c_str(), hostlength);
595 buffer.buffertail += hostlength;
597 return true;
600 bool ConnectSocket(UniquePRFileDesc& fd, const PRNetAddr* addr,
601 PRIntervalTime timeout) {
602 PRStatus stat = PR_Connect(fd.get(), addr, timeout);
603 if (stat != PR_SUCCESS) return false;
605 PRSocketOptionData option;
606 option.option = PR_SockOpt_Nonblocking;
607 option.value.non_blocking = true;
608 PR_SetSocketOption(fd.get(), &option);
610 return true;
614 * Handle an incoming client connection. The server thread has already
615 * accepted the connection, so we just need to connect to the remote
616 * port and then proxy data back and forth.
617 * The data parameter is a connection_info_t*, and must be deleted
618 * by this function.
620 void HandleConnection(void* data) {
621 connection_info_t* ci = static_cast<connection_info_t*>(data);
622 PRIntervalTime connect_timeout = PR_SecondsToInterval(30);
624 UniquePRFileDesc other_sock(PR_NewTCPSocket());
625 bool client_done = false;
626 bool client_error = false;
627 bool connect_accepted = !do_http_proxy;
628 bool ssl_updated = !do_http_proxy;
629 bool expect_request_start = do_http_proxy;
630 string certificateToUse;
631 string locationHeader;
632 client_auth_option clientAuth;
633 string fullHost;
634 int32_t flags = 0;
636 LOG_DEBUG(("SSLTUNNEL(%p)): incoming connection csock(0)=%p, ssock(1)=%p\n",
637 static_cast<void*>(data), static_cast<void*>(ci->client_sock),
638 static_cast<void*>(other_sock.get())));
639 if (other_sock) {
640 int32_t numberOfSockets = 1;
642 relayBuffer buffers[2];
644 if (!do_http_proxy) {
645 if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info,
646 certificateToUse, caNone, flags))
647 client_error = true;
648 else if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
649 client_error = true;
650 else
651 numberOfSockets = 2;
654 PRPollDesc sockets[2] = {{ci->client_sock, PR_POLL_READ, 0},
655 {other_sock.get(), PR_POLL_READ, 0}};
656 bool socketErrorState[2] = {false, false};
658 while (!((client_error || client_done) && buffers[0].empty() &&
659 buffers[1].empty())) {
660 sockets[0].in_flags |= PR_POLL_EXCEPT;
661 sockets[1].in_flags |= PR_POLL_EXCEPT;
662 LOG_DEBUG(("SSLTUNNEL(%p)): polling flags csock(0)=%c%c, ssock(1)=%c%c\n",
663 static_cast<void*>(data),
664 sockets[0].in_flags & PR_POLL_READ ? 'R' : '-',
665 sockets[0].in_flags & PR_POLL_WRITE ? 'W' : '-',
666 sockets[1].in_flags & PR_POLL_READ ? 'R' : '-',
667 sockets[1].in_flags & PR_POLL_WRITE ? 'W' : '-'));
668 int32_t pollStatus =
669 PR_Poll(sockets, numberOfSockets, PR_MillisecondsToInterval(1000));
670 if (pollStatus < 0) {
671 LOG_DEBUG(("SSLTUNNEL(%p)): pollStatus=%d, exiting\n",
672 static_cast<void*>(data), pollStatus));
673 client_error = true;
674 break;
677 if (pollStatus == 0) {
678 // timeout
679 LOG_DEBUG(("SSLTUNNEL(%p)): poll timeout, looping\n",
680 static_cast<void*>(data)));
681 continue;
684 for (int32_t s = 0; s < numberOfSockets; ++s) {
685 int32_t s2 = s == 1 ? 0 : 1;
686 int16_t out_flags = sockets[s].out_flags;
687 int16_t& in_flags = sockets[s].in_flags;
688 int16_t& in_flags2 = sockets[s2].in_flags;
689 sockets[s].out_flags = 0;
691 LOG_BEGIN_BLOCK();
692 LOG_DEBUG(("SSLTUNNEL(%p)): %csock(%d)=%p out_flags=%d",
693 static_cast<void*>(data), s == 0 ? 'c' : 's', s,
694 static_cast<void*>(sockets[s].fd), out_flags));
695 if (out_flags & (PR_POLL_EXCEPT | PR_POLL_ERR | PR_POLL_HUP)) {
696 LOG_DEBUG((" :exception\n"));
697 client_error = true;
698 socketErrorState[s] = true;
699 // We got a fatal error state on the socket. Clear the output buffer
700 // for this socket to break the main loop, we will never more be able
701 // to send those data anyway.
702 buffers[s2].bufferhead = buffers[s2].buffertail = buffers[s2].buffer;
703 continue;
704 } // PR_POLL_EXCEPT, PR_POLL_ERR, PR_POLL_HUP handling
706 if (out_flags & PR_POLL_READ && !buffers[s].areafree()) {
707 LOG_DEBUG(
708 (" no place in read buffer but got read flag, dropping it now!"));
709 in_flags &= ~PR_POLL_READ;
712 if (out_flags & PR_POLL_READ && buffers[s].areafree()) {
713 LOG_DEBUG((" :reading"));
714 int32_t bytesRead =
715 PR_Recv(sockets[s].fd, buffers[s].buffertail,
716 buffers[s].areafree(), 0, PR_INTERVAL_NO_TIMEOUT);
718 if (bytesRead == 0) {
719 LOG_DEBUG((" socket gracefully closed"));
720 client_done = true;
721 in_flags &= ~PR_POLL_READ;
722 } else if (bytesRead < 0) {
723 if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
724 LOG_DEBUG((" error=%d", PR_GetError()));
725 // We are in error state, indicate that the connection was
726 // not closed gracefully
727 client_error = true;
728 socketErrorState[s] = true;
729 // Wipe out our send buffer, we cannot send it anyway.
730 buffers[s2].bufferhead = buffers[s2].buffertail =
731 buffers[s2].buffer;
732 } else
733 LOG_DEBUG((" would block"));
734 } else {
735 // If the other socket is in error state (unable to send/receive)
736 // throw this data away and continue loop
737 if (socketErrorState[s2]) {
738 LOG_DEBUG((" have read but other socket is in error state\n"));
739 continue;
742 buffers[s].buffertail += bytesRead;
743 LOG_DEBUG((", read %d bytes", bytesRead));
745 // We have to accept and handle the initial CONNECT request here
746 int32_t response;
747 if (!connect_accepted &&
748 ReadConnectRequest(ci->server_info, buffers[s], &response,
749 certificateToUse, &clientAuth, fullHost,
750 locationHeader, &flags)) {
751 // Mark this as a proxy-only connection (no SSL) if the CONNECT
752 // request didn't come for port 443 or from any of the server's
753 // cert or clientauth hostnames.
754 if (fullHost.find(":443") == string::npos) {
755 server_match_t match;
756 match.fullHost = fullHost;
757 match.matched = false;
758 PL_HashTableEnumerateEntries(ci->server_info->host_cert_table,
759 match_hostname, &match);
760 PL_HashTableEnumerateEntries(
761 ci->server_info->host_clientauth_table, match_hostname,
762 &match);
763 PL_HashTableEnumerateEntries(ci->server_info->host_ssl3_table,
764 match_hostname, &match);
765 PL_HashTableEnumerateEntries(ci->server_info->host_tls1_table,
766 match_hostname, &match);
767 PL_HashTableEnumerateEntries(ci->server_info->host_tls11_table,
768 match_hostname, &match);
769 PL_HashTableEnumerateEntries(ci->server_info->host_tls12_table,
770 match_hostname, &match);
771 PL_HashTableEnumerateEntries(ci->server_info->host_tls13_table,
772 match_hostname, &match);
773 PL_HashTableEnumerateEntries(ci->server_info->host_3des_table,
774 match_hostname, &match);
775 PL_HashTableEnumerateEntries(
776 ci->server_info->host_failhandshake_table, match_hostname,
777 &match);
778 ci->http_proxy_only = !match.matched;
779 } else {
780 ci->http_proxy_only = false;
783 // Clean the request as it would be read
784 buffers[s].bufferhead = buffers[s].buffertail = buffers[s].buffer;
785 in_flags |= PR_POLL_WRITE;
786 connect_accepted = true;
788 // Store response to the oposite buffer
789 if (response == 200) {
790 LOG_DEBUG(
791 (" accepted CONNECT request, connected to the server, "
792 "sending OK to the client\n"));
793 strcpy(
794 buffers[s2].buffer,
795 "HTTP/1.1 200 Connected\r\nConnection: keep-alive\r\n\r\n");
796 } else if (response == 302) {
797 LOG_DEBUG(
798 (" accepted CONNECT request with redirection, "
799 "sending location and 302 to the client\n"));
800 client_done = true;
801 snprintf(buffers[s2].buffer,
802 buffers[s2].bufferend - buffers[s2].buffer,
803 "HTTP/1.1 302 Moved\r\n"
804 "Location: https://%s/\r\n"
805 "Connection: close\r\n\r\n",
806 locationHeader.c_str());
807 } else {
808 LOG_ERRORD(
809 (" could not read the connect request, closing connection "
810 "with %d",
811 response));
812 client_done = true;
813 snprintf(buffers[s2].buffer,
814 buffers[s2].bufferend - buffers[s2].buffer,
815 "HTTP/1.1 %d ERROR\r\nConnection: close\r\n\r\n",
816 response);
818 break;
821 buffers[s2].buffertail =
822 buffers[s2].buffer + strlen(buffers[s2].buffer);
824 // Send the response to the client socket
825 break;
826 } // end of CONNECT handling
828 if (!buffers[s].areafree()) {
829 // Do not poll for read when the buffer is full
830 LOG_DEBUG((" no place in our read buffer, stop reading"));
831 in_flags &= ~PR_POLL_READ;
834 if (ssl_updated) {
835 if (s == 0 && expect_request_start) {
836 if (!strstr(buffers[s].bufferhead, "\r\n\r\n")) {
837 // We haven't received the complete header yet, so wait.
838 continue;
840 ci->iswebsocket = AdjustWebSocketHost(buffers[s], ci);
841 expect_request_start = !(
842 ci->iswebsocket || AdjustRequestURI(buffers[s], &fullHost));
843 PRNetAddr* addr = &remote_addr;
844 if (ci->iswebsocket && websocket_server.inet.port)
845 addr = &websocket_server;
846 if (!ConnectSocket(other_sock, addr, connect_timeout)) {
847 LOG_ERRORD(
848 (" could not open connection to the real server\n"));
849 client_error = true;
850 break;
852 LOG_DEBUG(("\n connected to remote server\n"));
853 numberOfSockets = 2;
854 } else if (s == 1 && ci->iswebsocket) {
855 if (!AdjustWebSocketLocation(buffers[s], ci)) continue;
858 in_flags2 |= PR_POLL_WRITE;
859 LOG_DEBUG((" telling the other socket to write"));
860 } else
861 LOG_DEBUG(
862 (" we have something for the other socket to write, but ssl "
863 "has not been administered on it"));
865 } // PR_POLL_READ handling
867 if (out_flags & PR_POLL_WRITE) {
868 LOG_DEBUG((" :writing"));
869 int32_t bytesWrite =
870 PR_Send(sockets[s].fd, buffers[s2].bufferhead,
871 buffers[s2].present(), 0, PR_INTERVAL_NO_TIMEOUT);
873 if (bytesWrite < 0) {
874 if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
875 LOG_DEBUG((" error=%d", PR_GetError()));
876 client_error = true;
877 socketErrorState[s] = true;
878 // We got a fatal error while writting the buffer. Clear it to
879 // break the main loop, we will never more be able to send it.
880 buffers[s2].bufferhead = buffers[s2].buffertail =
881 buffers[s2].buffer;
882 } else
883 LOG_DEBUG((" would block"));
884 } else {
885 LOG_DEBUG((", written %d bytes", bytesWrite));
886 buffers[s2].buffertail[1] = '\0';
887 LOG_DEBUG((" dump:\n%.*s\n", bytesWrite, buffers[s2].bufferhead));
889 buffers[s2].bufferhead += bytesWrite;
890 if (buffers[s2].present()) {
891 LOG_DEBUG((" still have to write %d bytes",
892 (int)buffers[s2].present()));
893 in_flags |= PR_POLL_WRITE;
894 } else {
895 if (!ssl_updated) {
896 LOG_DEBUG((" proxy response sent to the client"));
897 // Proxy response has just been writen, update to ssl
898 ssl_updated = true;
899 if (ci->http_proxy_only) {
900 LOG_DEBUG(
901 (" not updating to SSL based on http_proxy_only for this "
902 "socket"));
903 } else if (!ConfigureSSLServerSocket(
904 ci->client_sock, ci->server_info,
905 certificateToUse, clientAuth, flags)) {
906 LOG_ERRORD((" failed to config server socket\n"));
907 client_error = true;
908 break;
909 } else {
910 LOG_DEBUG((" client socket updated to SSL"));
912 } // sslUpdate
914 LOG_DEBUG(
915 (" dropping our write flag and setting other socket read "
916 "flag"));
917 in_flags &= ~PR_POLL_WRITE;
918 in_flags2 |= PR_POLL_READ;
919 buffers[s2].compact();
922 } // PR_POLL_WRITE handling
923 LOG_END_BLOCK(); // end the log
924 } // for...
925 } // while, poll
926 } else
927 client_error = true;
929 LOG_DEBUG(("SSLTUNNEL(%p)): exiting root function for csock=%p, ssock=%p\n",
930 static_cast<void*>(data), static_cast<void*>(ci->client_sock),
931 static_cast<void*>(other_sock.get())));
932 if (!client_error) PR_Shutdown(ci->client_sock, PR_SHUTDOWN_SEND);
933 PR_Close(ci->client_sock);
935 delete ci;
939 * Start listening for SSL connections on a specified port, handing
940 * them off to client threads after accepting the connection.
941 * The data parameter is a server_info_t*, owned by the calling
942 * function.
944 void StartServer(void* data) {
945 server_info_t* si = static_cast<server_info_t*>(data);
947 // TODO: select ciphers?
948 UniquePRFileDesc listen_socket(PR_NewTCPSocket());
949 if (!listen_socket) {
950 LOG_ERROR(("failed to create socket\n"));
951 SignalShutdown();
952 return;
955 // In case the socket is still open in the TIME_WAIT state from a previous
956 // instance of ssltunnel we ask to reuse the port.
957 PRSocketOptionData socket_option;
958 socket_option.option = PR_SockOpt_Reuseaddr;
959 socket_option.value.reuse_addr = true;
960 PR_SetSocketOption(listen_socket.get(), &socket_option);
962 PRNetAddr server_addr;
963 PRNetAddrValue listen_addr;
964 if (listen_public) {
965 listen_addr = PR_IpAddrAny;
966 } else {
967 listen_addr = PR_IpAddrLoopback;
969 PR_InitializeNetAddr(listen_addr, si->listen_port, &server_addr);
971 if (PR_Bind(listen_socket.get(), &server_addr) != PR_SUCCESS) {
972 LOG_ERROR(("failed to bind socket on port %d: error %d\n", si->listen_port,
973 PR_GetError()));
974 SignalShutdown();
975 return;
978 if (PR_Listen(listen_socket.get(), 1) != PR_SUCCESS) {
979 LOG_ERROR(("failed to listen on socket\n"));
980 SignalShutdown();
981 return;
984 LOG_INFO(("Server listening on port %d with cert %s\n", si->listen_port,
985 si->cert_nickname.c_str()));
987 while (!shutdown_server) {
988 connection_info_t* ci = new connection_info_t();
989 ci->server_info = si;
990 ci->http_proxy_only = do_http_proxy;
991 // block waiting for connections
992 ci->client_sock = PR_Accept(listen_socket.get(), &ci->client_addr,
993 PR_INTERVAL_NO_TIMEOUT);
995 PRSocketOptionData option;
996 option.option = PR_SockOpt_Nonblocking;
997 option.value.non_blocking = true;
998 PR_SetSocketOption(ci->client_sock, &option);
1000 if (ci->client_sock)
1001 // Not actually using this PRJob*...
1002 // PRJob* job =
1003 PR_QueueJob(threads, HandleConnection, ci, true);
1004 else
1005 delete ci;
1009 // bogus password func, just don't use passwords. :-P
1010 char* password_func(PK11SlotInfo* slot, PRBool retry, void* arg) {
1011 if (retry) return nullptr;
1013 return PL_strdup("");
1016 server_info_t* findServerInfo(int portnumber) {
1017 for (auto& server : servers) {
1018 if (server.listen_port == portnumber) return &server;
1021 return nullptr;
1024 PLHashTable* get_ssl3_table(server_info_t* server) {
1025 return server->host_ssl3_table;
1028 PLHashTable* get_tls1_table(server_info_t* server) {
1029 return server->host_tls1_table;
1032 PLHashTable* get_tls11_table(server_info_t* server) {
1033 return server->host_tls11_table;
1036 PLHashTable* get_tls12_table(server_info_t* server) {
1037 return server->host_tls12_table;
1040 PLHashTable* get_tls13_table(server_info_t* server) {
1041 return server->host_tls13_table;
1044 PLHashTable* get_3des_table(server_info_t* server) {
1045 return server->host_3des_table;
1048 PLHashTable* get_failhandshake_table(server_info_t* server) {
1049 return server->host_failhandshake_table;
1052 int parseWeakCryptoConfig(char* const& keyword, char*& _caret,
1053 PLHashTable* (*get_table)(server_info_t*)) {
1054 char* hostname = strtok2(_caret, ":", &_caret);
1055 char* hostportstring = strtok2(_caret, ":", &_caret);
1056 char* serverportstring = strtok2(_caret, "\n", &_caret);
1058 int port = atoi(serverportstring);
1059 if (port <= 0) {
1060 LOG_ERROR(("Invalid port specified: %s\n", serverportstring));
1061 return 1;
1064 if (server_info_t* existingServer = findServerInfo(port)) {
1065 any_host_spec_config = true;
1067 char* hostname_copy =
1068 new char[strlen(hostname) + strlen(hostportstring) + 2];
1069 if (!hostname_copy) {
1070 LOG_ERROR(("Out of memory"));
1071 return 1;
1074 strcpy(hostname_copy, hostname);
1075 strcat(hostname_copy, ":");
1076 strcat(hostname_copy, hostportstring);
1078 PLHashEntry* entry =
1079 PL_HashTableAdd(get_table(existingServer), hostname_copy, keyword);
1080 if (!entry) {
1081 LOG_ERROR(("Out of memory"));
1082 return 1;
1084 } else {
1085 LOG_ERROR(
1086 ("Server on port %d for redirhost option is not defined, use 'listen' "
1087 "option first",
1088 port));
1089 return 1;
1092 return 0;
1095 int processConfigLine(char* configLine) {
1096 if (*configLine == 0 || *configLine == '#') return 0;
1098 char* _caret;
1099 char* keyword = strtok2(configLine, ":", &_caret);
1100 // Configure usage of http/ssl tunneling proxy behavior
1101 if (!strcmp(keyword, "httpproxy")) {
1102 char* value = strtok2(_caret, ":", &_caret);
1103 if (!strcmp(value, "1")) do_http_proxy = true;
1105 return 0;
1108 if (!strcmp(keyword, "websocketserver")) {
1109 char* ipstring = strtok2(_caret, ":", &_caret);
1110 if (PR_StringToNetAddr(ipstring, &websocket_server) != PR_SUCCESS) {
1111 LOG_ERROR(("Invalid IP address in proxy config: %s\n", ipstring));
1112 return 1;
1114 char* remoteport = strtok2(_caret, ":", &_caret);
1115 int port = atoi(remoteport);
1116 if (port <= 0) {
1117 LOG_ERROR(("Invalid remote port in proxy config: %s\n", remoteport));
1118 return 1;
1120 websocket_server.inet.port = PR_htons(port);
1121 return 0;
1124 // Configure the forward address of the target server
1125 if (!strcmp(keyword, "forward")) {
1126 char* ipstring = strtok2(_caret, ":", &_caret);
1127 if (PR_StringToNetAddr(ipstring, &remote_addr) != PR_SUCCESS) {
1128 LOG_ERROR(("Invalid remote IP address: %s\n", ipstring));
1129 return 1;
1131 char* serverportstring = strtok2(_caret, ":", &_caret);
1132 int port = atoi(serverportstring);
1133 if (port <= 0) {
1134 LOG_ERROR(("Invalid remote port: %s\n", serverportstring));
1135 return 1;
1137 remote_addr.inet.port = PR_htons(port);
1139 return 0;
1142 // Configure all listen sockets and port+certificate bindings.
1143 // Listen on the public address if "*" was specified as the listen
1144 // address or listen on the loopback address if "127.0.0.1" was
1145 // specified. Using loopback will prevent users getting errors from
1146 // their firewalls about ssltunnel needing permission. A public
1147 // address is required when proxying ssl traffic from a physical or
1148 // emulated Android device since it has a different ip address from
1149 // the host.
1150 if (!strcmp(keyword, "listen")) {
1151 char* hostname = strtok2(_caret, ":", &_caret);
1152 char* hostportstring = nullptr;
1153 if (!strcmp(hostname, "*")) {
1154 listen_public = true;
1155 } else if (strcmp(hostname, "127.0.0.1")) {
1156 any_host_spec_config = true;
1157 hostportstring = strtok2(_caret, ":", &_caret);
1160 char* serverportstring = strtok2(_caret, ":", &_caret);
1161 char* certnick = strtok2(_caret, ":", &_caret);
1163 int port = atoi(serverportstring);
1164 if (port <= 0) {
1165 LOG_ERROR(("Invalid port specified: %s\n", serverportstring));
1166 return 1;
1169 if (server_info_t* existingServer = findServerInfo(port)) {
1170 if (!hostportstring) {
1171 LOG_ERROR(
1172 ("Null hostportstring specified for hostname %s\n", hostname));
1173 return 1;
1175 char* certnick_copy = new char[strlen(certnick) + 1];
1176 char* hostname_copy =
1177 new char[strlen(hostname) + strlen(hostportstring) + 2];
1179 strcpy(hostname_copy, hostname);
1180 strcat(hostname_copy, ":");
1181 strcat(hostname_copy, hostportstring);
1182 strcpy(certnick_copy, certnick);
1184 PLHashEntry* entry = PL_HashTableAdd(existingServer->host_cert_table,
1185 hostname_copy, certnick_copy);
1186 if (!entry) {
1187 LOG_ERROR(("Out of memory"));
1188 return 1;
1190 } else {
1191 server_info_t server;
1192 server.cert_nickname = certnick;
1193 server.listen_port = port;
1194 server.host_cert_table =
1195 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1196 PL_CompareStrings, nullptr, nullptr);
1197 if (!server.host_cert_table) {
1198 LOG_ERROR(("Internal, could not create hash table\n"));
1199 return 1;
1201 server.host_clientauth_table =
1202 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1203 ClientAuthValueComparator, nullptr, nullptr);
1204 if (!server.host_clientauth_table) {
1205 LOG_ERROR(("Internal, could not create hash table\n"));
1206 return 1;
1208 server.host_redir_table =
1209 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1210 PL_CompareStrings, nullptr, nullptr);
1211 if (!server.host_redir_table) {
1212 LOG_ERROR(("Internal, could not create hash table\n"));
1213 return 1;
1216 server.host_ssl3_table =
1217 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1218 PL_CompareStrings, nullptr, nullptr);
1220 if (!server.host_ssl3_table) {
1221 LOG_ERROR(("Internal, could not create hash table\n"));
1222 return 1;
1225 server.host_tls1_table =
1226 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1227 PL_CompareStrings, nullptr, nullptr);
1229 if (!server.host_tls1_table) {
1230 LOG_ERROR(("Internal, could not create hash table\n"));
1231 return 1;
1234 server.host_tls11_table =
1235 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1236 PL_CompareStrings, nullptr, nullptr);
1238 if (!server.host_tls11_table) {
1239 LOG_ERROR(("Internal, could not create hash table\n"));
1240 return 1;
1243 server.host_tls12_table =
1244 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1245 PL_CompareStrings, nullptr, nullptr);
1247 if (!server.host_tls12_table) {
1248 LOG_ERROR(("Internal, could not create hash table\n"));
1249 return 1;
1252 server.host_tls13_table =
1253 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1254 PL_CompareStrings, nullptr, nullptr);
1256 if (!server.host_tls13_table) {
1257 LOG_ERROR(("Internal, could not create hash table\n"));
1258 return 1;
1261 server.host_3des_table =
1262 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1263 PL_CompareStrings, nullptr, nullptr);
1265 if (!server.host_3des_table) {
1266 LOG_ERROR(("Internal, could not create hash table\n"));
1267 return 1;
1270 server.host_failhandshake_table =
1271 PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
1272 PL_CompareStrings, nullptr, nullptr);
1274 if (!server.host_failhandshake_table) {
1275 LOG_ERROR(("Internal, could not create hash table\n"));
1276 return 1;
1279 servers.push_back(server);
1282 return 0;
1285 if (!strcmp(keyword, "clientauth")) {
1286 char* hostname = strtok2(_caret, ":", &_caret);
1287 char* hostportstring = strtok2(_caret, ":", &_caret);
1288 char* serverportstring = strtok2(_caret, ":", &_caret);
1290 int port = atoi(serverportstring);
1291 if (port <= 0) {
1292 LOG_ERROR(("Invalid port specified: %s\n", serverportstring));
1293 return 1;
1296 if (server_info_t* existingServer = findServerInfo(port)) {
1297 char* authoptionstring = strtok2(_caret, ":", &_caret);
1298 client_auth_option* authoption = new client_auth_option;
1299 if (!authoption) {
1300 LOG_ERROR(("Out of memory"));
1301 return 1;
1304 if (!strcmp(authoptionstring, "require"))
1305 *authoption = caRequire;
1306 else if (!strcmp(authoptionstring, "request"))
1307 *authoption = caRequest;
1308 else if (!strcmp(authoptionstring, "none"))
1309 *authoption = caNone;
1310 else {
1311 LOG_ERROR(
1312 ("Incorrect client auth option modifier for host '%s'", hostname));
1313 delete authoption;
1314 return 1;
1317 any_host_spec_config = true;
1319 char* hostname_copy =
1320 new char[strlen(hostname) + strlen(hostportstring) + 2];
1321 if (!hostname_copy) {
1322 LOG_ERROR(("Out of memory"));
1323 delete authoption;
1324 return 1;
1327 strcpy(hostname_copy, hostname);
1328 strcat(hostname_copy, ":");
1329 strcat(hostname_copy, hostportstring);
1331 PLHashEntry* entry = PL_HashTableAdd(
1332 existingServer->host_clientauth_table, hostname_copy, authoption);
1333 if (!entry) {
1334 LOG_ERROR(("Out of memory"));
1335 delete authoption;
1336 return 1;
1338 } else {
1339 LOG_ERROR(
1340 ("Server on port %d for client authentication option is not defined, "
1341 "use 'listen' option first",
1342 port));
1343 return 1;
1346 return 0;
1349 if (!strcmp(keyword, "redirhost")) {
1350 char* hostname = strtok2(_caret, ":", &_caret);
1351 char* hostportstring = strtok2(_caret, ":", &_caret);
1352 char* serverportstring = strtok2(_caret, ":", &_caret);
1354 int port = atoi(serverportstring);
1355 if (port <= 0) {
1356 LOG_ERROR(("Invalid port specified: %s\n", serverportstring));
1357 return 1;
1360 if (server_info_t* existingServer = findServerInfo(port)) {
1361 char* redirhoststring = strtok2(_caret, ":", &_caret);
1363 any_host_spec_config = true;
1365 char* hostname_copy =
1366 new char[strlen(hostname) + strlen(hostportstring) + 2];
1367 if (!hostname_copy) {
1368 LOG_ERROR(("Out of memory"));
1369 return 1;
1372 strcpy(hostname_copy, hostname);
1373 strcat(hostname_copy, ":");
1374 strcat(hostname_copy, hostportstring);
1376 char* redir_copy = new char[strlen(redirhoststring) + 1];
1377 strcpy(redir_copy, redirhoststring);
1378 PLHashEntry* entry = PL_HashTableAdd(existingServer->host_redir_table,
1379 hostname_copy, redir_copy);
1380 if (!entry) {
1381 LOG_ERROR(("Out of memory"));
1382 delete[] hostname_copy;
1383 delete[] redir_copy;
1384 return 1;
1386 } else {
1387 LOG_ERROR(
1388 ("Server on port %d for redirhost option is not defined, use "
1389 "'listen' option first",
1390 port));
1391 return 1;
1394 return 0;
1397 if (!strcmp(keyword, "ssl3")) {
1398 return parseWeakCryptoConfig(keyword, _caret, get_ssl3_table);
1400 if (!strcmp(keyword, "tls1")) {
1401 return parseWeakCryptoConfig(keyword, _caret, get_tls1_table);
1403 if (!strcmp(keyword, "tls1_1")) {
1404 return parseWeakCryptoConfig(keyword, _caret, get_tls11_table);
1406 if (!strcmp(keyword, "tls1_2")) {
1407 return parseWeakCryptoConfig(keyword, _caret, get_tls12_table);
1409 if (!strcmp(keyword, "tls1_3")) {
1410 return parseWeakCryptoConfig(keyword, _caret, get_tls13_table);
1413 if (!strcmp(keyword, "3des")) {
1414 return parseWeakCryptoConfig(keyword, _caret, get_3des_table);
1417 if (!strcmp(keyword, "failHandshake")) {
1418 return parseWeakCryptoConfig(keyword, _caret, get_failhandshake_table);
1421 // Configure the NSS certificate database directory
1422 if (!strcmp(keyword, "certdbdir")) {
1423 nssconfigdir = strtok2(_caret, "\n", &_caret);
1424 return 0;
1427 LOG_ERROR(("Error: keyword \"%s\" unexpected\n", keyword));
1428 return 1;
1431 int parseConfigFile(const char* filePath) {
1432 FILE* f = fopen(filePath, "r");
1433 if (!f) return 1;
1435 char buffer[1024], *b = buffer;
1436 while (!feof(f)) {
1437 char c;
1439 if (fscanf(f, "%c", &c) != 1) {
1440 break;
1443 switch (c) {
1444 case '\n':
1445 *b++ = 0;
1446 if (processConfigLine(buffer)) {
1447 fclose(f);
1448 return 1;
1450 b = buffer;
1451 continue;
1453 case '\r':
1454 continue;
1456 default:
1457 *b++ = c;
1461 fclose(f);
1463 // Check mandatory items
1464 if (nssconfigdir.empty()) {
1465 LOG_ERROR(
1466 ("Error: missing path to NSS certification database\n,use "
1467 "certdbdir:<path> in the config file\n"));
1468 return 1;
1471 if (any_host_spec_config && !do_http_proxy) {
1472 LOG_ERROR(
1473 ("Warning: any host-specific configurations are ignored, add "
1474 "httpproxy:1 to allow them\n"));
1477 return 0;
1480 int freeHostCertHashItems(PLHashEntry* he, int i, void* arg) {
1481 delete[] (char*)he->key;
1482 delete[] (char*)he->value;
1483 return HT_ENUMERATE_REMOVE;
1486 int freeHostRedirHashItems(PLHashEntry* he, int i, void* arg) {
1487 delete[] (char*)he->key;
1488 delete[] (char*)he->value;
1489 return HT_ENUMERATE_REMOVE;
1492 int freeClientAuthHashItems(PLHashEntry* he, int i, void* arg) {
1493 delete[] (char*)he->key;
1494 delete (client_auth_option*)he->value;
1495 return HT_ENUMERATE_REMOVE;
1498 int freeSSL3HashItems(PLHashEntry* he, int i, void* arg) {
1499 delete[] (char*)he->key;
1500 return HT_ENUMERATE_REMOVE;
1503 int freeTLSHashItems(PLHashEntry* he, int i, void* arg) {
1504 delete[] (char*)he->key;
1505 return HT_ENUMERATE_REMOVE;
1508 int free3DESHashItems(PLHashEntry* he, int i, void* arg) {
1509 delete[] (char*)he->key;
1510 return HT_ENUMERATE_REMOVE;
1513 int main(int argc, char** argv) {
1514 const char* configFilePath;
1516 const char* logLevelEnv = PR_GetEnv("SSLTUNNEL_LOG_LEVEL");
1517 gLogLevel = logLevelEnv ? (LogLevel)atoi(logLevelEnv) : LEVEL_INFO;
1519 if (argc == 1)
1520 configFilePath = "ssltunnel.cfg";
1521 else
1522 configFilePath = argv[1];
1524 memset(&websocket_server, 0, sizeof(PRNetAddr));
1526 if (parseConfigFile(configFilePath)) {
1527 LOG_ERROR((
1528 "Error: config file \"%s\" missing or formating incorrect\n"
1529 "Specify path to the config file as parameter to ssltunnel or \n"
1530 "create ssltunnel.cfg in the working directory.\n\n"
1531 "Example format of the config file:\n\n"
1532 " # Enable http/ssl tunneling proxy-like behavior.\n"
1533 " # If not specified ssltunnel simply does direct forward.\n"
1534 " httpproxy:1\n\n"
1535 " # Specify path to the certification database used.\n"
1536 " certdbdir:/path/to/certdb\n\n"
1537 " # Forward/proxy all requests in raw to 127.0.0.1:8888.\n"
1538 " forward:127.0.0.1:8888\n\n"
1539 " # Accept connections on port 4443 or 5678 resp. and "
1540 "authenticate\n"
1541 " # to any host ('*') using the 'server cert' or 'server cert 2' "
1542 "resp.\n"
1543 " listen:*:4443:server cert\n"
1544 " listen:*:5678:server cert 2\n\n"
1545 " # Accept connections on port 4443 and authenticate using\n"
1546 " # 'a different cert' when target host is 'my.host.name:443'.\n"
1547 " # This only works in httpproxy mode and has higher priority\n"
1548 " # than the previous option.\n"
1549 " listen:my.host.name:443:4443:a different cert\n\n"
1550 " # To make a specific host require or just request a client "
1551 "certificate\n"
1552 " # to authenticate use the following options. This can only be "
1553 "used\n"
1554 " # in httpproxy mode and only after the 'listen' option has "
1555 "been\n"
1556 " # specified. You also have to specify the tunnel listen port.\n"
1557 " clientauth:requesting-client-cert.host.com:443:4443:request\n"
1558 " clientauth:requiring-client-cert.host.com:443:4443:require\n"
1559 " # Proxy WebSocket traffic to the server at 127.0.0.1:9999,\n"
1560 " # instead of the server specified in the 'forward' option.\n"
1561 " websocketserver:127.0.0.1:9999\n",
1562 configFilePath));
1563 return 1;
1566 // create a thread pool to handle connections
1567 threads =
1568 PR_CreateThreadPool(INITIAL_THREADS * servers.size(),
1569 MAX_THREADS * servers.size(), DEFAULT_STACKSIZE);
1570 if (!threads) {
1571 LOG_ERROR(("Failed to create thread pool\n"));
1572 return 1;
1575 shutdown_lock = PR_NewLock();
1576 if (!shutdown_lock) {
1577 LOG_ERROR(("Failed to create lock\n"));
1578 PR_ShutdownThreadPool(threads);
1579 return 1;
1581 shutdown_condvar = PR_NewCondVar(shutdown_lock);
1582 if (!shutdown_condvar) {
1583 LOG_ERROR(("Failed to create condvar\n"));
1584 PR_ShutdownThreadPool(threads);
1585 PR_DestroyLock(shutdown_lock);
1586 return 1;
1589 PK11_SetPasswordFunc(password_func);
1591 // Initialize NSS
1592 if (NSS_Init(nssconfigdir.c_str()) != SECSuccess) {
1593 int32_t errorlen = PR_GetErrorTextLength();
1594 if (errorlen) {
1595 auto err = mozilla::MakeUnique<char[]>(errorlen + 1);
1596 PR_GetErrorText(err.get());
1597 LOG_ERROR(("Failed to init NSS: %s", err.get()));
1598 } else {
1599 LOG_ERROR(("Failed to init NSS: Cannot get error from NSPR."));
1601 PR_ShutdownThreadPool(threads);
1602 PR_DestroyCondVar(shutdown_condvar);
1603 PR_DestroyLock(shutdown_lock);
1604 return 1;
1607 if (NSS_SetDomesticPolicy() != SECSuccess) {
1608 LOG_ERROR(("NSS_SetDomesticPolicy failed\n"));
1609 PR_ShutdownThreadPool(threads);
1610 PR_DestroyCondVar(shutdown_condvar);
1611 PR_DestroyLock(shutdown_lock);
1612 NSS_Shutdown();
1613 return 1;
1616 // these values should make NSS use the defaults
1617 if (SSL_ConfigServerSessionIDCache(0, 0, 0, nullptr) != SECSuccess) {
1618 LOG_ERROR(("SSL_ConfigServerSessionIDCache failed\n"));
1619 PR_ShutdownThreadPool(threads);
1620 PR_DestroyCondVar(shutdown_condvar);
1621 PR_DestroyLock(shutdown_lock);
1622 NSS_Shutdown();
1623 return 1;
1626 for (auto& server : servers) {
1627 // Not actually using this PRJob*...
1628 // PRJob* server_job =
1629 PR_QueueJob(threads, StartServer, &server, true);
1631 // now wait for someone to tell us to quit
1632 PR_Lock(shutdown_lock);
1633 PR_WaitCondVar(shutdown_condvar, PR_INTERVAL_NO_TIMEOUT);
1634 PR_Unlock(shutdown_lock);
1635 shutdown_server = true;
1636 LOG_INFO(("Shutting down...\n"));
1637 // cleanup
1638 PR_ShutdownThreadPool(threads);
1639 PR_JoinThreadPool(threads);
1640 PR_DestroyCondVar(shutdown_condvar);
1641 PR_DestroyLock(shutdown_lock);
1642 if (NSS_Shutdown() == SECFailure) {
1643 LOG_DEBUG(("Leaked NSS objects!\n"));
1646 for (auto& server : servers) {
1647 PL_HashTableEnumerateEntries(server.host_cert_table, freeHostCertHashItems,
1648 nullptr);
1649 PL_HashTableEnumerateEntries(server.host_clientauth_table,
1650 freeClientAuthHashItems, nullptr);
1651 PL_HashTableEnumerateEntries(server.host_redir_table,
1652 freeHostRedirHashItems, nullptr);
1653 PL_HashTableEnumerateEntries(server.host_ssl3_table, freeSSL3HashItems,
1654 nullptr);
1655 PL_HashTableEnumerateEntries(server.host_tls1_table, freeTLSHashItems,
1656 nullptr);
1657 PL_HashTableEnumerateEntries(server.host_tls11_table, freeTLSHashItems,
1658 nullptr);
1659 PL_HashTableEnumerateEntries(server.host_tls12_table, freeTLSHashItems,
1660 nullptr);
1661 PL_HashTableEnumerateEntries(server.host_tls13_table, freeTLSHashItems,
1662 nullptr);
1663 PL_HashTableEnumerateEntries(server.host_3des_table, free3DESHashItems,
1664 nullptr);
1665 PL_HashTableEnumerateEntries(server.host_failhandshake_table,
1666 free3DESHashItems, nullptr);
1667 PL_HashTableDestroy(server.host_cert_table);
1668 PL_HashTableDestroy(server.host_clientauth_table);
1669 PL_HashTableDestroy(server.host_redir_table);
1670 PL_HashTableDestroy(server.host_ssl3_table);
1671 PL_HashTableDestroy(server.host_tls1_table);
1672 PL_HashTableDestroy(server.host_tls11_table);
1673 PL_HashTableDestroy(server.host_tls12_table);
1674 PL_HashTableDestroy(server.host_tls13_table);
1675 PL_HashTableDestroy(server.host_3des_table);
1676 PL_HashTableDestroy(server.host_failhandshake_table);
1679 PR_Cleanup();
1680 return 0;