testing/mochitest/ssltunnel/ssltunnel.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 /*
michael@0 7 * WARNING: DO NOT USE THIS CODE IN PRODUCTION SYSTEMS. It is highly likely to
michael@0 8 * be plagued with the usual problems endemic to C (buffer overflows
michael@0 9 * and the like). We don't especially care here (but would accept
michael@0 10 * patches!) because this is only intended for use in our test
michael@0 11 * harnesses in controlled situations where input is guaranteed not to
michael@0 12 * be malicious.
michael@0 13 */
michael@0 14
michael@0 15 #include "ScopedNSSTypes.h"
michael@0 16 #include <assert.h>
michael@0 17 #include <stdio.h>
michael@0 18 #include <string>
michael@0 19 #include <vector>
michael@0 20 #include <algorithm>
michael@0 21 #include <stdarg.h>
michael@0 22 #include "prinit.h"
michael@0 23 #include "prerror.h"
michael@0 24 #include "prenv.h"
michael@0 25 #include "prnetdb.h"
michael@0 26 #include "prtpool.h"
michael@0 27 #include "nsAlgorithm.h"
michael@0 28 #include "nss.h"
michael@0 29 #include "key.h"
michael@0 30 #include "ssl.h"
michael@0 31 #include "plhash.h"
michael@0 32
michael@0 33 using namespace mozilla;
michael@0 34 using namespace mozilla::psm;
michael@0 35 using std::string;
michael@0 36 using std::vector;
michael@0 37
michael@0 38 #define IS_DELIM(m, c) ((m)[(c) >> 3] & (1 << ((c) & 7)))
michael@0 39 #define SET_DELIM(m, c) ((m)[(c) >> 3] |= (1 << ((c) & 7)))
michael@0 40 #define DELIM_TABLE_SIZE 32
michael@0 41
michael@0 42 // You can set the level of logging by env var SSLTUNNEL_LOG_LEVEL=n, where n
michael@0 43 // is 0 through 3. The default is 1, INFO level logging.
michael@0 44 enum LogLevel {
michael@0 45 LEVEL_DEBUG = 0,
michael@0 46 LEVEL_INFO = 1,
michael@0 47 LEVEL_ERROR = 2,
michael@0 48 LEVEL_SILENT = 3
michael@0 49 } gLogLevel, gLastLogLevel;
michael@0 50
michael@0 51 #define _LOG_OUTPUT(level, func, params) \
michael@0 52 PR_BEGIN_MACRO \
michael@0 53 if (level >= gLogLevel) { \
michael@0 54 gLastLogLevel = level; \
michael@0 55 func params;\
michael@0 56 } \
michael@0 57 PR_END_MACRO
michael@0 58
michael@0 59 // The most verbose output
michael@0 60 #define LOG_DEBUG(params) \
michael@0 61 _LOG_OUTPUT(LEVEL_DEBUG, printf, params)
michael@0 62
michael@0 63 // Top level informative messages
michael@0 64 #define LOG_INFO(params) \
michael@0 65 _LOG_OUTPUT(LEVEL_INFO, printf, params)
michael@0 66
michael@0 67 // Serious errors that must be logged always until completely gag
michael@0 68 #define LOG_ERROR(params) \
michael@0 69 _LOG_OUTPUT(LEVEL_ERROR, eprintf, params)
michael@0 70
michael@0 71 // Same as LOG_ERROR, but when logging is set to LEVEL_DEBUG, the message
michael@0 72 // will be put to the stdout instead of stderr to keep continuity with other
michael@0 73 // LOG_DEBUG message output
michael@0 74 #define LOG_ERRORD(params) \
michael@0 75 PR_BEGIN_MACRO \
michael@0 76 if (gLogLevel == LEVEL_DEBUG) \
michael@0 77 _LOG_OUTPUT(LEVEL_ERROR, printf, params); \
michael@0 78 else \
michael@0 79 _LOG_OUTPUT(LEVEL_ERROR, eprintf, params); \
michael@0 80 PR_END_MACRO
michael@0 81
michael@0 82 // If there is any output written between LOG_BEGIN_BLOCK() and
michael@0 83 // LOG_END_BLOCK() then a new line will be put to the proper output (out/err)
michael@0 84 #define LOG_BEGIN_BLOCK() \
michael@0 85 gLastLogLevel = LEVEL_SILENT;
michael@0 86
michael@0 87 #define LOG_END_BLOCK() \
michael@0 88 PR_BEGIN_MACRO \
michael@0 89 if (gLastLogLevel == LEVEL_ERROR) \
michael@0 90 LOG_ERROR(("\n")); \
michael@0 91 if (gLastLogLevel < LEVEL_ERROR) \
michael@0 92 _LOG_OUTPUT(gLastLogLevel, printf, ("\n")); \
michael@0 93 PR_END_MACRO
michael@0 94
michael@0 95 int eprintf(const char* str, ...)
michael@0 96 {
michael@0 97 va_list ap;
michael@0 98 va_start(ap, str);
michael@0 99 int result = vfprintf(stderr, str, ap);
michael@0 100 va_end(ap);
michael@0 101 return result;
michael@0 102 }
michael@0 103
michael@0 104 // Copied from nsCRT
michael@0 105 char* strtok2(char* string, const char* delims, char* *newStr)
michael@0 106 {
michael@0 107 PR_ASSERT(string);
michael@0 108
michael@0 109 char delimTable[DELIM_TABLE_SIZE];
michael@0 110 uint32_t i;
michael@0 111 char* result;
michael@0 112 char* str = string;
michael@0 113
michael@0 114 for (i = 0; i < DELIM_TABLE_SIZE; i++)
michael@0 115 delimTable[i] = '\0';
michael@0 116
michael@0 117 for (i = 0; delims[i]; i++) {
michael@0 118 SET_DELIM(delimTable, static_cast<uint8_t>(delims[i]));
michael@0 119 }
michael@0 120
michael@0 121 // skip to beginning
michael@0 122 while (*str && IS_DELIM(delimTable, static_cast<uint8_t>(*str))) {
michael@0 123 str++;
michael@0 124 }
michael@0 125 result = str;
michael@0 126
michael@0 127 // fix up the end of the token
michael@0 128 while (*str) {
michael@0 129 if (IS_DELIM(delimTable, static_cast<uint8_t>(*str))) {
michael@0 130 *str++ = '\0';
michael@0 131 break;
michael@0 132 }
michael@0 133 str++;
michael@0 134 }
michael@0 135 *newStr = str;
michael@0 136
michael@0 137 return str == result ? nullptr : result;
michael@0 138 }
michael@0 139
michael@0 140
michael@0 141
michael@0 142 enum client_auth_option {
michael@0 143 caNone = 0,
michael@0 144 caRequire = 1,
michael@0 145 caRequest = 2
michael@0 146 };
michael@0 147
michael@0 148 // Structs for passing data into jobs on the thread pool
michael@0 149 typedef struct {
michael@0 150 int32_t listen_port;
michael@0 151 string cert_nickname;
michael@0 152 PLHashTable* host_cert_table;
michael@0 153 PLHashTable* host_clientauth_table;
michael@0 154 PLHashTable* host_redir_table;
michael@0 155 } server_info_t;
michael@0 156
michael@0 157 typedef struct {
michael@0 158 PRFileDesc* client_sock;
michael@0 159 PRNetAddr client_addr;
michael@0 160 server_info_t* server_info;
michael@0 161 // the original host in the Host: header for this connection is
michael@0 162 // stored here, for proxied connections
michael@0 163 string original_host;
michael@0 164 // true if no SSL should be used for this connection
michael@0 165 bool http_proxy_only;
michael@0 166 // true if this connection is for a WebSocket
michael@0 167 bool iswebsocket;
michael@0 168 } connection_info_t;
michael@0 169
michael@0 170 typedef struct {
michael@0 171 string fullHost;
michael@0 172 bool matched;
michael@0 173 } server_match_t;
michael@0 174
michael@0 175 const int32_t BUF_SIZE = 16384;
michael@0 176 const int32_t BUF_MARGIN = 1024;
michael@0 177 const int32_t BUF_TOTAL = BUF_SIZE + BUF_MARGIN;
michael@0 178
michael@0 179 struct relayBuffer
michael@0 180 {
michael@0 181 char *buffer, *bufferhead, *buffertail, *bufferend;
michael@0 182
michael@0 183 relayBuffer()
michael@0 184 {
michael@0 185 // Leave 1024 bytes more for request line manipulations
michael@0 186 bufferhead = buffertail = buffer = new char[BUF_TOTAL];
michael@0 187 bufferend = buffer + BUF_SIZE;
michael@0 188 }
michael@0 189
michael@0 190 ~relayBuffer()
michael@0 191 {
michael@0 192 delete [] buffer;
michael@0 193 }
michael@0 194
michael@0 195 void compact() {
michael@0 196 if (buffertail == bufferhead)
michael@0 197 buffertail = bufferhead = buffer;
michael@0 198 }
michael@0 199
michael@0 200 bool empty() { return bufferhead == buffertail; }
michael@0 201 size_t areafree() { return bufferend - buffertail; }
michael@0 202 size_t margin() { return areafree() + BUF_MARGIN; }
michael@0 203 size_t present() { return buffertail - bufferhead; }
michael@0 204 };
michael@0 205
michael@0 206 // These numbers are multiplied by the number of listening ports (actual
michael@0 207 // servers running). According the thread pool implementation there is no
michael@0 208 // need to limit the number of threads initially, threads are allocated
michael@0 209 // dynamically and stored in a linked list. Initial number of 2 is chosen
michael@0 210 // to allocate a thread for socket accept and preallocate one for the first
michael@0 211 // connection that is with high probability expected to come.
michael@0 212 const uint32_t INITIAL_THREADS = 2;
michael@0 213 const uint32_t MAX_THREADS = 100;
michael@0 214 const uint32_t DEFAULT_STACKSIZE = (512 * 1024);
michael@0 215
michael@0 216 // global data
michael@0 217 string nssconfigdir;
michael@0 218 vector<server_info_t> servers;
michael@0 219 PRNetAddr remote_addr;
michael@0 220 PRNetAddr websocket_server;
michael@0 221 PRThreadPool* threads = nullptr;
michael@0 222 PRLock* shutdown_lock = nullptr;
michael@0 223 PRCondVar* shutdown_condvar = nullptr;
michael@0 224 // Not really used, unless something fails to start
michael@0 225 bool shutdown_server = false;
michael@0 226 bool do_http_proxy = false;
michael@0 227 bool any_host_spec_config = false;
michael@0 228
michael@0 229 int ClientAuthValueComparator(const void *v1, const void *v2)
michael@0 230 {
michael@0 231 int a = *static_cast<const client_auth_option*>(v1) -
michael@0 232 *static_cast<const client_auth_option*>(v2);
michael@0 233 if (a == 0)
michael@0 234 return 0;
michael@0 235 if (a > 0)
michael@0 236 return 1;
michael@0 237 else // (a < 0)
michael@0 238 return -1;
michael@0 239 }
michael@0 240
michael@0 241 static int match_hostname(PLHashEntry *he, int index, void* arg)
michael@0 242 {
michael@0 243 server_match_t *match = (server_match_t*)arg;
michael@0 244 if (match->fullHost.find((char*)he->key) != string::npos)
michael@0 245 match->matched = true;
michael@0 246 return HT_ENUMERATE_NEXT;
michael@0 247 }
michael@0 248
michael@0 249 /*
michael@0 250 * Signal the main thread that the application should shut down.
michael@0 251 */
michael@0 252 void SignalShutdown()
michael@0 253 {
michael@0 254 PR_Lock(shutdown_lock);
michael@0 255 PR_NotifyCondVar(shutdown_condvar);
michael@0 256 PR_Unlock(shutdown_lock);
michael@0 257 }
michael@0 258
michael@0 259 bool ReadConnectRequest(server_info_t* server_info,
michael@0 260 relayBuffer& buffer, int32_t* result, string& certificate,
michael@0 261 client_auth_option* clientauth, string& host, string& location)
michael@0 262 {
michael@0 263 if (buffer.present() < 4) {
michael@0 264 LOG_DEBUG((" !! only %d bytes present in the buffer", (int)buffer.present()));
michael@0 265 return false;
michael@0 266 }
michael@0 267 if (strncmp(buffer.buffertail-4, "\r\n\r\n", 4)) {
michael@0 268 LOG_ERRORD((" !! request is not tailed with CRLFCRLF but with %x %x %x %x",
michael@0 269 *(buffer.buffertail-4),
michael@0 270 *(buffer.buffertail-3),
michael@0 271 *(buffer.buffertail-2),
michael@0 272 *(buffer.buffertail-1)));
michael@0 273 return false;
michael@0 274 }
michael@0 275
michael@0 276 LOG_DEBUG((" parsing initial connect request, dump:\n%.*s\n", (int)buffer.present(), buffer.bufferhead));
michael@0 277
michael@0 278 *result = 400;
michael@0 279
michael@0 280 char* token;
michael@0 281 char* _caret;
michael@0 282 token = strtok2(buffer.bufferhead, " ", &_caret);
michael@0 283 if (!token) {
michael@0 284 LOG_ERRORD((" no space found"));
michael@0 285 return true;
michael@0 286 }
michael@0 287 if (strcmp(token, "CONNECT")) {
michael@0 288 LOG_ERRORD((" not CONNECT request but %s", token));
michael@0 289 return true;
michael@0 290 }
michael@0 291
michael@0 292 token = strtok2(_caret, " ", &_caret);
michael@0 293 void* c = PL_HashTableLookup(server_info->host_cert_table, token);
michael@0 294 if (c)
michael@0 295 certificate = static_cast<char*>(c);
michael@0 296
michael@0 297 host = "https://";
michael@0 298 host += token;
michael@0 299
michael@0 300 c = PL_HashTableLookup(server_info->host_clientauth_table, token);
michael@0 301 if (c)
michael@0 302 *clientauth = *static_cast<client_auth_option*>(c);
michael@0 303 else
michael@0 304 *clientauth = caNone;
michael@0 305
michael@0 306 void *redir = PL_HashTableLookup(server_info->host_redir_table, token);
michael@0 307 if (redir)
michael@0 308 location = static_cast<char*>(redir);
michael@0 309
michael@0 310 token = strtok2(_caret, "/", &_caret);
michael@0 311 if (strcmp(token, "HTTP")) {
michael@0 312 LOG_ERRORD((" not tailed with HTTP but with %s", token));
michael@0 313 return true;
michael@0 314 }
michael@0 315
michael@0 316 *result = (redir) ? 302 : 200;
michael@0 317 return true;
michael@0 318 }
michael@0 319
michael@0 320 bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, string &certificate, client_auth_option clientAuth)
michael@0 321 {
michael@0 322 const char* certnick = certificate.empty() ?
michael@0 323 si->cert_nickname.c_str() : certificate.c_str();
michael@0 324
michael@0 325 ScopedCERTCertificate cert(PK11_FindCertFromNickname(certnick, nullptr));
michael@0 326 if (!cert) {
michael@0 327 LOG_ERROR(("Failed to find cert %s\n", certnick));
michael@0 328 return false;
michael@0 329 }
michael@0 330
michael@0 331 ScopedSECKEYPrivateKey privKey(PK11_FindKeyByAnyCert(cert, nullptr));
michael@0 332 if (!privKey) {
michael@0 333 LOG_ERROR(("Failed to find private key\n"));
michael@0 334 return false;
michael@0 335 }
michael@0 336
michael@0 337 PRFileDesc* ssl_socket = SSL_ImportFD(nullptr, socket);
michael@0 338 if (!ssl_socket) {
michael@0 339 LOG_ERROR(("Error importing SSL socket\n"));
michael@0 340 return false;
michael@0 341 }
michael@0 342
michael@0 343 SSLKEAType certKEA = NSS_FindCertKEAType(cert);
michael@0 344 if (SSL_ConfigSecureServer(ssl_socket, cert, privKey, certKEA)
michael@0 345 != SECSuccess) {
michael@0 346 LOG_ERROR(("Error configuring SSL server socket\n"));
michael@0 347 return false;
michael@0 348 }
michael@0 349
michael@0 350 SSL_OptionSet(ssl_socket, SSL_SECURITY, true);
michael@0 351 SSL_OptionSet(ssl_socket, SSL_HANDSHAKE_AS_CLIENT, false);
michael@0 352 SSL_OptionSet(ssl_socket, SSL_HANDSHAKE_AS_SERVER, true);
michael@0 353
michael@0 354 if (clientAuth != caNone)
michael@0 355 {
michael@0 356 SSL_OptionSet(ssl_socket, SSL_REQUEST_CERTIFICATE, true);
michael@0 357 SSL_OptionSet(ssl_socket, SSL_REQUIRE_CERTIFICATE, clientAuth == caRequire);
michael@0 358 }
michael@0 359
michael@0 360 SSL_ResetHandshake(ssl_socket, true);
michael@0 361
michael@0 362 return true;
michael@0 363 }
michael@0 364
michael@0 365 /**
michael@0 366 * This function examines the buffer for a Sec-WebSocket-Location: field,
michael@0 367 * and if it's present, it replaces the hostname in that field with the
michael@0 368 * value in the server's original_host field. This function works
michael@0 369 * in the reverse direction as AdjustWebSocketHost(), replacing the real
michael@0 370 * hostname of a response with the potentially fake hostname that is expected
michael@0 371 * by the browser (e.g., mochi.test).
michael@0 372 *
michael@0 373 * @return true if the header was adjusted successfully, or not found, false
michael@0 374 * if the header is present but the url is not, which should indicate
michael@0 375 * that more data needs to be read from the socket
michael@0 376 */
michael@0 377 bool AdjustWebSocketLocation(relayBuffer& buffer, connection_info_t *ci)
michael@0 378 {
michael@0 379 assert(buffer.margin());
michael@0 380 buffer.buffertail[1] = '\0';
michael@0 381
michael@0 382 char* wsloc = strstr(buffer.bufferhead, "Sec-WebSocket-Location:");
michael@0 383 if (!wsloc)
michael@0 384 return true;
michael@0 385 // advance pointer to the start of the hostname
michael@0 386 wsloc = strstr(wsloc, "ws://");
michael@0 387 if (!wsloc)
michael@0 388 return false;
michael@0 389 wsloc += 5;
michael@0 390 // find the end of the hostname
michael@0 391 char* wslocend = strchr(wsloc + 1, '/');
michael@0 392 if (!wslocend)
michael@0 393 return false;
michael@0 394 char *crlf = strstr(wsloc, "\r\n");
michael@0 395 if (!crlf)
michael@0 396 return false;
michael@0 397 if (ci->original_host.empty())
michael@0 398 return true;
michael@0 399
michael@0 400 int diff = ci->original_host.length() - (wslocend-wsloc);
michael@0 401 if (diff > 0)
michael@0 402 assert(size_t(diff) <= buffer.margin());
michael@0 403 memmove(wslocend + diff, wslocend, buffer.buffertail - wsloc - diff);
michael@0 404 buffer.buffertail += diff;
michael@0 405
michael@0 406 memcpy(wsloc, ci->original_host.c_str(), ci->original_host.length());
michael@0 407 return true;
michael@0 408 }
michael@0 409
michael@0 410 /**
michael@0 411 * This function examines the buffer for a Host: field, and if it's present,
michael@0 412 * it replaces the hostname in that field with the hostname in the server's
michael@0 413 * remote_addr field. This is needed because proxy requests may be coming
michael@0 414 * from mochitest with fake hosts, like mochi.test, and these need to be
michael@0 415 * replaced with the host that the destination server is actually running
michael@0 416 * on.
michael@0 417 */
michael@0 418 bool AdjustWebSocketHost(relayBuffer& buffer, connection_info_t *ci)
michael@0 419 {
michael@0 420 const char HEADER_UPGRADE[] = "Upgrade:";
michael@0 421 const char HEADER_HOST[] = "Host:";
michael@0 422
michael@0 423 PRNetAddr inet_addr = (websocket_server.inet.port ? websocket_server :
michael@0 424 remote_addr);
michael@0 425
michael@0 426 assert(buffer.margin());
michael@0 427
michael@0 428 // Cannot use strnchr so add a null char at the end. There is always some
michael@0 429 // space left because we preserve a margin.
michael@0 430 buffer.buffertail[1] = '\0';
michael@0 431
michael@0 432 // Verify this is a WebSocket header.
michael@0 433 char* h1 = strstr(buffer.bufferhead, HEADER_UPGRADE);
michael@0 434 if (!h1)
michael@0 435 return false;
michael@0 436 h1 += strlen(HEADER_UPGRADE);
michael@0 437 h1 += strspn(h1, " \t");
michael@0 438 char* h2 = strstr(h1, "WebSocket\r\n");
michael@0 439 if (!h2) h2 = strstr(h1, "websocket\r\n");
michael@0 440 if (!h2) h2 = strstr(h1, "Websocket\r\n");
michael@0 441 if (!h2)
michael@0 442 return false;
michael@0 443
michael@0 444 char* host = strstr(buffer.bufferhead, HEADER_HOST);
michael@0 445 if (!host)
michael@0 446 return false;
michael@0 447 // advance pointer to beginning of hostname
michael@0 448 host += strlen(HEADER_HOST);
michael@0 449 host += strspn(host, " \t");
michael@0 450
michael@0 451 char* endhost = strstr(host, "\r\n");
michael@0 452 if (!endhost)
michael@0 453 return false;
michael@0 454
michael@0 455 // Save the original host, so we can use it later on responses from the
michael@0 456 // server.
michael@0 457 ci->original_host.assign(host, endhost-host);
michael@0 458
michael@0 459 char newhost[40];
michael@0 460 PR_NetAddrToString(&inet_addr, newhost, sizeof(newhost));
michael@0 461 assert(strlen(newhost) < sizeof(newhost) - 7);
michael@0 462 sprintf(newhost, "%s:%d", newhost, PR_ntohs(inet_addr.inet.port));
michael@0 463
michael@0 464 int diff = strlen(newhost) - (endhost-host);
michael@0 465 if (diff > 0)
michael@0 466 assert(size_t(diff) <= buffer.margin());
michael@0 467 memmove(endhost + diff, endhost, buffer.buffertail - host - diff);
michael@0 468 buffer.buffertail += diff;
michael@0 469
michael@0 470 memcpy(host, newhost, strlen(newhost));
michael@0 471 return true;
michael@0 472 }
michael@0 473
michael@0 474 /**
michael@0 475 * This function prefixes Request-URI path with a full scheme-host-port
michael@0 476 * string.
michael@0 477 */
michael@0 478 bool AdjustRequestURI(relayBuffer& buffer, string *host)
michael@0 479 {
michael@0 480 assert(buffer.margin());
michael@0 481
michael@0 482 // Cannot use strnchr so add a null char at the end. There is always some space left
michael@0 483 // because we preserve a margin.
michael@0 484 buffer.buffertail[1] = '\0';
michael@0 485 LOG_DEBUG((" incoming request to adjust:\n%s\n", buffer.bufferhead));
michael@0 486
michael@0 487 char *token, *path;
michael@0 488 path = strchr(buffer.bufferhead, ' ') + 1;
michael@0 489 if (!path)
michael@0 490 return false;
michael@0 491
michael@0 492 // If the path doesn't start with a slash don't change it, it is probably '*' or a full
michael@0 493 // path already. Return true, we are done with this request adjustment.
michael@0 494 if (*path != '/')
michael@0 495 return true;
michael@0 496
michael@0 497 token = strchr(path, ' ') + 1;
michael@0 498 if (!token)
michael@0 499 return false;
michael@0 500
michael@0 501 if (strncmp(token, "HTTP/", 5))
michael@0 502 return false;
michael@0 503
michael@0 504 size_t hostlength = host->length();
michael@0 505 assert(hostlength <= buffer.margin());
michael@0 506
michael@0 507 memmove(path + hostlength, path, buffer.buffertail - path);
michael@0 508 memcpy(path, host->c_str(), hostlength);
michael@0 509 buffer.buffertail += hostlength;
michael@0 510
michael@0 511 return true;
michael@0 512 }
michael@0 513
michael@0 514 bool ConnectSocket(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
michael@0 515 {
michael@0 516 PRStatus stat = PR_Connect(fd, addr, timeout);
michael@0 517 if (stat != PR_SUCCESS)
michael@0 518 return false;
michael@0 519
michael@0 520 PRSocketOptionData option;
michael@0 521 option.option = PR_SockOpt_Nonblocking;
michael@0 522 option.value.non_blocking = true;
michael@0 523 PR_SetSocketOption(fd, &option);
michael@0 524
michael@0 525 return true;
michael@0 526 }
michael@0 527
michael@0 528 /*
michael@0 529 * Handle an incoming client connection. The server thread has already
michael@0 530 * accepted the connection, so we just need to connect to the remote
michael@0 531 * port and then proxy data back and forth.
michael@0 532 * The data parameter is a connection_info_t*, and must be deleted
michael@0 533 * by this function.
michael@0 534 */
michael@0 535 void HandleConnection(void* data)
michael@0 536 {
michael@0 537 connection_info_t* ci = static_cast<connection_info_t*>(data);
michael@0 538 PRIntervalTime connect_timeout = PR_SecondsToInterval(30);
michael@0 539
michael@0 540 ScopedPRFileDesc other_sock(PR_NewTCPSocket());
michael@0 541 bool client_done = false;
michael@0 542 bool client_error = false;
michael@0 543 bool connect_accepted = !do_http_proxy;
michael@0 544 bool ssl_updated = !do_http_proxy;
michael@0 545 bool expect_request_start = do_http_proxy;
michael@0 546 string certificateToUse;
michael@0 547 string locationHeader;
michael@0 548 client_auth_option clientAuth;
michael@0 549 string fullHost;
michael@0 550
michael@0 551 LOG_DEBUG(("SSLTUNNEL(%p)): incoming connection csock(0)=%p, ssock(1)=%p\n",
michael@0 552 static_cast<void*>(data),
michael@0 553 static_cast<void*>(ci->client_sock),
michael@0 554 static_cast<void*>(other_sock)));
michael@0 555 if (other_sock)
michael@0 556 {
michael@0 557 int32_t numberOfSockets = 1;
michael@0 558
michael@0 559 relayBuffer buffers[2];
michael@0 560
michael@0 561 if (!do_http_proxy)
michael@0 562 {
michael@0 563 if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone))
michael@0 564 client_error = true;
michael@0 565 else if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
michael@0 566 client_error = true;
michael@0 567 else
michael@0 568 numberOfSockets = 2;
michael@0 569 }
michael@0 570
michael@0 571 PRPollDesc sockets[2] =
michael@0 572 {
michael@0 573 {ci->client_sock, PR_POLL_READ, 0},
michael@0 574 {other_sock, PR_POLL_READ, 0}
michael@0 575 };
michael@0 576 bool socketErrorState[2] = {false, false};
michael@0 577
michael@0 578 while (!((client_error||client_done) && buffers[0].empty() && buffers[1].empty()))
michael@0 579 {
michael@0 580 sockets[0].in_flags |= PR_POLL_EXCEPT;
michael@0 581 sockets[1].in_flags |= PR_POLL_EXCEPT;
michael@0 582 LOG_DEBUG(("SSLTUNNEL(%p)): polling flags csock(0)=%c%c, ssock(1)=%c%c\n",
michael@0 583 static_cast<void*>(data),
michael@0 584 sockets[0].in_flags & PR_POLL_READ ? 'R' : '-',
michael@0 585 sockets[0].in_flags & PR_POLL_WRITE ? 'W' : '-',
michael@0 586 sockets[1].in_flags & PR_POLL_READ ? 'R' : '-',
michael@0 587 sockets[1].in_flags & PR_POLL_WRITE ? 'W' : '-'));
michael@0 588 int32_t pollStatus = PR_Poll(sockets, numberOfSockets, PR_MillisecondsToInterval(1000));
michael@0 589 if (pollStatus < 0)
michael@0 590 {
michael@0 591 LOG_DEBUG(("SSLTUNNEL(%p)): pollStatus=%d, exiting\n",
michael@0 592 static_cast<void*>(data), pollStatus));
michael@0 593 client_error = true;
michael@0 594 break;
michael@0 595 }
michael@0 596
michael@0 597 if (pollStatus == 0)
michael@0 598 {
michael@0 599 // timeout
michael@0 600 LOG_DEBUG(("SSLTUNNEL(%p)): poll timeout, looping\n",
michael@0 601 static_cast<void*>(data)));
michael@0 602 continue;
michael@0 603 }
michael@0 604
michael@0 605 for (int32_t s = 0; s < numberOfSockets; ++s)
michael@0 606 {
michael@0 607 int32_t s2 = s == 1 ? 0 : 1;
michael@0 608 int16_t out_flags = sockets[s].out_flags;
michael@0 609 int16_t &in_flags = sockets[s].in_flags;
michael@0 610 int16_t &in_flags2 = sockets[s2].in_flags;
michael@0 611 sockets[s].out_flags = 0;
michael@0 612
michael@0 613 LOG_BEGIN_BLOCK();
michael@0 614 LOG_DEBUG(("SSLTUNNEL(%p)): %csock(%d)=%p out_flags=%d",
michael@0 615 static_cast<void*>(data),
michael@0 616 s == 0 ? 'c' : 's',
michael@0 617 s,
michael@0 618 static_cast<void*>(sockets[s].fd),
michael@0 619 out_flags));
michael@0 620 if (out_flags & (PR_POLL_EXCEPT | PR_POLL_ERR | PR_POLL_HUP))
michael@0 621 {
michael@0 622 LOG_DEBUG((" :exception\n"));
michael@0 623 client_error = true;
michael@0 624 socketErrorState[s] = true;
michael@0 625 // We got a fatal error state on the socket. Clear the output buffer
michael@0 626 // for this socket to break the main loop, we will never more be able
michael@0 627 // to send those data anyway.
michael@0 628 buffers[s2].bufferhead = buffers[s2].buffertail = buffers[s2].buffer;
michael@0 629 continue;
michael@0 630 } // PR_POLL_EXCEPT, PR_POLL_ERR, PR_POLL_HUP handling
michael@0 631
michael@0 632 if (out_flags & PR_POLL_READ && !buffers[s].areafree())
michael@0 633 {
michael@0 634 LOG_DEBUG((" no place in read buffer but got read flag, dropping it now!"));
michael@0 635 in_flags &= ~PR_POLL_READ;
michael@0 636 }
michael@0 637
michael@0 638 if (out_flags & PR_POLL_READ && buffers[s].areafree())
michael@0 639 {
michael@0 640 LOG_DEBUG((" :reading"));
michael@0 641 int32_t bytesRead = PR_Recv(sockets[s].fd, buffers[s].buffertail,
michael@0 642 buffers[s].areafree(), 0, PR_INTERVAL_NO_TIMEOUT);
michael@0 643
michael@0 644 if (bytesRead == 0)
michael@0 645 {
michael@0 646 LOG_DEBUG((" socket gracefully closed"));
michael@0 647 client_done = true;
michael@0 648 in_flags &= ~PR_POLL_READ;
michael@0 649 }
michael@0 650 else if (bytesRead < 0)
michael@0 651 {
michael@0 652 if (PR_GetError() != PR_WOULD_BLOCK_ERROR)
michael@0 653 {
michael@0 654 LOG_DEBUG((" error=%d", PR_GetError()));
michael@0 655 // We are in error state, indicate that the connection was
michael@0 656 // not closed gracefully
michael@0 657 client_error = true;
michael@0 658 socketErrorState[s] = true;
michael@0 659 // Wipe out our send buffer, we cannot send it anyway.
michael@0 660 buffers[s2].bufferhead = buffers[s2].buffertail = buffers[s2].buffer;
michael@0 661 }
michael@0 662 else
michael@0 663 LOG_DEBUG((" would block"));
michael@0 664 }
michael@0 665 else
michael@0 666 {
michael@0 667 // If the other socket is in error state (unable to send/receive)
michael@0 668 // throw this data away and continue loop
michael@0 669 if (socketErrorState[s2])
michael@0 670 {
michael@0 671 LOG_DEBUG((" have read but other socket is in error state\n"));
michael@0 672 continue;
michael@0 673 }
michael@0 674
michael@0 675 buffers[s].buffertail += bytesRead;
michael@0 676 LOG_DEBUG((", read %d bytes", bytesRead));
michael@0 677
michael@0 678 // We have to accept and handle the initial CONNECT request here
michael@0 679 int32_t response;
michael@0 680 if (!connect_accepted && ReadConnectRequest(ci->server_info, buffers[s],
michael@0 681 &response, certificateToUse, &clientAuth, fullHost, locationHeader))
michael@0 682 {
michael@0 683 // Mark this as a proxy-only connection (no SSL) if the CONNECT
michael@0 684 // request didn't come for port 443 or from any of the server's
michael@0 685 // cert or clientauth hostnames.
michael@0 686 if (fullHost.find(":443") == string::npos)
michael@0 687 {
michael@0 688 server_match_t match;
michael@0 689 match.fullHost = fullHost;
michael@0 690 match.matched = false;
michael@0 691 PL_HashTableEnumerateEntries(ci->server_info->host_cert_table,
michael@0 692 match_hostname,
michael@0 693 &match);
michael@0 694 PL_HashTableEnumerateEntries(ci->server_info->host_clientauth_table,
michael@0 695 match_hostname,
michael@0 696 &match);
michael@0 697 ci->http_proxy_only = !match.matched;
michael@0 698 }
michael@0 699 else
michael@0 700 {
michael@0 701 ci->http_proxy_only = false;
michael@0 702 }
michael@0 703
michael@0 704 // Clean the request as it would be read
michael@0 705 buffers[s].bufferhead = buffers[s].buffertail = buffers[s].buffer;
michael@0 706 in_flags |= PR_POLL_WRITE;
michael@0 707 connect_accepted = true;
michael@0 708
michael@0 709 // Store response to the oposite buffer
michael@0 710 if (response == 200)
michael@0 711 {
michael@0 712 LOG_DEBUG((" accepted CONNECT request, connected to the server, sending OK to the client\n"));
michael@0 713 strcpy(buffers[s2].buffer, "HTTP/1.1 200 Connected\r\nConnection: keep-alive\r\n\r\n");
michael@0 714 }
michael@0 715 else if (response == 302)
michael@0 716 {
michael@0 717 LOG_DEBUG((" accepted CONNECT request with redirection, "
michael@0 718 "sending location and 302 to the client\n"));
michael@0 719 client_done = true;
michael@0 720 sprintf(buffers[s2].buffer,
michael@0 721 "HTTP/1.1 302 Moved\r\n"
michael@0 722 "Location: https://%s/\r\n"
michael@0 723 "Connection: close\r\n\r\n",
michael@0 724 locationHeader.c_str());
michael@0 725 }
michael@0 726 else
michael@0 727 {
michael@0 728 LOG_ERRORD((" could not read the connect request, closing connection with %d", response));
michael@0 729 client_done = true;
michael@0 730 sprintf(buffers[s2].buffer, "HTTP/1.1 %d ERROR\r\nConnection: close\r\n\r\n", response);
michael@0 731
michael@0 732 break;
michael@0 733 }
michael@0 734
michael@0 735 buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer);
michael@0 736
michael@0 737 // Send the response to the client socket
michael@0 738 break;
michael@0 739 } // end of CONNECT handling
michael@0 740
michael@0 741 if (!buffers[s].areafree())
michael@0 742 {
michael@0 743 // Do not poll for read when the buffer is full
michael@0 744 LOG_DEBUG((" no place in our read buffer, stop reading"));
michael@0 745 in_flags &= ~PR_POLL_READ;
michael@0 746 }
michael@0 747
michael@0 748 if (ssl_updated)
michael@0 749 {
michael@0 750 if (s == 0 && expect_request_start)
michael@0 751 {
michael@0 752 if (!strstr(buffers[s].bufferhead, "\r\n\r\n"))
michael@0 753 {
michael@0 754 // We haven't received the complete header yet, so wait.
michael@0 755 continue;
michael@0 756 }
michael@0 757 else
michael@0 758 {
michael@0 759 ci->iswebsocket = AdjustWebSocketHost(buffers[s], ci);
michael@0 760 expect_request_start = !(ci->iswebsocket ||
michael@0 761 AdjustRequestURI(buffers[s], &fullHost));
michael@0 762 PRNetAddr* addr = &remote_addr;
michael@0 763 if (ci->iswebsocket && websocket_server.inet.port)
michael@0 764 addr = &websocket_server;
michael@0 765 if (!ConnectSocket(other_sock, addr, connect_timeout))
michael@0 766 {
michael@0 767 LOG_ERRORD((" could not open connection to the real server\n"));
michael@0 768 client_error = true;
michael@0 769 break;
michael@0 770 }
michael@0 771 LOG_DEBUG(("\n connected to remote server\n"));
michael@0 772 numberOfSockets = 2;
michael@0 773 }
michael@0 774 }
michael@0 775 else if (s == 1 && ci->iswebsocket)
michael@0 776 {
michael@0 777 if (!AdjustWebSocketLocation(buffers[s], ci))
michael@0 778 continue;
michael@0 779 }
michael@0 780
michael@0 781 in_flags2 |= PR_POLL_WRITE;
michael@0 782 LOG_DEBUG((" telling the other socket to write"));
michael@0 783 }
michael@0 784 else
michael@0 785 LOG_DEBUG((" we have something for the other socket to write, but ssl has not been administered on it"));
michael@0 786 }
michael@0 787 } // PR_POLL_READ handling
michael@0 788
michael@0 789 if (out_flags & PR_POLL_WRITE)
michael@0 790 {
michael@0 791 LOG_DEBUG((" :writing"));
michael@0 792 int32_t bytesWrite = PR_Send(sockets[s].fd, buffers[s2].bufferhead,
michael@0 793 buffers[s2].present(), 0, PR_INTERVAL_NO_TIMEOUT);
michael@0 794
michael@0 795 if (bytesWrite < 0)
michael@0 796 {
michael@0 797 if (PR_GetError() != PR_WOULD_BLOCK_ERROR) {
michael@0 798 LOG_DEBUG((" error=%d", PR_GetError()));
michael@0 799 client_error = true;
michael@0 800 socketErrorState[s] = true;
michael@0 801 // We got a fatal error while writting the buffer. Clear it to break
michael@0 802 // the main loop, we will never more be able to send it.
michael@0 803 buffers[s2].bufferhead = buffers[s2].buffertail = buffers[s2].buffer;
michael@0 804 }
michael@0 805 else
michael@0 806 LOG_DEBUG((" would block"));
michael@0 807 }
michael@0 808 else
michael@0 809 {
michael@0 810 LOG_DEBUG((", written %d bytes", bytesWrite));
michael@0 811 buffers[s2].buffertail[1] = '\0';
michael@0 812 LOG_DEBUG((" dump:\n%.*s\n", bytesWrite, buffers[s2].bufferhead));
michael@0 813
michael@0 814 buffers[s2].bufferhead += bytesWrite;
michael@0 815 if (buffers[s2].present())
michael@0 816 {
michael@0 817 LOG_DEBUG((" still have to write %d bytes", (int)buffers[s2].present()));
michael@0 818 in_flags |= PR_POLL_WRITE;
michael@0 819 }
michael@0 820 else
michael@0 821 {
michael@0 822 if (!ssl_updated)
michael@0 823 {
michael@0 824 LOG_DEBUG((" proxy response sent to the client"));
michael@0 825 // Proxy response has just been writen, update to ssl
michael@0 826 ssl_updated = true;
michael@0 827 if (ci->http_proxy_only)
michael@0 828 {
michael@0 829 LOG_DEBUG((" not updating to SSL based on http_proxy_only for this socket"));
michael@0 830 }
michael@0 831 else if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info,
michael@0 832 certificateToUse, clientAuth))
michael@0 833 {
michael@0 834 LOG_ERRORD((" failed to config server socket\n"));
michael@0 835 client_error = true;
michael@0 836 break;
michael@0 837 }
michael@0 838 else
michael@0 839 {
michael@0 840 LOG_DEBUG((" client socket updated to SSL"));
michael@0 841 }
michael@0 842 } // sslUpdate
michael@0 843
michael@0 844 LOG_DEBUG((" dropping our write flag and setting other socket read flag"));
michael@0 845 in_flags &= ~PR_POLL_WRITE;
michael@0 846 in_flags2 |= PR_POLL_READ;
michael@0 847 buffers[s2].compact();
michael@0 848 }
michael@0 849 }
michael@0 850 } // PR_POLL_WRITE handling
michael@0 851 LOG_END_BLOCK(); // end the log
michael@0 852 } // for...
michael@0 853 } // while, poll
michael@0 854 }
michael@0 855 else
michael@0 856 client_error = true;
michael@0 857
michael@0 858 LOG_DEBUG(("SSLTUNNEL(%p)): exiting root function for csock=%p, ssock=%p\n",
michael@0 859 static_cast<void*>(data),
michael@0 860 static_cast<void*>(ci->client_sock),
michael@0 861 static_cast<void*>(other_sock)));
michael@0 862 if (!client_error)
michael@0 863 PR_Shutdown(ci->client_sock, PR_SHUTDOWN_SEND);
michael@0 864 PR_Close(ci->client_sock);
michael@0 865
michael@0 866 delete ci;
michael@0 867 }
michael@0 868
michael@0 869 /*
michael@0 870 * Start listening for SSL connections on a specified port, handing
michael@0 871 * them off to client threads after accepting the connection.
michael@0 872 * The data parameter is a server_info_t*, owned by the calling
michael@0 873 * function.
michael@0 874 */
michael@0 875 void StartServer(void* data)
michael@0 876 {
michael@0 877 server_info_t* si = static_cast<server_info_t*>(data);
michael@0 878
michael@0 879 //TODO: select ciphers?
michael@0 880 ScopedPRFileDesc listen_socket(PR_NewTCPSocket());
michael@0 881 if (!listen_socket) {
michael@0 882 LOG_ERROR(("failed to create socket\n"));
michael@0 883 SignalShutdown();
michael@0 884 return;
michael@0 885 }
michael@0 886
michael@0 887 // In case the socket is still open in the TIME_WAIT state from a previous
michael@0 888 // instance of ssltunnel we ask to reuse the port.
michael@0 889 PRSocketOptionData socket_option;
michael@0 890 socket_option.option = PR_SockOpt_Reuseaddr;
michael@0 891 socket_option.value.reuse_addr = true;
michael@0 892 PR_SetSocketOption(listen_socket, &socket_option);
michael@0 893
michael@0 894 PRNetAddr server_addr;
michael@0 895 PR_InitializeNetAddr(PR_IpAddrAny, si->listen_port, &server_addr);
michael@0 896 if (PR_Bind(listen_socket, &server_addr) != PR_SUCCESS) {
michael@0 897 LOG_ERROR(("failed to bind socket\n"));
michael@0 898 SignalShutdown();
michael@0 899 return;
michael@0 900 }
michael@0 901
michael@0 902 if (PR_Listen(listen_socket, 1) != PR_SUCCESS) {
michael@0 903 LOG_ERROR(("failed to listen on socket\n"));
michael@0 904 SignalShutdown();
michael@0 905 return;
michael@0 906 }
michael@0 907
michael@0 908 LOG_INFO(("Server listening on port %d with cert %s\n", si->listen_port,
michael@0 909 si->cert_nickname.c_str()));
michael@0 910
michael@0 911 while (!shutdown_server) {
michael@0 912 connection_info_t* ci = new connection_info_t();
michael@0 913 ci->server_info = si;
michael@0 914 ci->http_proxy_only = do_http_proxy;
michael@0 915 // block waiting for connections
michael@0 916 ci->client_sock = PR_Accept(listen_socket, &ci->client_addr,
michael@0 917 PR_INTERVAL_NO_TIMEOUT);
michael@0 918
michael@0 919 PRSocketOptionData option;
michael@0 920 option.option = PR_SockOpt_Nonblocking;
michael@0 921 option.value.non_blocking = true;
michael@0 922 PR_SetSocketOption(ci->client_sock, &option);
michael@0 923
michael@0 924 if (ci->client_sock)
michael@0 925 // Not actually using this PRJob*...
michael@0 926 //PRJob* job =
michael@0 927 PR_QueueJob(threads, HandleConnection, ci, true);
michael@0 928 else
michael@0 929 delete ci;
michael@0 930 }
michael@0 931 }
michael@0 932
michael@0 933 // bogus password func, just don't use passwords. :-P
michael@0 934 char* password_func(PK11SlotInfo* slot, PRBool retry, void* arg)
michael@0 935 {
michael@0 936 if (retry)
michael@0 937 return nullptr;
michael@0 938
michael@0 939 return PL_strdup("");
michael@0 940 }
michael@0 941
michael@0 942 server_info_t* findServerInfo(int portnumber)
michael@0 943 {
michael@0 944 for (vector<server_info_t>::iterator it = servers.begin();
michael@0 945 it != servers.end(); it++)
michael@0 946 {
michael@0 947 if (it->listen_port == portnumber)
michael@0 948 return &(*it);
michael@0 949 }
michael@0 950
michael@0 951 return nullptr;
michael@0 952 }
michael@0 953
michael@0 954 int processConfigLine(char* configLine)
michael@0 955 {
michael@0 956 if (*configLine == 0 || *configLine == '#')
michael@0 957 return 0;
michael@0 958
michael@0 959 char* _caret;
michael@0 960 char* keyword = strtok2(configLine, ":", &_caret);
michael@0 961
michael@0 962 // Configure usage of http/ssl tunneling proxy behavior
michael@0 963 if (!strcmp(keyword, "httpproxy"))
michael@0 964 {
michael@0 965 char* value = strtok2(_caret, ":", &_caret);
michael@0 966 if (!strcmp(value, "1"))
michael@0 967 do_http_proxy = true;
michael@0 968
michael@0 969 return 0;
michael@0 970 }
michael@0 971
michael@0 972 if (!strcmp(keyword, "websocketserver"))
michael@0 973 {
michael@0 974 char* ipstring = strtok2(_caret, ":", &_caret);
michael@0 975 if (PR_StringToNetAddr(ipstring, &websocket_server) != PR_SUCCESS) {
michael@0 976 LOG_ERROR(("Invalid IP address in proxy config: %s\n", ipstring));
michael@0 977 return 1;
michael@0 978 }
michael@0 979 char* remoteport = strtok2(_caret, ":", &_caret);
michael@0 980 int port = atoi(remoteport);
michael@0 981 if (port <= 0) {
michael@0 982 LOG_ERROR(("Invalid remote port in proxy config: %s\n", remoteport));
michael@0 983 return 1;
michael@0 984 }
michael@0 985 websocket_server.inet.port = PR_htons(port);
michael@0 986 return 0;
michael@0 987 }
michael@0 988
michael@0 989 // Configure the forward address of the target server
michael@0 990 if (!strcmp(keyword, "forward"))
michael@0 991 {
michael@0 992 char* ipstring = strtok2(_caret, ":", &_caret);
michael@0 993 if (PR_StringToNetAddr(ipstring, &remote_addr) != PR_SUCCESS) {
michael@0 994 LOG_ERROR(("Invalid remote IP address: %s\n", ipstring));
michael@0 995 return 1;
michael@0 996 }
michael@0 997 char* serverportstring = strtok2(_caret, ":", &_caret);
michael@0 998 int port = atoi(serverportstring);
michael@0 999 if (port <= 0) {
michael@0 1000 LOG_ERROR(("Invalid remote port: %s\n", serverportstring));
michael@0 1001 return 1;
michael@0 1002 }
michael@0 1003 remote_addr.inet.port = PR_htons(port);
michael@0 1004
michael@0 1005 return 0;
michael@0 1006 }
michael@0 1007
michael@0 1008 // Configure all listen sockets and port+certificate bindings
michael@0 1009 if (!strcmp(keyword, "listen"))
michael@0 1010 {
michael@0 1011 char* hostname = strtok2(_caret, ":", &_caret);
michael@0 1012 char* hostportstring = nullptr;
michael@0 1013 if (strcmp(hostname, "*"))
michael@0 1014 {
michael@0 1015 any_host_spec_config = true;
michael@0 1016 hostportstring = strtok2(_caret, ":", &_caret);
michael@0 1017 }
michael@0 1018
michael@0 1019 char* serverportstring = strtok2(_caret, ":", &_caret);
michael@0 1020 char* certnick = strtok2(_caret, ":", &_caret);
michael@0 1021
michael@0 1022 int port = atoi(serverportstring);
michael@0 1023 if (port <= 0) {
michael@0 1024 LOG_ERROR(("Invalid port specified: %s\n", serverportstring));
michael@0 1025 return 1;
michael@0 1026 }
michael@0 1027
michael@0 1028 if (server_info_t* existingServer = findServerInfo(port))
michael@0 1029 {
michael@0 1030 char *certnick_copy = new char[strlen(certnick)+1];
michael@0 1031 char *hostname_copy = new char[strlen(hostname)+strlen(hostportstring)+2];
michael@0 1032
michael@0 1033 strcpy(hostname_copy, hostname);
michael@0 1034 strcat(hostname_copy, ":");
michael@0 1035 strcat(hostname_copy, hostportstring);
michael@0 1036 strcpy(certnick_copy, certnick);
michael@0 1037
michael@0 1038 PLHashEntry* entry = PL_HashTableAdd(existingServer->host_cert_table, hostname_copy, certnick_copy);
michael@0 1039 if (!entry) {
michael@0 1040 LOG_ERROR(("Out of memory"));
michael@0 1041 return 1;
michael@0 1042 }
michael@0 1043 }
michael@0 1044 else
michael@0 1045 {
michael@0 1046 server_info_t server;
michael@0 1047 server.cert_nickname = certnick;
michael@0 1048 server.listen_port = port;
michael@0 1049 server.host_cert_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
michael@0 1050 PL_CompareStrings, nullptr, nullptr);
michael@0 1051 if (!server.host_cert_table)
michael@0 1052 {
michael@0 1053 LOG_ERROR(("Internal, could not create hash table\n"));
michael@0 1054 return 1;
michael@0 1055 }
michael@0 1056 server.host_clientauth_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
michael@0 1057 ClientAuthValueComparator, nullptr, nullptr);
michael@0 1058 if (!server.host_clientauth_table)
michael@0 1059 {
michael@0 1060 LOG_ERROR(("Internal, could not create hash table\n"));
michael@0 1061 return 1;
michael@0 1062 }
michael@0 1063 server.host_redir_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
michael@0 1064 PL_CompareStrings, nullptr, nullptr);
michael@0 1065 if (!server.host_redir_table)
michael@0 1066 {
michael@0 1067 LOG_ERROR(("Internal, could not create hash table\n"));
michael@0 1068 return 1;
michael@0 1069 }
michael@0 1070 servers.push_back(server);
michael@0 1071 }
michael@0 1072
michael@0 1073 return 0;
michael@0 1074 }
michael@0 1075
michael@0 1076 if (!strcmp(keyword, "clientauth"))
michael@0 1077 {
michael@0 1078 char* hostname = strtok2(_caret, ":", &_caret);
michael@0 1079 char* hostportstring = strtok2(_caret, ":", &_caret);
michael@0 1080 char* serverportstring = strtok2(_caret, ":", &_caret);
michael@0 1081
michael@0 1082 int port = atoi(serverportstring);
michael@0 1083 if (port <= 0) {
michael@0 1084 LOG_ERROR(("Invalid port specified: %s\n", serverportstring));
michael@0 1085 return 1;
michael@0 1086 }
michael@0 1087
michael@0 1088 if (server_info_t* existingServer = findServerInfo(port))
michael@0 1089 {
michael@0 1090 char* authoptionstring = strtok2(_caret, ":", &_caret);
michael@0 1091 client_auth_option* authoption = new client_auth_option;
michael@0 1092 if (!authoption) {
michael@0 1093 LOG_ERROR(("Out of memory"));
michael@0 1094 return 1;
michael@0 1095 }
michael@0 1096
michael@0 1097 if (!strcmp(authoptionstring, "require"))
michael@0 1098 *authoption = caRequire;
michael@0 1099 else if (!strcmp(authoptionstring, "request"))
michael@0 1100 *authoption = caRequest;
michael@0 1101 else if (!strcmp(authoptionstring, "none"))
michael@0 1102 *authoption = caNone;
michael@0 1103 else
michael@0 1104 {
michael@0 1105 LOG_ERROR(("Incorrect client auth option modifier for host '%s'", hostname));
michael@0 1106 return 1;
michael@0 1107 }
michael@0 1108
michael@0 1109 any_host_spec_config = true;
michael@0 1110
michael@0 1111 char *hostname_copy = new char[strlen(hostname)+strlen(hostportstring)+2];
michael@0 1112 if (!hostname_copy) {
michael@0 1113 LOG_ERROR(("Out of memory"));
michael@0 1114 return 1;
michael@0 1115 }
michael@0 1116
michael@0 1117 strcpy(hostname_copy, hostname);
michael@0 1118 strcat(hostname_copy, ":");
michael@0 1119 strcat(hostname_copy, hostportstring);
michael@0 1120
michael@0 1121 PLHashEntry* entry = PL_HashTableAdd(existingServer->host_clientauth_table, hostname_copy, authoption);
michael@0 1122 if (!entry) {
michael@0 1123 LOG_ERROR(("Out of memory"));
michael@0 1124 return 1;
michael@0 1125 }
michael@0 1126 }
michael@0 1127 else
michael@0 1128 {
michael@0 1129 LOG_ERROR(("Server on port %d for client authentication option is not defined, use 'listen' option first", port));
michael@0 1130 return 1;
michael@0 1131 }
michael@0 1132
michael@0 1133 return 0;
michael@0 1134 }
michael@0 1135
michael@0 1136 if (!strcmp(keyword, "redirhost"))
michael@0 1137 {
michael@0 1138 char* hostname = strtok2(_caret, ":", &_caret);
michael@0 1139 char* hostportstring = strtok2(_caret, ":", &_caret);
michael@0 1140 char* serverportstring = strtok2(_caret, ":", &_caret);
michael@0 1141
michael@0 1142 int port = atoi(serverportstring);
michael@0 1143 if (port <= 0) {
michael@0 1144 LOG_ERROR(("Invalid port specified: %s\n", serverportstring));
michael@0 1145 return 1;
michael@0 1146 }
michael@0 1147
michael@0 1148 if (server_info_t* existingServer = findServerInfo(port))
michael@0 1149 {
michael@0 1150 char* redirhoststring = strtok2(_caret, ":", &_caret);
michael@0 1151
michael@0 1152 any_host_spec_config = true;
michael@0 1153
michael@0 1154 char *hostname_copy = new char[strlen(hostname)+strlen(hostportstring)+2];
michael@0 1155 if (!hostname_copy) {
michael@0 1156 LOG_ERROR(("Out of memory"));
michael@0 1157 return 1;
michael@0 1158 }
michael@0 1159
michael@0 1160 strcpy(hostname_copy, hostname);
michael@0 1161 strcat(hostname_copy, ":");
michael@0 1162 strcat(hostname_copy, hostportstring);
michael@0 1163
michael@0 1164 char *redir_copy = new char[strlen(redirhoststring)+1];
michael@0 1165 strcpy(redir_copy, redirhoststring);
michael@0 1166 PLHashEntry* entry = PL_HashTableAdd(existingServer->host_redir_table, hostname_copy, redir_copy);
michael@0 1167 if (!entry) {
michael@0 1168 LOG_ERROR(("Out of memory"));
michael@0 1169 return 1;
michael@0 1170 }
michael@0 1171 }
michael@0 1172 else
michael@0 1173 {
michael@0 1174 LOG_ERROR(("Server on port %d for redirhost option is not defined, use 'listen' option first", port));
michael@0 1175 return 1;
michael@0 1176 }
michael@0 1177
michael@0 1178 return 0;
michael@0 1179 }
michael@0 1180
michael@0 1181 // Configure the NSS certificate database directory
michael@0 1182 if (!strcmp(keyword, "certdbdir"))
michael@0 1183 {
michael@0 1184 nssconfigdir = strtok2(_caret, "\n", &_caret);
michael@0 1185 return 0;
michael@0 1186 }
michael@0 1187
michael@0 1188 LOG_ERROR(("Error: keyword \"%s\" unexpected\n", keyword));
michael@0 1189 return 1;
michael@0 1190 }
michael@0 1191
michael@0 1192 int parseConfigFile(const char* filePath)
michael@0 1193 {
michael@0 1194 FILE* f = fopen(filePath, "r");
michael@0 1195 if (!f)
michael@0 1196 return 1;
michael@0 1197
michael@0 1198 char buffer[1024], *b = buffer;
michael@0 1199 while (!feof(f))
michael@0 1200 {
michael@0 1201 char c;
michael@0 1202 fscanf(f, "%c", &c);
michael@0 1203 switch (c)
michael@0 1204 {
michael@0 1205 case '\n':
michael@0 1206 *b++ = 0;
michael@0 1207 if (processConfigLine(buffer))
michael@0 1208 return 1;
michael@0 1209 b = buffer;
michael@0 1210 case '\r':
michael@0 1211 continue;
michael@0 1212 default:
michael@0 1213 *b++ = c;
michael@0 1214 }
michael@0 1215 }
michael@0 1216
michael@0 1217 fclose(f);
michael@0 1218
michael@0 1219 // Check mandatory items
michael@0 1220 if (nssconfigdir.empty())
michael@0 1221 {
michael@0 1222 LOG_ERROR(("Error: missing path to NSS certification database\n,use certdbdir:<path> in the config file\n"));
michael@0 1223 return 1;
michael@0 1224 }
michael@0 1225
michael@0 1226 if (any_host_spec_config && !do_http_proxy)
michael@0 1227 {
michael@0 1228 LOG_ERROR(("Warning: any host-specific configurations are ignored, add httpproxy:1 to allow them\n"));
michael@0 1229 }
michael@0 1230
michael@0 1231 return 0;
michael@0 1232 }
michael@0 1233
michael@0 1234 int freeHostCertHashItems(PLHashEntry *he, int i, void *arg)
michael@0 1235 {
michael@0 1236 delete [] (char*)he->key;
michael@0 1237 delete [] (char*)he->value;
michael@0 1238 return HT_ENUMERATE_REMOVE;
michael@0 1239 }
michael@0 1240
michael@0 1241 int freeHostRedirHashItems(PLHashEntry *he, int i, void *arg)
michael@0 1242 {
michael@0 1243 delete [] (char*)he->key;
michael@0 1244 delete [] (char*)he->value;
michael@0 1245 return HT_ENUMERATE_REMOVE;
michael@0 1246 }
michael@0 1247
michael@0 1248 int freeClientAuthHashItems(PLHashEntry *he, int i, void *arg)
michael@0 1249 {
michael@0 1250 delete [] (char*)he->key;
michael@0 1251 delete (client_auth_option*)he->value;
michael@0 1252 return HT_ENUMERATE_REMOVE;
michael@0 1253 }
michael@0 1254
michael@0 1255 int main(int argc, char** argv)
michael@0 1256 {
michael@0 1257 const char* configFilePath;
michael@0 1258
michael@0 1259 const char* logLevelEnv = PR_GetEnv("SSLTUNNEL_LOG_LEVEL");
michael@0 1260 gLogLevel = logLevelEnv ? (LogLevel)atoi(logLevelEnv) : LEVEL_INFO;
michael@0 1261
michael@0 1262 if (argc == 1)
michael@0 1263 configFilePath = "ssltunnel.cfg";
michael@0 1264 else
michael@0 1265 configFilePath = argv[1];
michael@0 1266
michael@0 1267 memset(&websocket_server, 0, sizeof(PRNetAddr));
michael@0 1268
michael@0 1269 if (parseConfigFile(configFilePath)) {
michael@0 1270 LOG_ERROR(("Error: config file \"%s\" missing or formating incorrect\n"
michael@0 1271 "Specify path to the config file as parameter to ssltunnel or \n"
michael@0 1272 "create ssltunnel.cfg in the working directory.\n\n"
michael@0 1273 "Example format of the config file:\n\n"
michael@0 1274 " # Enable http/ssl tunneling proxy-like behavior.\n"
michael@0 1275 " # If not specified ssltunnel simply does direct forward.\n"
michael@0 1276 " httpproxy:1\n\n"
michael@0 1277 " # Specify path to the certification database used.\n"
michael@0 1278 " certdbdir:/path/to/certdb\n\n"
michael@0 1279 " # Forward/proxy all requests in raw to 127.0.0.1:8888.\n"
michael@0 1280 " forward:127.0.0.1:8888\n\n"
michael@0 1281 " # Accept connections on port 4443 or 5678 resp. and authenticate\n"
michael@0 1282 " # to any host ('*') using the 'server cert' or 'server cert 2' resp.\n"
michael@0 1283 " listen:*:4443:server cert\n"
michael@0 1284 " listen:*:5678:server cert 2\n\n"
michael@0 1285 " # Accept connections on port 4443 and authenticate using\n"
michael@0 1286 " # 'a different cert' when target host is 'my.host.name:443'.\n"
michael@0 1287 " # This only works in httpproxy mode and has higher priority\n"
michael@0 1288 " # than the previous option.\n"
michael@0 1289 " listen:my.host.name:443:4443:a different cert\n\n"
michael@0 1290 " # To make a specific host require or just request a client certificate\n"
michael@0 1291 " # to authenticate use the following options. This can only be used\n"
michael@0 1292 " # in httpproxy mode and only after the 'listen' option has been\n"
michael@0 1293 " # specified. You also have to specify the tunnel listen port.\n"
michael@0 1294 " clientauth:requesting-client-cert.host.com:443:4443:request\n"
michael@0 1295 " clientauth:requiring-client-cert.host.com:443:4443:require\n"
michael@0 1296 " # Proxy WebSocket traffic to the server at 127.0.0.1:9999,\n"
michael@0 1297 " # instead of the server specified in the 'forward' option.\n"
michael@0 1298 " websocketserver:127.0.0.1:9999\n",
michael@0 1299 configFilePath));
michael@0 1300 return 1;
michael@0 1301 }
michael@0 1302
michael@0 1303 // create a thread pool to handle connections
michael@0 1304 threads = PR_CreateThreadPool(INITIAL_THREADS * servers.size(),
michael@0 1305 MAX_THREADS * servers.size(),
michael@0 1306 DEFAULT_STACKSIZE);
michael@0 1307 if (!threads) {
michael@0 1308 LOG_ERROR(("Failed to create thread pool\n"));
michael@0 1309 return 1;
michael@0 1310 }
michael@0 1311
michael@0 1312 shutdown_lock = PR_NewLock();
michael@0 1313 if (!shutdown_lock) {
michael@0 1314 LOG_ERROR(("Failed to create lock\n"));
michael@0 1315 PR_ShutdownThreadPool(threads);
michael@0 1316 return 1;
michael@0 1317 }
michael@0 1318 shutdown_condvar = PR_NewCondVar(shutdown_lock);
michael@0 1319 if (!shutdown_condvar) {
michael@0 1320 LOG_ERROR(("Failed to create condvar\n"));
michael@0 1321 PR_ShutdownThreadPool(threads);
michael@0 1322 PR_DestroyLock(shutdown_lock);
michael@0 1323 return 1;
michael@0 1324 }
michael@0 1325
michael@0 1326 PK11_SetPasswordFunc(password_func);
michael@0 1327
michael@0 1328 // Initialize NSS
michael@0 1329 if (NSS_Init(nssconfigdir.c_str()) != SECSuccess) {
michael@0 1330 int32_t errorlen = PR_GetErrorTextLength();
michael@0 1331 char* err = new char[errorlen+1];
michael@0 1332 PR_GetErrorText(err);
michael@0 1333 LOG_ERROR(("Failed to init NSS: %s", err));
michael@0 1334 delete[] err;
michael@0 1335 PR_ShutdownThreadPool(threads);
michael@0 1336 PR_DestroyCondVar(shutdown_condvar);
michael@0 1337 PR_DestroyLock(shutdown_lock);
michael@0 1338 return 1;
michael@0 1339 }
michael@0 1340
michael@0 1341 if (NSS_SetDomesticPolicy() != SECSuccess) {
michael@0 1342 LOG_ERROR(("NSS_SetDomesticPolicy failed\n"));
michael@0 1343 PR_ShutdownThreadPool(threads);
michael@0 1344 PR_DestroyCondVar(shutdown_condvar);
michael@0 1345 PR_DestroyLock(shutdown_lock);
michael@0 1346 NSS_Shutdown();
michael@0 1347 return 1;
michael@0 1348 }
michael@0 1349
michael@0 1350 // these values should make NSS use the defaults
michael@0 1351 if (SSL_ConfigServerSessionIDCache(0, 0, 0, nullptr) != SECSuccess) {
michael@0 1352 LOG_ERROR(("SSL_ConfigServerSessionIDCache failed\n"));
michael@0 1353 PR_ShutdownThreadPool(threads);
michael@0 1354 PR_DestroyCondVar(shutdown_condvar);
michael@0 1355 PR_DestroyLock(shutdown_lock);
michael@0 1356 NSS_Shutdown();
michael@0 1357 return 1;
michael@0 1358 }
michael@0 1359
michael@0 1360 for (vector<server_info_t>::iterator it = servers.begin();
michael@0 1361 it != servers.end(); it++) {
michael@0 1362 // Not actually using this PRJob*...
michael@0 1363 // PRJob* server_job =
michael@0 1364 PR_QueueJob(threads, StartServer, &(*it), true);
michael@0 1365 }
michael@0 1366 // now wait for someone to tell us to quit
michael@0 1367 PR_Lock(shutdown_lock);
michael@0 1368 PR_WaitCondVar(shutdown_condvar, PR_INTERVAL_NO_TIMEOUT);
michael@0 1369 PR_Unlock(shutdown_lock);
michael@0 1370 shutdown_server = true;
michael@0 1371 LOG_INFO(("Shutting down...\n"));
michael@0 1372 // cleanup
michael@0 1373 PR_ShutdownThreadPool(threads);
michael@0 1374 PR_JoinThreadPool(threads);
michael@0 1375 PR_DestroyCondVar(shutdown_condvar);
michael@0 1376 PR_DestroyLock(shutdown_lock);
michael@0 1377 if (NSS_Shutdown() == SECFailure) {
michael@0 1378 LOG_DEBUG(("Leaked NSS objects!\n"));
michael@0 1379 }
michael@0 1380
michael@0 1381 for (vector<server_info_t>::iterator it = servers.begin();
michael@0 1382 it != servers.end(); it++)
michael@0 1383 {
michael@0 1384 PL_HashTableEnumerateEntries(it->host_cert_table, freeHostCertHashItems, nullptr);
michael@0 1385 PL_HashTableEnumerateEntries(it->host_clientauth_table, freeClientAuthHashItems, nullptr);
michael@0 1386 PL_HashTableEnumerateEntries(it->host_redir_table, freeHostRedirHashItems, nullptr);
michael@0 1387 PL_HashTableDestroy(it->host_cert_table);
michael@0 1388 PL_HashTableDestroy(it->host_clientauth_table);
michael@0 1389 PL_HashTableDestroy(it->host_redir_table);
michael@0 1390 }
michael@0 1391
michael@0 1392 PR_Cleanup();
michael@0 1393 return 0;
michael@0 1394 }

mercurial