зеркало из https://github.com/mozilla/gecko-dev.git
2786 строки
85 KiB
C
2786 строки
85 KiB
C
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* -r flag is interepreted as follows:
|
|
* 1 -r means request, not require, on initial handshake.
|
|
* 2 -r's mean request and require, on initial handshake.
|
|
* 3 -r's mean request, not require, on second handshake.
|
|
* 4 -r's mean request and require, on second handshake.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "secutil.h"
|
|
|
|
#if defined(XP_UNIX)
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#if defined(_WINDOWS)
|
|
#include <process.h> /* for getpid() */
|
|
#endif
|
|
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "nspr.h"
|
|
#include "prio.h"
|
|
#include "prerror.h"
|
|
#include "prnetdb.h"
|
|
#include "prclist.h"
|
|
#include "plgetopt.h"
|
|
#include "pk11func.h"
|
|
#include "secitem.h"
|
|
#include "nss.h"
|
|
#include "ssl.h"
|
|
#include "sslproto.h"
|
|
#include "sslexp.h"
|
|
#include "cert.h"
|
|
#include "certt.h"
|
|
#include "ocsp.h"
|
|
|
|
#ifndef PORT_Sprintf
|
|
#define PORT_Sprintf sprintf
|
|
#endif
|
|
|
|
#ifndef PORT_Strstr
|
|
#define PORT_Strstr strstr
|
|
#endif
|
|
|
|
#ifndef PORT_Malloc
|
|
#define PORT_Malloc PR_Malloc
|
|
#endif
|
|
|
|
int NumSidCacheEntries = 1024;
|
|
|
|
static int handle_connection(PRFileDesc *, PRFileDesc *, int);
|
|
|
|
static const char envVarName[] = { SSL_ENV_VAR_NAME };
|
|
static const char inheritableSockName[] = { "SELFSERV_LISTEN_SOCKET" };
|
|
|
|
#define MAX_VIRT_SERVER_NAME_ARRAY_INDEX 10
|
|
#define MAX_CERT_NICKNAME_ARRAY_INDEX 10
|
|
|
|
#define DEFAULT_BULK_TEST 16384
|
|
#define MAX_BULK_TEST 1048576 /* 1 MB */
|
|
static PRBool testBulk;
|
|
static PRUint32 testBulkSize = DEFAULT_BULK_TEST;
|
|
static PRInt32 testBulkTotal;
|
|
static char *testBulkBuf;
|
|
static PRDescIdentity log_layer_id = PR_INVALID_IO_LAYER;
|
|
static PRFileDesc *loggingFD;
|
|
static PRIOMethods loggingMethods;
|
|
|
|
static PRBool logStats;
|
|
static PRBool loggingLayer;
|
|
static int logPeriod = 30;
|
|
static PRInt32 loggerOps;
|
|
static PRInt32 loggerBytes;
|
|
static PRInt32 loggerBytesTCP;
|
|
static PRInt32 bulkSentChunks;
|
|
static enum ocspStaplingModeEnum {
|
|
osm_disabled, /* server doesn't support stapling */
|
|
osm_good, /* supply a signed good status */
|
|
osm_revoked, /* supply a signed revoked status */
|
|
osm_unknown, /* supply a signed unknown status */
|
|
osm_failure, /* supply a unsigned failure status, "try later" */
|
|
osm_badsig, /* supply a good status response with a bad signature */
|
|
osm_corrupted, /* supply a corrupted data block as the status */
|
|
osm_random, /* use a random response for each connection */
|
|
osm_ocsp /* retrieve ocsp status from external ocsp server,
|
|
use empty status if server is unavailable */
|
|
} ocspStaplingMode = osm_disabled;
|
|
typedef enum ocspStaplingModeEnum ocspStaplingModeType;
|
|
static char *ocspStaplingCA = NULL;
|
|
static SECItemArray *certStatus[MAX_CERT_NICKNAME_ARRAY_INDEX] = { NULL };
|
|
|
|
const int ssl3CipherSuites[] = {
|
|
-1, /* SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA* a */
|
|
-1, /* SSL_FORTEZZA_DMS_WITH_RC4_128_SHA * b */
|
|
TLS_RSA_WITH_RC4_128_MD5, /* c */
|
|
TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* d */
|
|
TLS_RSA_WITH_DES_CBC_SHA, /* e */
|
|
-1, /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 * f */
|
|
-1, /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 * g */
|
|
-1, /* SSL_FORTEZZA_DMS_WITH_NULL_SHA * h */
|
|
TLS_RSA_WITH_NULL_MD5, /* i */
|
|
-1, /* SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA * j */
|
|
-1, /* SSL_RSA_FIPS_WITH_DES_CBC_SHA * k */
|
|
-1, /* TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA * l */
|
|
-1, /* TLS_RSA_EXPORT1024_WITH_RC4_56_SHA * m */
|
|
TLS_RSA_WITH_RC4_128_SHA, /* n */
|
|
TLS_DHE_DSS_WITH_RC4_128_SHA, /* o */
|
|
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* p */
|
|
TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* q */
|
|
TLS_DHE_RSA_WITH_DES_CBC_SHA, /* r */
|
|
TLS_DHE_DSS_WITH_DES_CBC_SHA, /* s */
|
|
TLS_DHE_DSS_WITH_AES_128_CBC_SHA, /* t */
|
|
TLS_DHE_RSA_WITH_AES_128_CBC_SHA, /* u */
|
|
TLS_RSA_WITH_AES_128_CBC_SHA, /* v */
|
|
TLS_DHE_DSS_WITH_AES_256_CBC_SHA, /* w */
|
|
TLS_DHE_RSA_WITH_AES_256_CBC_SHA, /* x */
|
|
TLS_RSA_WITH_AES_256_CBC_SHA, /* y */
|
|
TLS_RSA_WITH_NULL_SHA, /* z */
|
|
0
|
|
};
|
|
|
|
/* data and structures for shutdown */
|
|
static int stopping;
|
|
|
|
static PRBool noDelay;
|
|
static int requestCert;
|
|
static int verbose;
|
|
static SECItem bigBuf;
|
|
static int configureDHE = -1; /* -1: don't configure, 0 disable, >=1 enable*/
|
|
static int configureReuseECDHE = -1; /* -1: don't configure, 0 refresh, >=1 reuse*/
|
|
static int configureWeakDHE = -1; /* -1: don't configure, 0 disable, >=1 enable*/
|
|
|
|
static PRThread *acceptorThread;
|
|
|
|
static PRLogModuleInfo *lm;
|
|
|
|
#define PRINTF \
|
|
if (verbose) \
|
|
printf
|
|
#define FPRINTF \
|
|
if (verbose) \
|
|
fprintf
|
|
#define FLUSH \
|
|
if (verbose) { \
|
|
fflush(stdout); \
|
|
fflush(stderr); \
|
|
}
|
|
#define VLOG(arg) PR_LOG(lm, PR_LOG_DEBUG, arg)
|
|
|
|
static void
|
|
PrintUsageHeader(const char *progName)
|
|
{
|
|
fprintf(stderr,
|
|
"Usage: %s -n rsa_nickname -p port [-BDENRZbjlmrsuvx] [-w password]\n"
|
|
" [-t threads] [-i pid_file] [-c ciphers] [-Y] [-d dbdir] [-g numblocks]\n"
|
|
" [-f password_file] [-L [seconds]] [-M maxProcs] [-P dbprefix]\n"
|
|
" [-V [min-version]:[max-version]] [-a sni_name]\n"
|
|
" [ T <good|revoked|unknown|badsig|corrupted|none|ocsp>] [-A ca]\n"
|
|
" [-C SSLCacheEntries] [-S dsa_nickname] -Q [-I groups]"
|
|
" [-e ec_nickname]"
|
|
"\n"
|
|
" -U [0|1] -H [0|1|2] -W [0|1]\n"
|
|
"\n",
|
|
progName);
|
|
}
|
|
|
|
static void
|
|
PrintParameterUsage()
|
|
{
|
|
fputs(
|
|
"-V [min]:[max] restricts the set of enabled SSL/TLS protocol versions.\n"
|
|
" All versions are enabled by default.\n"
|
|
" Possible values for min/max: ssl3 tls1.0 tls1.1 tls1.2\n"
|
|
" Example: \"-V ssl3:\" enables SSL 3 and newer.\n"
|
|
"-D means disable Nagle delays in TCP\n"
|
|
"-R means disable detection of rollback from TLS to SSL3\n"
|
|
"-a configure server for SNI.\n"
|
|
"-k expected name negotiated on server sockets\n"
|
|
"-b means try binding to the port and exit\n"
|
|
"-m means test the model-socket feature of SSL_ImportFD.\n"
|
|
"-r flag is interepreted as follows:\n"
|
|
" 1 -r means request, not require, cert on initial handshake.\n"
|
|
" 2 -r's mean request and require, cert on initial handshake.\n"
|
|
" 3 -r's mean request, not require, cert on second handshake.\n"
|
|
" 4 -r's mean request and require, cert on second handshake.\n"
|
|
"-s means disable SSL socket locking for performance\n"
|
|
"-u means enable Session Ticket extension for TLS.\n"
|
|
"-v means verbose output\n"
|
|
"-z means enable compression.\n"
|
|
"-L seconds means log statistics every 'seconds' seconds (default=30).\n"
|
|
"-M maxProcs tells how many processes to run in a multi-process server\n"
|
|
"-N means do NOT use the server session cache. Incompatible with -M.\n"
|
|
"-t threads -- specify the number of threads to use for connections.\n"
|
|
"-i pid_file file to write the process id of selfserve\n"
|
|
"-l means use local threads instead of global threads\n"
|
|
"-g numblocks means test throughput by sending total numblocks chunks\n"
|
|
" of size 16kb to the client, 0 means unlimited (default=0)\n"
|
|
"-j means measure TCP throughput (for use with -g option)\n"
|
|
"-C SSLCacheEntries sets the maximum number of entries in the SSL\n"
|
|
" session cache\n"
|
|
"-T <mode> enable OCSP stapling. Possible modes:\n"
|
|
" none: don't send cert status (default)\n"
|
|
" good, revoked, unknown: Include locally signed response. Requires: -A\n"
|
|
" failure: return a failure response (try later, unsigned)\n"
|
|
" badsig: use a good status but with an invalid signature\n"
|
|
" corrupted: stapled cert status is an invalid block of data\n"
|
|
" random: each connection uses a random status from this list:\n"
|
|
" good, revoked, unknown, failure, badsig, corrupted\n"
|
|
" ocsp: fetch from external OCSP server using AIA, or none\n"
|
|
"-A <ca> Nickname of a CA used to sign a stapled cert status\n"
|
|
"-U override default ECDHE ephemeral key reuse, 0: refresh, 1: reuse\n"
|
|
"-H override default DHE server support, 0: disable, 1: enable, "
|
|
" 2: require DH named groups [RFC7919]\n"
|
|
"-W override default DHE server weak parameters support, 0: disable, 1: enable\n"
|
|
"-c Restrict ciphers\n"
|
|
"-Y prints cipher values allowed for parameter -c and exits\n"
|
|
"-G enables the extended master secret extension [RFC7627]\n"
|
|
"-Q enables ALPN for HTTP/1.1 [RFC7301]\n"
|
|
"-I comma separated list of enabled groups for TLS key exchange.\n"
|
|
" The following values are valid:\n"
|
|
" P256, P384, P521, x25519, FF2048, FF3072, FF4096, FF6144, FF8192\n"
|
|
"-Z enable 0-RTT (for TLS 1.3; also use -u)\n",
|
|
stderr);
|
|
}
|
|
|
|
static void
|
|
Usage(const char *progName)
|
|
{
|
|
PrintUsageHeader(progName);
|
|
PrintParameterUsage();
|
|
}
|
|
|
|
static void
|
|
PrintCipherUsage(const char *progName)
|
|
{
|
|
PrintUsageHeader(progName);
|
|
fputs(
|
|
"-c ciphers Letter(s) chosen from the following list\n"
|
|
"c SSL3 RSA WITH RC4 128 MD5\n"
|
|
"d SSL3 RSA WITH 3DES EDE CBC SHA\n"
|
|
"e SSL3 RSA WITH DES CBC SHA\n"
|
|
"f SSL3 RSA EXPORT WITH RC4 40 MD5\n"
|
|
"g SSL3 RSA EXPORT WITH RC2 CBC 40 MD5\n"
|
|
"i SSL3 RSA WITH NULL MD5\n"
|
|
"j SSL3 RSA FIPS WITH 3DES EDE CBC SHA\n"
|
|
"k SSL3 RSA FIPS WITH DES CBC SHA\n"
|
|
"l SSL3 RSA EXPORT WITH DES CBC SHA\t(new)\n"
|
|
"m SSL3 RSA EXPORT WITH RC4 56 SHA\t(new)\n"
|
|
"n SSL3 RSA WITH RC4 128 SHA\n"
|
|
"o TLS_DHE_DSS_WITH_RC4_128_SHA\n"
|
|
"p TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA\n"
|
|
"q TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA\n"
|
|
"r TLS_DHE_RSA_WITH_DES_CBC_SHA\n"
|
|
"s TLS_DHE_DSS_WITH_DES_CBC_SHA\n"
|
|
"t TLS_DHE_DSS_WITH_AES_128_CBC_SHA\n"
|
|
"u TLS_DHE_RSA_WITH_AES_128_CBC_SHA\n"
|
|
"v SSL3 RSA WITH AES 128 CBC SHA\n"
|
|
"w TLS_DHE_DSS_WITH_AES_256_CBC_SHA\n"
|
|
"x TLS_DHE_RSA_WITH_AES_256_CBC_SHA\n"
|
|
"y SSL3 RSA WITH AES 256 CBC SHA\n"
|
|
"z SSL3 RSA WITH NULL SHA\n"
|
|
"\n"
|
|
":WXYZ Use cipher with hex code { 0xWX , 0xYZ } in TLS\n",
|
|
stderr);
|
|
}
|
|
|
|
static const char *
|
|
errWarn(char *funcString)
|
|
{
|
|
PRErrorCode perr = PR_GetError();
|
|
const char *errString = SECU_Strerror(perr);
|
|
|
|
fprintf(stderr, "selfserv: %s returned error %d:\n%s\n",
|
|
funcString, perr, errString);
|
|
return errString;
|
|
}
|
|
|
|
static void
|
|
errExit(char *funcString)
|
|
{
|
|
errWarn(funcString);
|
|
exit(3);
|
|
}
|
|
|
|
/**************************************************************************
|
|
**
|
|
** Routines for disabling SSL ciphers.
|
|
**
|
|
**************************************************************************/
|
|
|
|
/* disable all the SSL cipher suites */
|
|
void
|
|
disableAllSSLCiphers(void)
|
|
{
|
|
const PRUint16 *cipherSuites = SSL_ImplementedCiphers;
|
|
int i = SSL_NumImplementedCiphers;
|
|
SECStatus rv;
|
|
|
|
while (--i >= 0) {
|
|
PRUint16 suite = cipherSuites[i];
|
|
rv = SSL_CipherPrefSetDefault(suite, PR_FALSE);
|
|
if (rv != SECSuccess) {
|
|
printf("SSL_CipherPrefSetDefault rejected suite 0x%04x (i = %d)\n",
|
|
suite, i);
|
|
errWarn("SSL_CipherPrefSetDefault");
|
|
}
|
|
}
|
|
}
|
|
|
|
static SECStatus
|
|
mySSLAuthCertificate(void *arg, PRFileDesc *fd, PRBool checkSig,
|
|
PRBool isServer)
|
|
{
|
|
SECStatus rv;
|
|
CERTCertificate *peerCert;
|
|
|
|
peerCert = SSL_PeerCertificate(fd);
|
|
|
|
if (peerCert) {
|
|
PRINTF("selfserv: Subject: %s\nselfserv: Issuer : %s\n",
|
|
peerCert->subjectName, peerCert->issuerName);
|
|
CERT_DestroyCertificate(peerCert);
|
|
}
|
|
|
|
rv = SSL_AuthCertificate(arg, fd, checkSig, isServer);
|
|
|
|
if (rv == SECSuccess) {
|
|
PRINTF("selfserv: -- SSL3: Certificate Validated.\n");
|
|
} else {
|
|
int err = PR_GetError();
|
|
FPRINTF(stderr, "selfserv: -- SSL3: Certificate Invalid, err %d.\n%s\n",
|
|
err, SECU_Strerror(err));
|
|
}
|
|
FLUSH;
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
printSSLStatistics()
|
|
{
|
|
SSL3Statistics *ssl3stats = SSL_GetStatistics();
|
|
|
|
printf(
|
|
"selfserv: %ld cache hits; %ld cache misses, %ld cache not reusable\n"
|
|
" %ld stateless resumes, %ld ticket parse failures\n",
|
|
ssl3stats->hch_sid_cache_hits, ssl3stats->hch_sid_cache_misses,
|
|
ssl3stats->hch_sid_cache_not_ok, ssl3stats->hch_sid_stateless_resumes,
|
|
ssl3stats->hch_sid_ticket_parse_failures);
|
|
}
|
|
|
|
void
|
|
printSecurityInfo(PRFileDesc *fd)
|
|
{
|
|
CERTCertificate *cert = NULL;
|
|
SECStatus result;
|
|
SSLChannelInfo channel;
|
|
SSLCipherSuiteInfo suite;
|
|
|
|
if (verbose)
|
|
printSSLStatistics();
|
|
|
|
result = SSL_GetChannelInfo(fd, &channel, sizeof channel);
|
|
if (result == SECSuccess &&
|
|
channel.length == sizeof channel &&
|
|
channel.cipherSuite) {
|
|
result = SSL_GetCipherSuiteInfo(channel.cipherSuite,
|
|
&suite, sizeof suite);
|
|
if (result == SECSuccess) {
|
|
FPRINTF(stderr,
|
|
"selfserv: SSL version %d.%d using %d-bit %s with %d-bit %s MAC\n",
|
|
channel.protocolVersion >> 8, channel.protocolVersion & 0xff,
|
|
suite.effectiveKeyBits, suite.symCipherName,
|
|
suite.macBits, suite.macAlgorithmName);
|
|
FPRINTF(stderr,
|
|
"selfserv: Server Auth: %d-bit %s, Key Exchange: %d-bit %s\n"
|
|
" Compression: %s, Extended Master Secret: %s\n",
|
|
channel.authKeyBits, suite.authAlgorithmName,
|
|
channel.keaKeyBits, suite.keaTypeName,
|
|
channel.compressionMethodName,
|
|
channel.extendedMasterSecretUsed ? "Yes" : "No");
|
|
}
|
|
}
|
|
if (verbose) {
|
|
SECItem *hostInfo = SSL_GetNegotiatedHostInfo(fd);
|
|
if (hostInfo) {
|
|
char namePref[] = "selfserv: Negotiated server name: ";
|
|
|
|
fprintf(stderr, "%s", namePref);
|
|
fwrite(hostInfo->data, hostInfo->len, 1, stderr);
|
|
SECITEM_FreeItem(hostInfo, PR_TRUE);
|
|
hostInfo = NULL;
|
|
fprintf(stderr, "\n");
|
|
}
|
|
}
|
|
if (requestCert)
|
|
cert = SSL_PeerCertificate(fd);
|
|
else
|
|
cert = SSL_LocalCertificate(fd);
|
|
if (cert) {
|
|
char *ip = CERT_NameToAscii(&cert->issuer);
|
|
char *sp = CERT_NameToAscii(&cert->subject);
|
|
if (sp) {
|
|
FPRINTF(stderr, "selfserv: subject DN: %s\n", sp);
|
|
PORT_Free(sp);
|
|
}
|
|
if (ip) {
|
|
FPRINTF(stderr, "selfserv: issuer DN: %s\n", ip);
|
|
PORT_Free(ip);
|
|
}
|
|
CERT_DestroyCertificate(cert);
|
|
cert = NULL;
|
|
}
|
|
FLUSH;
|
|
}
|
|
|
|
static int MakeCertOK;
|
|
|
|
static SECStatus
|
|
myBadCertHandler(void *arg, PRFileDesc *fd)
|
|
{
|
|
int err = PR_GetError();
|
|
if (!MakeCertOK)
|
|
fprintf(stderr,
|
|
"selfserv: -- SSL: Client Certificate Invalid, err %d.\n%s\n",
|
|
err, SECU_Strerror(err));
|
|
return (MakeCertOK ? SECSuccess : SECFailure);
|
|
}
|
|
|
|
/* Simple SNI socket config function that does not use SSL_ReconfigFD.
|
|
* Only uses one server name but verifies that the names match. */
|
|
PRInt32
|
|
mySSLSNISocketConfig(PRFileDesc *fd, const SECItem *sniNameArr,
|
|
PRUint32 sniNameArrSize, void *arg)
|
|
{
|
|
PRInt32 i = 0;
|
|
const SECItem *current = sniNameArr;
|
|
const char **nameArr = (const char **)arg;
|
|
secuPWData *pwdata;
|
|
CERTCertificate *cert = NULL;
|
|
SECKEYPrivateKey *privKey = NULL;
|
|
|
|
PORT_Assert(fd && sniNameArr);
|
|
if (!fd || !sniNameArr) {
|
|
return SSL_SNI_SEND_ALERT;
|
|
}
|
|
|
|
pwdata = SSL_RevealPinArg(fd);
|
|
|
|
for (; current && (PRUint32)i < sniNameArrSize; i++) {
|
|
unsigned int j = 0;
|
|
for (; j < MAX_VIRT_SERVER_NAME_ARRAY_INDEX && nameArr[j]; j++) {
|
|
if (!PORT_Strncmp(nameArr[j],
|
|
(const char *)current[i].data,
|
|
current[i].len) &&
|
|
PORT_Strlen(nameArr[j]) == current[i].len) {
|
|
const char *nickName = nameArr[j];
|
|
if (j == 0) {
|
|
/* default cert */
|
|
return 0;
|
|
}
|
|
/* if pwdata is NULL, then we would not get the key and
|
|
* return an error status. */
|
|
cert = PK11_FindCertFromNickname(nickName, &pwdata);
|
|
if (cert == NULL) {
|
|
goto loser; /* Send alert */
|
|
}
|
|
privKey = PK11_FindKeyByAnyCert(cert, &pwdata);
|
|
if (privKey == NULL) {
|
|
goto loser; /* Send alert */
|
|
}
|
|
if (SSL_ConfigServerCert(fd, cert, privKey, NULL, 0) != SECSuccess) {
|
|
goto loser; /* Send alert */
|
|
}
|
|
SECKEY_DestroyPrivateKey(privKey);
|
|
CERT_DestroyCertificate(cert);
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
loser:
|
|
if (privKey) {
|
|
SECKEY_DestroyPrivateKey(privKey);
|
|
}
|
|
if (cert) {
|
|
CERT_DestroyCertificate(cert);
|
|
}
|
|
return SSL_SNI_SEND_ALERT;
|
|
}
|
|
|
|
/**************************************************************************
|
|
** Begin thread management routines and data.
|
|
**************************************************************************/
|
|
#define MIN_THREADS 3
|
|
#define DEFAULT_THREADS 8
|
|
#define MAX_THREADS 4096
|
|
#define MAX_PROCS 25
|
|
static int maxThreads = DEFAULT_THREADS;
|
|
|
|
typedef struct jobStr {
|
|
PRCList link;
|
|
PRFileDesc *tcp_sock;
|
|
PRFileDesc *model_sock;
|
|
int requestCert;
|
|
} JOB;
|
|
|
|
static PZLock *qLock; /* this lock protects all data immediately below */
|
|
static PRLock *lastLoadedCrlLock; /* this lock protects lastLoadedCrl variable */
|
|
static PZCondVar *jobQNotEmptyCv;
|
|
static PZCondVar *freeListNotEmptyCv;
|
|
static PZCondVar *threadCountChangeCv;
|
|
static int threadCount;
|
|
static PRCList jobQ;
|
|
static PRCList freeJobs;
|
|
static JOB *jobTable;
|
|
|
|
SECStatus
|
|
setupJobs(int maxJobs)
|
|
{
|
|
int i;
|
|
|
|
jobTable = (JOB *)PR_Calloc(maxJobs, sizeof(JOB));
|
|
if (!jobTable)
|
|
return SECFailure;
|
|
|
|
PR_INIT_CLIST(&jobQ);
|
|
PR_INIT_CLIST(&freeJobs);
|
|
|
|
for (i = 0; i < maxJobs; ++i) {
|
|
JOB *pJob = jobTable + i;
|
|
PR_APPEND_LINK(&pJob->link, &freeJobs);
|
|
}
|
|
return SECSuccess;
|
|
}
|
|
|
|
typedef int startFn(PRFileDesc *a, PRFileDesc *b, int c);
|
|
|
|
typedef enum { rs_idle = 0,
|
|
rs_running = 1,
|
|
rs_zombie = 2 } runState;
|
|
|
|
typedef struct perThreadStr {
|
|
PRFileDesc *a;
|
|
PRFileDesc *b;
|
|
int c;
|
|
int rv;
|
|
startFn *startFunc;
|
|
PRThread *prThread;
|
|
runState state;
|
|
} perThread;
|
|
|
|
static perThread *threads;
|
|
|
|
void
|
|
thread_wrapper(void *arg)
|
|
{
|
|
perThread *slot = (perThread *)arg;
|
|
|
|
slot->rv = (*slot->startFunc)(slot->a, slot->b, slot->c);
|
|
|
|
/* notify the thread exit handler. */
|
|
PZ_Lock(qLock);
|
|
slot->state = rs_zombie;
|
|
--threadCount;
|
|
PZ_NotifyAllCondVar(threadCountChangeCv);
|
|
PZ_Unlock(qLock);
|
|
}
|
|
|
|
int
|
|
jobLoop(PRFileDesc *a, PRFileDesc *b, int c)
|
|
{
|
|
PRCList *myLink = 0;
|
|
JOB *myJob;
|
|
|
|
PZ_Lock(qLock);
|
|
do {
|
|
myLink = 0;
|
|
while (PR_CLIST_IS_EMPTY(&jobQ) && !stopping) {
|
|
PZ_WaitCondVar(jobQNotEmptyCv, PR_INTERVAL_NO_TIMEOUT);
|
|
}
|
|
if (!PR_CLIST_IS_EMPTY(&jobQ)) {
|
|
myLink = PR_LIST_HEAD(&jobQ);
|
|
PR_REMOVE_AND_INIT_LINK(myLink);
|
|
}
|
|
PZ_Unlock(qLock);
|
|
myJob = (JOB *)myLink;
|
|
/* myJob will be null when stopping is true and jobQ is empty */
|
|
if (!myJob)
|
|
break;
|
|
handle_connection(myJob->tcp_sock, myJob->model_sock,
|
|
myJob->requestCert);
|
|
PZ_Lock(qLock);
|
|
PR_APPEND_LINK(myLink, &freeJobs);
|
|
PZ_NotifyCondVar(freeListNotEmptyCv);
|
|
} while (PR_TRUE);
|
|
return 0;
|
|
}
|
|
|
|
SECStatus
|
|
launch_threads(
|
|
startFn *startFunc,
|
|
PRFileDesc *a,
|
|
PRFileDesc *b,
|
|
int c,
|
|
PRBool local)
|
|
{
|
|
int i;
|
|
SECStatus rv = SECSuccess;
|
|
|
|
/* create the thread management serialization structs */
|
|
qLock = PZ_NewLock(nssILockSelfServ);
|
|
jobQNotEmptyCv = PZ_NewCondVar(qLock);
|
|
freeListNotEmptyCv = PZ_NewCondVar(qLock);
|
|
threadCountChangeCv = PZ_NewCondVar(qLock);
|
|
|
|
/* create monitor for crl reload procedure */
|
|
lastLoadedCrlLock = PR_NewLock();
|
|
|
|
/* allocate the array of thread slots */
|
|
threads = PR_Calloc(maxThreads, sizeof(perThread));
|
|
if (NULL == threads) {
|
|
fprintf(stderr, "Oh Drat! Can't allocate the perThread array\n");
|
|
return SECFailure;
|
|
}
|
|
/* 5 is a little extra, intended to keep the jobQ from underflowing.
|
|
** That is, from going empty while not stopping and clients are still
|
|
** trying to contact us.
|
|
*/
|
|
rv = setupJobs(maxThreads + 5);
|
|
if (rv != SECSuccess)
|
|
return rv;
|
|
|
|
PZ_Lock(qLock);
|
|
for (i = 0; i < maxThreads; ++i) {
|
|
perThread *slot = threads + i;
|
|
|
|
slot->state = rs_running;
|
|
slot->a = a;
|
|
slot->b = b;
|
|
slot->c = c;
|
|
slot->startFunc = startFunc;
|
|
slot->prThread = PR_CreateThread(PR_USER_THREAD,
|
|
thread_wrapper, slot, PR_PRIORITY_NORMAL,
|
|
(PR_TRUE ==
|
|
local)
|
|
? PR_LOCAL_THREAD
|
|
: PR_GLOBAL_THREAD,
|
|
PR_JOINABLE_THREAD, 0);
|
|
if (slot->prThread == NULL) {
|
|
printf("selfserv: Failed to launch thread!\n");
|
|
slot->state = rs_idle;
|
|
rv = SECFailure;
|
|
break;
|
|
}
|
|
|
|
++threadCount;
|
|
}
|
|
PZ_Unlock(qLock);
|
|
|
|
return rv;
|
|
}
|
|
|
|
#define DESTROY_CONDVAR(name) \
|
|
if (name) { \
|
|
PZ_DestroyCondVar(name); \
|
|
name = NULL; \
|
|
}
|
|
#define DESTROY_LOCK(name) \
|
|
if (name) { \
|
|
PZ_DestroyLock(name); \
|
|
name = NULL; \
|
|
}
|
|
|
|
void
|
|
terminateWorkerThreads(void)
|
|
{
|
|
int i;
|
|
|
|
VLOG(("selfserv: server_thread: waiting on stopping"));
|
|
PZ_Lock(qLock);
|
|
PZ_NotifyAllCondVar(jobQNotEmptyCv);
|
|
PZ_Unlock(qLock);
|
|
|
|
/* Wait for worker threads to terminate. */
|
|
for (i = 0; i < maxThreads; ++i) {
|
|
perThread *slot = threads + i;
|
|
if (slot->prThread) {
|
|
PR_JoinThread(slot->prThread);
|
|
}
|
|
}
|
|
|
|
/* The worker threads empty the jobQ before they terminate. */
|
|
PZ_Lock(qLock);
|
|
PORT_Assert(threadCount == 0);
|
|
PORT_Assert(PR_CLIST_IS_EMPTY(&jobQ));
|
|
PZ_Unlock(qLock);
|
|
|
|
DESTROY_CONDVAR(jobQNotEmptyCv);
|
|
DESTROY_CONDVAR(freeListNotEmptyCv);
|
|
DESTROY_CONDVAR(threadCountChangeCv);
|
|
|
|
PR_DestroyLock(lastLoadedCrlLock);
|
|
DESTROY_LOCK(qLock);
|
|
PR_Free(jobTable);
|
|
PR_Free(threads);
|
|
}
|
|
|
|
static void
|
|
logger(void *arg)
|
|
{
|
|
PRFloat64 seconds;
|
|
PRFloat64 opsPerSec;
|
|
PRIntervalTime period;
|
|
PRIntervalTime previousTime;
|
|
PRIntervalTime latestTime;
|
|
PRInt32 previousOps;
|
|
PRInt32 ops;
|
|
PRIntervalTime logPeriodTicks = PR_TicksPerSecond();
|
|
PRFloat64 secondsPerTick = 1.0 / (PRFloat64)logPeriodTicks;
|
|
int iterations = 0;
|
|
int secondsElapsed = 0;
|
|
static PRInt64 totalPeriodBytes = 0;
|
|
static PRInt64 totalPeriodBytesTCP = 0;
|
|
|
|
previousOps = loggerOps;
|
|
previousTime = PR_IntervalNow();
|
|
|
|
for (;;) {
|
|
/* OK, implementing a new sleep algorithm here... always sleep
|
|
* for 1 second but print out info at the user-specified interval.
|
|
* This way, we don't overflow all of our PR_Atomic* functions and
|
|
* we don't have to use locks.
|
|
*/
|
|
PR_Sleep(logPeriodTicks);
|
|
secondsElapsed++;
|
|
totalPeriodBytes += PR_ATOMIC_SET(&loggerBytes, 0);
|
|
totalPeriodBytesTCP += PR_ATOMIC_SET(&loggerBytesTCP, 0);
|
|
if (secondsElapsed != logPeriod) {
|
|
continue;
|
|
}
|
|
/* when we reach the user-specified logging interval, print out all
|
|
* data
|
|
*/
|
|
secondsElapsed = 0;
|
|
latestTime = PR_IntervalNow();
|
|
ops = loggerOps;
|
|
period = latestTime - previousTime;
|
|
seconds = (PRFloat64)period * secondsPerTick;
|
|
opsPerSec = (ops - previousOps) / seconds;
|
|
|
|
if (testBulk) {
|
|
if (iterations == 0) {
|
|
if (loggingLayer == PR_TRUE) {
|
|
printf("Conn.--------App Data--------TCP Data\n");
|
|
} else {
|
|
printf("Conn.--------App Data\n");
|
|
}
|
|
}
|
|
if (loggingLayer == PR_TRUE) {
|
|
printf("%4.d %5.3f MB/s %5.3f MB/s\n", ops,
|
|
totalPeriodBytes / (seconds * 1048576.0),
|
|
totalPeriodBytesTCP / (seconds * 1048576.0));
|
|
} else {
|
|
printf("%4.d %5.3f MB/s\n", ops,
|
|
totalPeriodBytes / (seconds * 1048576.0));
|
|
}
|
|
totalPeriodBytes = 0;
|
|
totalPeriodBytesTCP = 0;
|
|
/* Print the "legend" every 20 iterations */
|
|
iterations = (iterations + 1) % 20;
|
|
} else {
|
|
printf("%.2f ops/second, %d threads\n", opsPerSec, threadCount);
|
|
}
|
|
|
|
fflush(stdout);
|
|
previousOps = ops;
|
|
previousTime = latestTime;
|
|
if (stopping) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************************************************
|
|
** End thread management routines.
|
|
**************************************************************************/
|
|
|
|
PRBool useModelSocket = PR_FALSE;
|
|
static SSLVersionRange enabledVersions;
|
|
PRBool disableRollBack = PR_FALSE;
|
|
PRBool NoReuse = PR_FALSE;
|
|
PRBool hasSidCache = PR_FALSE;
|
|
PRBool disableLocking = PR_FALSE;
|
|
PRBool enableSessionTickets = PR_FALSE;
|
|
PRBool enableCompression = PR_FALSE;
|
|
PRBool failedToNegotiateName = PR_FALSE;
|
|
PRBool enableExtendedMasterSecret = PR_FALSE;
|
|
PRBool zeroRTT = PR_FALSE;
|
|
PRBool enableALPN = PR_FALSE;
|
|
SSLNamedGroup *enabledGroups = NULL;
|
|
unsigned int enabledGroupsCount = 0;
|
|
|
|
static char *virtServerNameArray[MAX_VIRT_SERVER_NAME_ARRAY_INDEX];
|
|
static int virtServerNameIndex = 1;
|
|
|
|
static char *certNicknameArray[MAX_CERT_NICKNAME_ARRAY_INDEX];
|
|
static int certNicknameIndex = 0;
|
|
|
|
static const char stopCmd[] = { "GET /stop " };
|
|
static const char getCmd[] = { "GET " };
|
|
static const char EOFmsg[] = { "EOF\r\n\r\n\r\n" };
|
|
static const char outHeader[] = {
|
|
"HTTP/1.0 200 OK\r\n"
|
|
"Server: Generic Web Server\r\n"
|
|
"Date: Tue, 26 Aug 1997 22:10:05 GMT\r\n"
|
|
"Content-type: text/plain\r\n"
|
|
"\r\n"
|
|
};
|
|
static const char crlCacheErr[] = { "CRL ReCache Error: " };
|
|
|
|
PRUint16 cipherlist[100];
|
|
int nciphers;
|
|
|
|
void
|
|
savecipher(int c)
|
|
{
|
|
if (nciphers < sizeof cipherlist / sizeof(cipherlist[0]))
|
|
cipherlist[nciphers++] = (PRUint16)c;
|
|
}
|
|
|
|
#ifdef FULL_DUPLEX_CAPABLE
|
|
|
|
struct lockedVarsStr {
|
|
PZLock *lock;
|
|
int count;
|
|
int waiters;
|
|
PZCondVar *condVar;
|
|
};
|
|
|
|
typedef struct lockedVarsStr lockedVars;
|
|
|
|
void
|
|
lockedVars_Init(lockedVars *lv)
|
|
{
|
|
lv->count = 0;
|
|
lv->waiters = 0;
|
|
lv->lock = PZ_NewLock(nssILockSelfServ);
|
|
lv->condVar = PZ_NewCondVar(lv->lock);
|
|
}
|
|
|
|
void
|
|
lockedVars_Destroy(lockedVars *lv)
|
|
{
|
|
PZ_DestroyCondVar(lv->condVar);
|
|
lv->condVar = NULL;
|
|
|
|
PZ_DestroyLock(lv->lock);
|
|
lv->lock = NULL;
|
|
}
|
|
|
|
void
|
|
lockedVars_WaitForDone(lockedVars *lv)
|
|
{
|
|
PZ_Lock(lv->lock);
|
|
while (lv->count > 0) {
|
|
PZ_WaitCondVar(lv->condVar, PR_INTERVAL_NO_TIMEOUT);
|
|
}
|
|
PZ_Unlock(lv->lock);
|
|
}
|
|
|
|
int /* returns count */
|
|
lockedVars_AddToCount(lockedVars *lv, int addend)
|
|
{
|
|
int rv;
|
|
|
|
PZ_Lock(lv->lock);
|
|
rv = lv->count += addend;
|
|
if (rv <= 0) {
|
|
PZ_NotifyCondVar(lv->condVar);
|
|
}
|
|
PZ_Unlock(lv->lock);
|
|
return rv;
|
|
}
|
|
|
|
int
|
|
do_writes(
|
|
PRFileDesc *ssl_sock,
|
|
PRFileDesc *model_sock,
|
|
int requestCert)
|
|
{
|
|
int sent = 0;
|
|
int count = 0;
|
|
lockedVars *lv = (lockedVars *)model_sock;
|
|
|
|
VLOG(("selfserv: do_writes: starting"));
|
|
while (sent < bigBuf.len) {
|
|
|
|
count = PR_Write(ssl_sock, bigBuf.data + sent, bigBuf.len - sent);
|
|
if (count < 0) {
|
|
errWarn("PR_Write bigBuf");
|
|
break;
|
|
}
|
|
FPRINTF(stderr, "selfserv: PR_Write wrote %d bytes from bigBuf\n", count);
|
|
sent += count;
|
|
}
|
|
if (count >= 0) { /* last write didn't fail. */
|
|
PR_Shutdown(ssl_sock, PR_SHUTDOWN_SEND);
|
|
}
|
|
|
|
/* notify the reader that we're done. */
|
|
lockedVars_AddToCount(lv, -1);
|
|
FLUSH;
|
|
VLOG(("selfserv: do_writes: exiting"));
|
|
return (sent < bigBuf.len) ? SECFailure : SECSuccess;
|
|
}
|
|
|
|
static int
|
|
handle_fdx_connection(
|
|
PRFileDesc *tcp_sock,
|
|
PRFileDesc *model_sock,
|
|
int requestCert)
|
|
{
|
|
PRFileDesc *ssl_sock = NULL;
|
|
SECStatus result;
|
|
int firstTime = 1;
|
|
lockedVars lv;
|
|
PRSocketOptionData opt;
|
|
char buf[10240];
|
|
|
|
VLOG(("selfserv: handle_fdx_connection: starting"));
|
|
opt.option = PR_SockOpt_Nonblocking;
|
|
opt.value.non_blocking = PR_FALSE;
|
|
PR_SetSocketOption(tcp_sock, &opt);
|
|
|
|
if (useModelSocket && model_sock) {
|
|
SECStatus rv;
|
|
ssl_sock = SSL_ImportFD(model_sock, tcp_sock);
|
|
if (!ssl_sock) {
|
|
errWarn("SSL_ImportFD with model");
|
|
goto cleanup;
|
|
}
|
|
rv = SSL_ResetHandshake(ssl_sock, /* asServer */ 1);
|
|
if (rv != SECSuccess) {
|
|
errWarn("SSL_ResetHandshake");
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
ssl_sock = tcp_sock;
|
|
}
|
|
|
|
lockedVars_Init(&lv);
|
|
lockedVars_AddToCount(&lv, 1);
|
|
|
|
/* Attempt to launch the writer thread. */
|
|
result = launch_thread(do_writes, ssl_sock, (PRFileDesc *)&lv,
|
|
requestCert);
|
|
|
|
if (result == SECSuccess)
|
|
do {
|
|
/* do reads here. */
|
|
int count;
|
|
count = PR_Read(ssl_sock, buf, sizeof buf);
|
|
if (count < 0) {
|
|
errWarn("FDX PR_Read");
|
|
break;
|
|
}
|
|
FPRINTF(stderr, "selfserv: FDX PR_Read read %d bytes.\n", count);
|
|
if (firstTime) {
|
|
firstTime = 0;
|
|
printSecurityInfo(ssl_sock);
|
|
}
|
|
} while (lockedVars_AddToCount(&lv, 0) > 0);
|
|
|
|
/* Wait for writer to finish */
|
|
lockedVars_WaitForDone(&lv);
|
|
lockedVars_Destroy(&lv);
|
|
FLUSH;
|
|
|
|
cleanup:
|
|
if (ssl_sock) {
|
|
PR_Close(ssl_sock);
|
|
} else if (tcp_sock) {
|
|
PR_Close(tcp_sock);
|
|
}
|
|
|
|
VLOG(("selfserv: handle_fdx_connection: exiting"));
|
|
return SECSuccess;
|
|
}
|
|
|
|
#endif
|
|
|
|
static SECItem *lastLoadedCrl = NULL;
|
|
|
|
static SECStatus
|
|
reload_crl(PRFileDesc *crlFile)
|
|
{
|
|
SECItem *crlDer;
|
|
CERTCertDBHandle *certHandle = CERT_GetDefaultCertDB();
|
|
SECStatus rv;
|
|
|
|
/* Read in the entire file specified with the -f argument */
|
|
crlDer = PORT_Malloc(sizeof(SECItem));
|
|
if (!crlDer) {
|
|
errWarn("Can not allocate memory.");
|
|
return SECFailure;
|
|
}
|
|
|
|
rv = SECU_ReadDERFromFile(crlDer, crlFile, PR_FALSE, PR_FALSE);
|
|
if (rv != SECSuccess) {
|
|
errWarn("Unable to read input file.");
|
|
PORT_Free(crlDer);
|
|
return SECFailure;
|
|
}
|
|
|
|
PR_Lock(lastLoadedCrlLock);
|
|
rv = CERT_CacheCRL(certHandle, crlDer);
|
|
if (rv == SECSuccess) {
|
|
SECItem *tempItem = crlDer;
|
|
if (lastLoadedCrl != NULL) {
|
|
rv = CERT_UncacheCRL(certHandle, lastLoadedCrl);
|
|
if (rv != SECSuccess) {
|
|
errWarn("Unable to uncache crl.");
|
|
goto loser;
|
|
}
|
|
crlDer = lastLoadedCrl;
|
|
} else {
|
|
crlDer = NULL;
|
|
}
|
|
lastLoadedCrl = tempItem;
|
|
}
|
|
|
|
loser:
|
|
PR_Unlock(lastLoadedCrlLock);
|
|
SECITEM_FreeItem(crlDer, PR_TRUE);
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
stop_server()
|
|
{
|
|
stopping = 1;
|
|
PR_Interrupt(acceptorThread);
|
|
PZ_TraceFlush();
|
|
}
|
|
|
|
SECItemArray *
|
|
makeTryLaterOCSPResponse(PLArenaPool *arena)
|
|
{
|
|
SECItemArray *result = NULL;
|
|
SECItem *ocspResponse = NULL;
|
|
|
|
ocspResponse = CERT_CreateEncodedOCSPErrorResponse(arena,
|
|
SEC_ERROR_OCSP_TRY_SERVER_LATER);
|
|
if (!ocspResponse)
|
|
errExit("cannot created ocspResponse");
|
|
|
|
result = SECITEM_AllocArray(arena, NULL, 1);
|
|
if (!result)
|
|
errExit("cannot allocate multiOcspResponses");
|
|
|
|
result->items[0].data = ocspResponse->data;
|
|
result->items[0].len = ocspResponse->len;
|
|
|
|
return result;
|
|
}
|
|
|
|
SECItemArray *
|
|
makeCorruptedOCSPResponse(PLArenaPool *arena)
|
|
{
|
|
SECItemArray *result = NULL;
|
|
SECItem *ocspResponse = NULL;
|
|
|
|
ocspResponse = SECITEM_AllocItem(arena, NULL, 1);
|
|
if (!ocspResponse)
|
|
errExit("cannot created ocspResponse");
|
|
|
|
result = SECITEM_AllocArray(arena, NULL, 1);
|
|
if (!result)
|
|
errExit("cannot allocate multiOcspResponses");
|
|
|
|
result->items[0].data = ocspResponse->data;
|
|
result->items[0].len = ocspResponse->len;
|
|
|
|
return result;
|
|
}
|
|
|
|
SECItemArray *
|
|
makeSignedOCSPResponse(PLArenaPool *arena, ocspStaplingModeType osm,
|
|
CERTCertificate *cert, secuPWData *pwdata)
|
|
{
|
|
SECItemArray *result = NULL;
|
|
SECItem *ocspResponse = NULL;
|
|
CERTOCSPSingleResponse **singleResponses;
|
|
CERTOCSPSingleResponse *sr = NULL;
|
|
CERTOCSPCertID *cid = NULL;
|
|
CERTCertificate *ca;
|
|
PRTime now = PR_Now();
|
|
PRTime nextUpdate;
|
|
|
|
PORT_Assert(cert != NULL);
|
|
|
|
ca = CERT_FindCertByNickname(CERT_GetDefaultCertDB(), ocspStaplingCA);
|
|
if (!ca)
|
|
errExit("cannot find CA");
|
|
|
|
cid = CERT_CreateOCSPCertID(cert, now);
|
|
if (!cid)
|
|
errExit("cannot created cid");
|
|
|
|
nextUpdate = now + (PRTime)60 * 60 * 24 * PR_USEC_PER_SEC; /* plus 1 day */
|
|
|
|
switch (osm) {
|
|
case osm_good:
|
|
case osm_badsig:
|
|
sr = CERT_CreateOCSPSingleResponseGood(arena, cid, now,
|
|
&nextUpdate);
|
|
break;
|
|
case osm_unknown:
|
|
sr = CERT_CreateOCSPSingleResponseUnknown(arena, cid, now,
|
|
&nextUpdate);
|
|
break;
|
|
case osm_revoked:
|
|
sr = CERT_CreateOCSPSingleResponseRevoked(arena, cid, now,
|
|
&nextUpdate,
|
|
now - (PRTime)60 * 60 * 24 * PR_USEC_PER_SEC, /* minus 1 day */
|
|
NULL);
|
|
break;
|
|
default:
|
|
PORT_Assert(0);
|
|
break;
|
|
}
|
|
|
|
if (!sr)
|
|
errExit("cannot create sr");
|
|
|
|
/* meaning of value 2: one entry + one end marker */
|
|
singleResponses = PORT_ArenaNewArray(arena, CERTOCSPSingleResponse *, 2);
|
|
if (singleResponses == NULL)
|
|
errExit("cannot allocate singleResponses");
|
|
|
|
singleResponses[0] = sr;
|
|
singleResponses[1] = NULL;
|
|
|
|
ocspResponse = CERT_CreateEncodedOCSPSuccessResponse(arena,
|
|
(osm == osm_badsig)
|
|
? NULL
|
|
: ca,
|
|
ocspResponderID_byName, now, singleResponses,
|
|
&pwdata);
|
|
if (!ocspResponse)
|
|
errExit("cannot created ocspResponse");
|
|
|
|
CERT_DestroyCertificate(ca);
|
|
ca = NULL;
|
|
|
|
result = SECITEM_AllocArray(arena, NULL, 1);
|
|
if (!result)
|
|
errExit("cannot allocate multiOcspResponses");
|
|
|
|
result->items[0].data = ocspResponse->data;
|
|
result->items[0].len = ocspResponse->len;
|
|
|
|
CERT_DestroyOCSPCertID(cid);
|
|
cid = NULL;
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
setupCertStatus(PLArenaPool *arena, enum ocspStaplingModeEnum ocspStaplingMode,
|
|
CERTCertificate *cert, int index, secuPWData *pwdata)
|
|
{
|
|
if (ocspStaplingMode == osm_random) {
|
|
/* 6 different responses */
|
|
int r = rand() % 6;
|
|
switch (r) {
|
|
case 0:
|
|
ocspStaplingMode = osm_good;
|
|
break;
|
|
case 1:
|
|
ocspStaplingMode = osm_revoked;
|
|
break;
|
|
case 2:
|
|
ocspStaplingMode = osm_unknown;
|
|
break;
|
|
case 3:
|
|
ocspStaplingMode = osm_badsig;
|
|
break;
|
|
case 4:
|
|
ocspStaplingMode = osm_corrupted;
|
|
break;
|
|
case 5:
|
|
ocspStaplingMode = osm_failure;
|
|
break;
|
|
default:
|
|
PORT_Assert(0);
|
|
break;
|
|
}
|
|
}
|
|
if (ocspStaplingMode != osm_disabled) {
|
|
SECItemArray *multiOcspResponses = NULL;
|
|
switch (ocspStaplingMode) {
|
|
case osm_good:
|
|
case osm_revoked:
|
|
case osm_unknown:
|
|
case osm_badsig:
|
|
multiOcspResponses =
|
|
makeSignedOCSPResponse(arena, ocspStaplingMode, cert,
|
|
pwdata);
|
|
break;
|
|
case osm_corrupted:
|
|
multiOcspResponses = makeCorruptedOCSPResponse(arena);
|
|
break;
|
|
case osm_failure:
|
|
multiOcspResponses = makeTryLaterOCSPResponse(arena);
|
|
break;
|
|
case osm_ocsp:
|
|
errExit("stapling mode \"ocsp\" not implemented");
|
|
break;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (multiOcspResponses) {
|
|
certStatus[index] = multiOcspResponses;
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
handle_connection(
|
|
PRFileDesc *tcp_sock,
|
|
PRFileDesc *model_sock,
|
|
int requestCert)
|
|
{
|
|
PRFileDesc *ssl_sock = NULL;
|
|
PRFileDesc *local_file_fd = NULL;
|
|
char *post;
|
|
char *pBuf; /* unused space at end of buf */
|
|
const char *errString;
|
|
PRStatus status;
|
|
int bufRem; /* unused bytes at end of buf */
|
|
int bufDat; /* characters received in buf */
|
|
int newln = 0; /* # of consecutive newlns */
|
|
int firstTime = 1;
|
|
int reqLen;
|
|
int rv;
|
|
int numIOVs;
|
|
PRSocketOptionData opt;
|
|
PRIOVec iovs[16];
|
|
char msgBuf[160];
|
|
char buf[10240] = { 0 };
|
|
char fileName[513];
|
|
char proto[128];
|
|
PRDescIdentity aboveLayer = PR_INVALID_IO_LAYER;
|
|
|
|
pBuf = buf;
|
|
bufRem = sizeof buf;
|
|
|
|
VLOG(("selfserv: handle_connection: starting"));
|
|
opt.option = PR_SockOpt_Nonblocking;
|
|
opt.value.non_blocking = PR_FALSE;
|
|
PR_SetSocketOption(tcp_sock, &opt);
|
|
|
|
VLOG(("selfserv: handle_connection: starting\n"));
|
|
if (useModelSocket && model_sock) {
|
|
SECStatus rv;
|
|
ssl_sock = SSL_ImportFD(model_sock, tcp_sock);
|
|
if (!ssl_sock) {
|
|
errWarn("SSL_ImportFD with model");
|
|
goto cleanup;
|
|
}
|
|
rv = SSL_ResetHandshake(ssl_sock, /* asServer */ 1);
|
|
if (rv != SECSuccess) {
|
|
errWarn("SSL_ResetHandshake");
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
ssl_sock = tcp_sock;
|
|
}
|
|
|
|
if (loggingLayer) {
|
|
/* find the layer where our new layer is to be pushed */
|
|
aboveLayer = PR_GetLayersIdentity(ssl_sock->lower);
|
|
if (aboveLayer == PR_INVALID_IO_LAYER) {
|
|
errExit("PRGetUniqueIdentity");
|
|
}
|
|
/* create the new layer - this is a very cheap operation */
|
|
loggingFD = PR_CreateIOLayerStub(log_layer_id, &loggingMethods);
|
|
if (!loggingFD)
|
|
errExit("PR_CreateIOLayerStub");
|
|
/* push the layer below ssl but above TCP */
|
|
rv = PR_PushIOLayer(ssl_sock, aboveLayer, loggingFD);
|
|
if (rv != PR_SUCCESS) {
|
|
errExit("PR_PushIOLayer");
|
|
}
|
|
}
|
|
|
|
if (noDelay) {
|
|
opt.option = PR_SockOpt_NoDelay;
|
|
opt.value.no_delay = PR_TRUE;
|
|
status = PR_SetSocketOption(ssl_sock, &opt);
|
|
if (status != PR_SUCCESS) {
|
|
errWarn("PR_SetSocketOption(PR_SockOpt_NoDelay, PR_TRUE)");
|
|
if (ssl_sock) {
|
|
PR_Close(ssl_sock);
|
|
}
|
|
return SECFailure;
|
|
}
|
|
}
|
|
|
|
while (1) {
|
|
newln = 0;
|
|
reqLen = 0;
|
|
rv = PR_Read(ssl_sock, pBuf, bufRem - 1);
|
|
if (rv == 0 ||
|
|
(rv < 0 && PR_END_OF_FILE_ERROR == PR_GetError())) {
|
|
if (verbose)
|
|
errWarn("HDX PR_Read hit EOF");
|
|
break;
|
|
}
|
|
if (rv < 0) {
|
|
errWarn("HDX PR_Read");
|
|
goto cleanup;
|
|
}
|
|
/* NULL termination */
|
|
pBuf[rv] = 0;
|
|
if (firstTime) {
|
|
firstTime = 0;
|
|
printSecurityInfo(ssl_sock);
|
|
}
|
|
|
|
pBuf += rv;
|
|
bufRem -= rv;
|
|
bufDat = pBuf - buf;
|
|
/* Parse the input, starting at the beginning of the buffer.
|
|
* Stop when we detect two consecutive \n's (or \r\n's)
|
|
* as this signifies the end of the GET or POST portion.
|
|
* The posted data follows.
|
|
*/
|
|
while (reqLen < bufDat && newln < 2) {
|
|
int octet = buf[reqLen++];
|
|
if (octet == '\n') {
|
|
newln++;
|
|
} else if (octet != '\r') {
|
|
newln = 0;
|
|
}
|
|
}
|
|
|
|
/* came to the end of the buffer, or second newln
|
|
* If we didn't get an empty line (CRLFCRLF) then keep on reading.
|
|
*/
|
|
if (newln < 2)
|
|
continue;
|
|
|
|
/* we're at the end of the HTTP request.
|
|
* If the request is a POST, then there will be one more
|
|
* line of data.
|
|
* This parsing is a hack, but ok for SSL test purposes.
|
|
*/
|
|
post = PORT_Strstr(buf, "POST ");
|
|
if (!post || *post != 'P')
|
|
break;
|
|
|
|
/* It's a post, so look for the next and final CR/LF. */
|
|
/* We should parse content length here, but ... */
|
|
while (reqLen < bufDat && newln < 3) {
|
|
int octet = buf[reqLen++];
|
|
if (octet == '\n') {
|
|
newln++;
|
|
}
|
|
}
|
|
if (newln == 3)
|
|
break;
|
|
} /* read loop */
|
|
|
|
bufDat = pBuf - buf;
|
|
if (bufDat)
|
|
do { /* just close if no data */
|
|
/* Have either (a) a complete get, (b) a complete post, (c) EOF */
|
|
if (reqLen > 0 && !strncmp(buf, getCmd, sizeof getCmd - 1)) {
|
|
char *fnBegin = buf + 4;
|
|
char *fnEnd;
|
|
PRFileInfo info;
|
|
/* try to open the file named.
|
|
* If successful, then write it to the client.
|
|
*/
|
|
fnEnd = strpbrk(fnBegin, " \r\n");
|
|
if (fnEnd) {
|
|
int fnLen = fnEnd - fnBegin;
|
|
if (fnLen < sizeof fileName) {
|
|
char *real_fileName = fileName;
|
|
char *protoEnd = NULL;
|
|
strncpy(fileName, fnBegin, fnLen);
|
|
fileName[fnLen] = 0; /* null terminate */
|
|
if ((protoEnd = strstr(fileName, "://")) != NULL) {
|
|
int protoLen = PR_MIN(protoEnd - fileName, sizeof(proto) - 1);
|
|
PL_strncpy(proto, fileName, protoLen);
|
|
proto[protoLen] = 0;
|
|
real_fileName = protoEnd + 3;
|
|
} else {
|
|
proto[0] = 0;
|
|
}
|
|
status = PR_GetFileInfo(real_fileName, &info);
|
|
if (status == PR_SUCCESS &&
|
|
info.type == PR_FILE_FILE &&
|
|
info.size >= 0) {
|
|
local_file_fd = PR_Open(real_fileName, PR_RDONLY, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* if user has requested client auth in a subsequent handshake,
|
|
* do it here.
|
|
*/
|
|
if (requestCert > 2) { /* request cert was 3 or 4 */
|
|
CERTCertificate *cert = SSL_PeerCertificate(ssl_sock);
|
|
if (cert) {
|
|
CERT_DestroyCertificate(cert);
|
|
} else {
|
|
rv = SSL_OptionSet(ssl_sock, SSL_REQUEST_CERTIFICATE, 1);
|
|
if (rv < 0) {
|
|
errWarn("second SSL_OptionSet SSL_REQUEST_CERTIFICATE");
|
|
break;
|
|
}
|
|
rv = SSL_OptionSet(ssl_sock, SSL_REQUIRE_CERTIFICATE,
|
|
(requestCert == 4));
|
|
if (rv < 0) {
|
|
errWarn("second SSL_OptionSet SSL_REQUIRE_CERTIFICATE");
|
|
break;
|
|
}
|
|
rv = SSL_ReHandshake(ssl_sock, PR_TRUE);
|
|
if (rv != 0) {
|
|
errWarn("SSL_ReHandshake");
|
|
break;
|
|
}
|
|
rv = SSL_ForceHandshake(ssl_sock);
|
|
if (rv < 0) {
|
|
errWarn("SSL_ForceHandshake");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
numIOVs = 0;
|
|
|
|
iovs[numIOVs].iov_base = (char *)outHeader;
|
|
iovs[numIOVs].iov_len = (sizeof(outHeader)) - 1;
|
|
numIOVs++;
|
|
|
|
if (local_file_fd) {
|
|
PRInt32 bytes;
|
|
int errLen;
|
|
if (!PL_strlen(proto) || !PL_strcmp(proto, "file")) {
|
|
bytes = PR_TransmitFile(ssl_sock, local_file_fd, outHeader,
|
|
sizeof outHeader - 1,
|
|
PR_TRANSMITFILE_KEEP_OPEN,
|
|
PR_INTERVAL_NO_TIMEOUT);
|
|
if (bytes >= 0) {
|
|
bytes -= sizeof outHeader - 1;
|
|
FPRINTF(stderr,
|
|
"selfserv: PR_TransmitFile wrote %d bytes from %s\n",
|
|
bytes, fileName);
|
|
break;
|
|
}
|
|
errString = errWarn("PR_TransmitFile");
|
|
errLen = PORT_Strlen(errString);
|
|
errLen = PR_MIN(errLen, sizeof msgBuf - 1);
|
|
PORT_Memcpy(msgBuf, errString, errLen);
|
|
msgBuf[errLen] = 0;
|
|
|
|
iovs[numIOVs].iov_base = msgBuf;
|
|
iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
|
|
numIOVs++;
|
|
}
|
|
if (!PL_strcmp(proto, "crl")) {
|
|
if (reload_crl(local_file_fd) == SECFailure) {
|
|
errString = errWarn("CERT_CacheCRL");
|
|
if (!errString)
|
|
errString = "Unknow error";
|
|
PR_snprintf(msgBuf, sizeof(msgBuf), "%s%s ",
|
|
crlCacheErr, errString);
|
|
|
|
iovs[numIOVs].iov_base = msgBuf;
|
|
iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
|
|
numIOVs++;
|
|
} else {
|
|
FPRINTF(stderr,
|
|
"selfserv: CRL %s reloaded.\n",
|
|
fileName);
|
|
break;
|
|
}
|
|
}
|
|
} else if (reqLen <= 0) { /* hit eof */
|
|
PORT_Sprintf(msgBuf, "Get or Post incomplete after %d bytes.\r\n",
|
|
bufDat);
|
|
|
|
iovs[numIOVs].iov_base = msgBuf;
|
|
iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
|
|
numIOVs++;
|
|
} else if (reqLen < bufDat) {
|
|
PORT_Sprintf(msgBuf, "Discarded %d characters.\r\n",
|
|
bufDat - reqLen);
|
|
|
|
iovs[numIOVs].iov_base = msgBuf;
|
|
iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
|
|
numIOVs++;
|
|
}
|
|
|
|
if (reqLen > 0) {
|
|
if (verbose > 1)
|
|
fwrite(buf, 1, reqLen, stdout); /* display it */
|
|
|
|
iovs[numIOVs].iov_base = buf;
|
|
iovs[numIOVs].iov_len = reqLen;
|
|
numIOVs++;
|
|
}
|
|
|
|
/* Don't add the EOF if we want to test bulk encryption */
|
|
if (!testBulk) {
|
|
iovs[numIOVs].iov_base = (char *)EOFmsg;
|
|
iovs[numIOVs].iov_len = sizeof EOFmsg - 1;
|
|
numIOVs++;
|
|
}
|
|
|
|
rv = PR_Writev(ssl_sock, iovs, numIOVs, PR_INTERVAL_NO_TIMEOUT);
|
|
if (rv < 0) {
|
|
errWarn("PR_Writev");
|
|
break;
|
|
}
|
|
|
|
/* Send testBulkTotal chunks to the client. Unlimited if 0. */
|
|
if (testBulk) {
|
|
while (0 < (rv = PR_Write(ssl_sock, testBulkBuf, testBulkSize))) {
|
|
PR_ATOMIC_ADD(&loggerBytes, rv);
|
|
PR_ATOMIC_INCREMENT(&bulkSentChunks);
|
|
if ((bulkSentChunks > testBulkTotal) && (testBulkTotal != 0))
|
|
break;
|
|
}
|
|
|
|
/* There was a write error, so close this connection. */
|
|
if (bulkSentChunks <= testBulkTotal) {
|
|
errWarn("PR_Write");
|
|
}
|
|
PR_ATOMIC_DECREMENT(&loggerOps);
|
|
break;
|
|
}
|
|
} while (0);
|
|
|
|
cleanup:
|
|
if (ssl_sock) {
|
|
PR_Close(ssl_sock);
|
|
} else if (tcp_sock) {
|
|
PR_Close(tcp_sock);
|
|
}
|
|
if (local_file_fd)
|
|
PR_Close(local_file_fd);
|
|
VLOG(("selfserv: handle_connection: exiting\n"));
|
|
|
|
/* do a nice shutdown if asked. */
|
|
if (!strncmp(buf, stopCmd, sizeof stopCmd - 1)) {
|
|
VLOG(("selfserv: handle_connection: stop command"));
|
|
stop_server();
|
|
}
|
|
VLOG(("selfserv: handle_connection: exiting"));
|
|
return SECSuccess; /* success */
|
|
}
|
|
|
|
#ifdef XP_UNIX
|
|
|
|
void
|
|
sigusr1_handler(int sig)
|
|
{
|
|
VLOG(("selfserv: sigusr1_handler: stop server"));
|
|
stop_server();
|
|
}
|
|
|
|
#endif
|
|
|
|
SECStatus
|
|
do_accepts(
|
|
PRFileDesc *listen_sock,
|
|
PRFileDesc *model_sock,
|
|
int requestCert)
|
|
{
|
|
PRNetAddr addr;
|
|
PRErrorCode perr;
|
|
#ifdef XP_UNIX
|
|
struct sigaction act;
|
|
#endif
|
|
|
|
VLOG(("selfserv: do_accepts: starting"));
|
|
PR_SetThreadPriority(PR_GetCurrentThread(), PR_PRIORITY_HIGH);
|
|
|
|
acceptorThread = PR_GetCurrentThread();
|
|
#ifdef XP_UNIX
|
|
/* set up the signal handler */
|
|
act.sa_handler = sigusr1_handler;
|
|
sigemptyset(&act.sa_mask);
|
|
act.sa_flags = 0;
|
|
if (sigaction(SIGUSR1, &act, NULL)) {
|
|
fprintf(stderr, "Error installing signal handler.\n");
|
|
exit(1);
|
|
}
|
|
#endif
|
|
while (!stopping) {
|
|
PRFileDesc *tcp_sock;
|
|
PRCList *myLink;
|
|
|
|
FPRINTF(stderr, "\n\n\nselfserv: About to call accept.\n");
|
|
tcp_sock = PR_Accept(listen_sock, &addr, PR_INTERVAL_NO_TIMEOUT);
|
|
if (tcp_sock == NULL) {
|
|
perr = PR_GetError();
|
|
if ((perr != PR_CONNECT_RESET_ERROR &&
|
|
perr != PR_PENDING_INTERRUPT_ERROR) ||
|
|
verbose) {
|
|
errWarn("PR_Accept");
|
|
}
|
|
if (perr == PR_CONNECT_RESET_ERROR) {
|
|
FPRINTF(stderr,
|
|
"Ignoring PR_CONNECT_RESET_ERROR error - continue\n");
|
|
continue;
|
|
}
|
|
stopping = 1;
|
|
break;
|
|
}
|
|
|
|
VLOG(("selfserv: do_accept: Got connection\n"));
|
|
|
|
if (logStats) {
|
|
PR_ATOMIC_INCREMENT(&loggerOps);
|
|
}
|
|
|
|
PZ_Lock(qLock);
|
|
while (PR_CLIST_IS_EMPTY(&freeJobs) && !stopping) {
|
|
PZ_WaitCondVar(freeListNotEmptyCv, PR_INTERVAL_NO_TIMEOUT);
|
|
}
|
|
if (stopping) {
|
|
PZ_Unlock(qLock);
|
|
if (tcp_sock) {
|
|
PR_Close(tcp_sock);
|
|
}
|
|
break;
|
|
}
|
|
myLink = PR_LIST_HEAD(&freeJobs);
|
|
PR_REMOVE_AND_INIT_LINK(myLink);
|
|
/* could release qLock here and reaquire it 7 lines below, but
|
|
** why bother for 4 assignment statements?
|
|
*/
|
|
{
|
|
JOB *myJob = (JOB *)myLink;
|
|
myJob->tcp_sock = tcp_sock;
|
|
myJob->model_sock = model_sock;
|
|
myJob->requestCert = requestCert;
|
|
}
|
|
|
|
PR_APPEND_LINK(myLink, &jobQ);
|
|
PZ_NotifyCondVar(jobQNotEmptyCv);
|
|
PZ_Unlock(qLock);
|
|
}
|
|
|
|
FPRINTF(stderr, "selfserv: Closing listen socket.\n");
|
|
VLOG(("selfserv: do_accepts: exiting"));
|
|
if (listen_sock) {
|
|
PR_Close(listen_sock);
|
|
}
|
|
return SECSuccess;
|
|
}
|
|
|
|
PRFileDesc *
|
|
getBoundListenSocket(unsigned short port)
|
|
{
|
|
PRFileDesc *listen_sock;
|
|
int listenQueueDepth = 5 + (2 * maxThreads);
|
|
PRStatus prStatus;
|
|
PRNetAddr addr;
|
|
PRSocketOptionData opt;
|
|
|
|
addr.inet.family = PR_AF_INET;
|
|
addr.inet.ip = PR_INADDR_ANY;
|
|
addr.inet.port = PR_htons(port);
|
|
|
|
listen_sock = PR_NewTCPSocket();
|
|
if (listen_sock == NULL) {
|
|
errExit("PR_NewTCPSocket");
|
|
}
|
|
|
|
opt.option = PR_SockOpt_Nonblocking;
|
|
opt.value.non_blocking = PR_FALSE;
|
|
prStatus = PR_SetSocketOption(listen_sock, &opt);
|
|
if (prStatus < 0) {
|
|
PR_Close(listen_sock);
|
|
errExit("PR_SetSocketOption(PR_SockOpt_Nonblocking)");
|
|
}
|
|
|
|
opt.option = PR_SockOpt_Reuseaddr;
|
|
opt.value.reuse_addr = PR_TRUE;
|
|
prStatus = PR_SetSocketOption(listen_sock, &opt);
|
|
if (prStatus < 0) {
|
|
PR_Close(listen_sock);
|
|
errExit("PR_SetSocketOption(PR_SockOpt_Reuseaddr)");
|
|
}
|
|
|
|
#ifndef WIN95
|
|
/* Set PR_SockOpt_Linger because it helps prevent a server bind issue
|
|
* after clean shutdown . See bug 331413 .
|
|
* Don't do it in the WIN95 build configuration because clean shutdown is
|
|
* not implemented, and PR_SockOpt_Linger causes a hang in ssl.sh .
|
|
* See bug 332348 */
|
|
opt.option = PR_SockOpt_Linger;
|
|
opt.value.linger.polarity = PR_TRUE;
|
|
opt.value.linger.linger = PR_SecondsToInterval(1);
|
|
prStatus = PR_SetSocketOption(listen_sock, &opt);
|
|
if (prStatus < 0) {
|
|
PR_Close(listen_sock);
|
|
errExit("PR_SetSocketOption(PR_SockOpt_Linger)");
|
|
}
|
|
#endif
|
|
|
|
prStatus = PR_Bind(listen_sock, &addr);
|
|
if (prStatus < 0) {
|
|
PR_Close(listen_sock);
|
|
errExit("PR_Bind");
|
|
}
|
|
|
|
prStatus = PR_Listen(listen_sock, listenQueueDepth);
|
|
if (prStatus < 0) {
|
|
PR_Close(listen_sock);
|
|
errExit("PR_Listen");
|
|
}
|
|
return listen_sock;
|
|
}
|
|
|
|
PRInt32 PR_CALLBACK
|
|
logWritev(
|
|
PRFileDesc *fd,
|
|
const PRIOVec *iov,
|
|
PRInt32 size,
|
|
PRIntervalTime timeout)
|
|
{
|
|
PRInt32 rv = (fd->lower->methods->writev)(fd->lower, iov, size,
|
|
timeout);
|
|
/* Add the amount written, but not if there's an error */
|
|
if (rv > 0)
|
|
PR_ATOMIC_ADD(&loggerBytesTCP, rv);
|
|
return rv;
|
|
}
|
|
|
|
PRInt32 PR_CALLBACK
|
|
logWrite(
|
|
PRFileDesc *fd,
|
|
const void *buf,
|
|
PRInt32 amount)
|
|
{
|
|
PRInt32 rv = (fd->lower->methods->write)(fd->lower, buf, amount);
|
|
/* Add the amount written, but not if there's an error */
|
|
if (rv > 0)
|
|
PR_ATOMIC_ADD(&loggerBytesTCP, rv);
|
|
|
|
return rv;
|
|
}
|
|
|
|
PRInt32 PR_CALLBACK
|
|
logSend(
|
|
PRFileDesc *fd,
|
|
const void *buf,
|
|
PRInt32 amount,
|
|
PRIntn flags,
|
|
PRIntervalTime timeout)
|
|
{
|
|
PRInt32 rv = (fd->lower->methods->send)(fd->lower, buf, amount,
|
|
flags, timeout);
|
|
/* Add the amount written, but not if there's an error */
|
|
if (rv > 0)
|
|
PR_ATOMIC_ADD(&loggerBytesTCP, rv);
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
initLoggingLayer(void)
|
|
{
|
|
/* get a new layer ID */
|
|
log_layer_id = PR_GetUniqueIdentity("Selfserv Logging");
|
|
if (log_layer_id == PR_INVALID_IO_LAYER)
|
|
errExit("PR_GetUniqueIdentity");
|
|
|
|
/* setup the default IO methods with my custom write methods */
|
|
memcpy(&loggingMethods, PR_GetDefaultIOMethods(), sizeof(PRIOMethods));
|
|
loggingMethods.writev = logWritev;
|
|
loggingMethods.write = logWrite;
|
|
loggingMethods.send = logSend;
|
|
}
|
|
|
|
void
|
|
handshakeCallback(PRFileDesc *fd, void *client_data)
|
|
{
|
|
const char *handshakeName = (const char *)client_data;
|
|
if (handshakeName && !failedToNegotiateName) {
|
|
SECItem *hostInfo = SSL_GetNegotiatedHostInfo(fd);
|
|
if (!hostInfo || PORT_Strncmp(handshakeName, (char *)hostInfo->data,
|
|
hostInfo->len)) {
|
|
failedToNegotiateName = PR_TRUE;
|
|
}
|
|
if (hostInfo) {
|
|
SECITEM_FreeItem(hostInfo, PR_TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
server_main(
|
|
PRFileDesc *listen_sock,
|
|
int requestCert,
|
|
SECKEYPrivateKey **privKey,
|
|
CERTCertificate **cert,
|
|
const char *expectedHostNameVal)
|
|
{
|
|
int i;
|
|
PRFileDesc *model_sock = NULL;
|
|
int rv;
|
|
SECStatus secStatus;
|
|
|
|
if (useModelSocket) {
|
|
model_sock = PR_NewTCPSocket();
|
|
if (model_sock == NULL) {
|
|
errExit("PR_NewTCPSocket on model socket");
|
|
}
|
|
model_sock = SSL_ImportFD(NULL, model_sock);
|
|
if (model_sock == NULL) {
|
|
errExit("SSL_ImportFD");
|
|
}
|
|
} else {
|
|
model_sock = listen_sock = SSL_ImportFD(NULL, listen_sock);
|
|
if (listen_sock == NULL) {
|
|
errExit("SSL_ImportFD");
|
|
}
|
|
}
|
|
|
|
/* do SSL configuration. */
|
|
rv = SSL_OptionSet(model_sock, SSL_SECURITY, enabledVersions.min != 0);
|
|
if (rv < 0) {
|
|
errExit("SSL_OptionSet SSL_SECURITY");
|
|
}
|
|
|
|
rv = SSL_VersionRangeSet(model_sock, &enabledVersions);
|
|
if (rv != SECSuccess) {
|
|
errExit("error setting SSL/TLS version range ");
|
|
}
|
|
|
|
rv = SSL_OptionSet(model_sock, SSL_ROLLBACK_DETECTION, !disableRollBack);
|
|
if (rv != SECSuccess) {
|
|
errExit("error enabling RollBack detection ");
|
|
}
|
|
if (disableLocking) {
|
|
rv = SSL_OptionSet(model_sock, SSL_NO_LOCKS, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
errExit("error disabling SSL socket locking ");
|
|
}
|
|
}
|
|
if (enableSessionTickets) {
|
|
rv = SSL_OptionSet(model_sock, SSL_ENABLE_SESSION_TICKETS, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
errExit("error enabling Session Ticket extension ");
|
|
}
|
|
}
|
|
|
|
if (enableCompression) {
|
|
rv = SSL_OptionSet(model_sock, SSL_ENABLE_DEFLATE, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
errExit("error enabling compression ");
|
|
}
|
|
}
|
|
|
|
if (virtServerNameIndex > 1) {
|
|
rv = SSL_SNISocketConfigHook(model_sock, mySSLSNISocketConfig,
|
|
(void *)&virtServerNameArray);
|
|
if (rv != SECSuccess) {
|
|
errExit("error enabling SNI extension ");
|
|
}
|
|
}
|
|
|
|
if (configureDHE > -1) {
|
|
rv = SSL_OptionSet(model_sock, SSL_ENABLE_SERVER_DHE, (configureDHE > 0));
|
|
if (rv != SECSuccess) {
|
|
errExit("error configuring server side DHE support");
|
|
}
|
|
rv = SSL_OptionSet(model_sock, SSL_REQUIRE_DH_NAMED_GROUPS, (configureDHE > 1));
|
|
if (rv != SECSuccess) {
|
|
errExit("error configuring server side FFDHE support");
|
|
}
|
|
PORT_Assert(configureDHE <= 2);
|
|
}
|
|
|
|
if (configureReuseECDHE > -1) {
|
|
rv = SSL_OptionSet(model_sock, SSL_REUSE_SERVER_ECDHE_KEY, (configureReuseECDHE > 0));
|
|
if (rv != SECSuccess) {
|
|
errExit("error configuring server side reuse of ECDHE key");
|
|
}
|
|
}
|
|
|
|
if (configureWeakDHE > -1) {
|
|
rv = SSL_EnableWeakDHEPrimeGroup(model_sock, (configureWeakDHE > 0));
|
|
if (rv != SECSuccess) {
|
|
errExit("error configuring weak DHE prime group");
|
|
}
|
|
}
|
|
|
|
if (enableExtendedMasterSecret) {
|
|
rv = SSL_OptionSet(model_sock, SSL_ENABLE_EXTENDED_MASTER_SECRET, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
errExit("error enabling extended master secret ");
|
|
}
|
|
}
|
|
|
|
/* This uses the legacy certificate API. See mySSLSNISocketConfig() for the
|
|
* new, prefered API. */
|
|
for (i = 0; i < certNicknameIndex; i++) {
|
|
if (cert[i] != NULL) {
|
|
const SSLExtraServerCertData ocspData = {
|
|
ssl_auth_null, NULL, certStatus[i], NULL
|
|
};
|
|
|
|
secStatus = SSL_ConfigServerCert(model_sock, cert[i],
|
|
privKey[i], &ocspData,
|
|
sizeof(ocspData));
|
|
if (secStatus != SECSuccess)
|
|
errExit("SSL_ConfigServerCert");
|
|
}
|
|
}
|
|
|
|
if (bigBuf.data) { /* doing FDX */
|
|
rv = SSL_OptionSet(model_sock, SSL_ENABLE_FDX, 1);
|
|
if (rv < 0) {
|
|
errExit("SSL_OptionSet SSL_ENABLE_FDX");
|
|
}
|
|
}
|
|
|
|
if (NoReuse) {
|
|
rv = SSL_OptionSet(model_sock, SSL_NO_CACHE, 1);
|
|
if (rv < 0) {
|
|
errExit("SSL_OptionSet SSL_NO_CACHE");
|
|
}
|
|
}
|
|
|
|
if (zeroRTT) {
|
|
if (enabledVersions.max < SSL_LIBRARY_VERSION_TLS_1_3) {
|
|
errExit("You tried enabling 0RTT without enabling TLS 1.3!");
|
|
}
|
|
rv = SSL_SetupAntiReplay(10 * PR_USEC_PER_SEC, 7, 14);
|
|
if (rv != SECSuccess) {
|
|
errExit("error configuring anti-replay ");
|
|
}
|
|
rv = SSL_OptionSet(model_sock, SSL_ENABLE_0RTT_DATA, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
errExit("error enabling 0RTT ");
|
|
}
|
|
}
|
|
|
|
if (enableALPN) {
|
|
PRUint8 alpnVal[] = { 0x08,
|
|
0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31 };
|
|
rv = SSL_OptionSet(model_sock, SSL_ENABLE_ALPN, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
errExit("error enabling ALPN");
|
|
}
|
|
|
|
rv = SSL_SetNextProtoNego(model_sock, alpnVal, sizeof(alpnVal));
|
|
if (rv != SECSuccess) {
|
|
errExit("error enabling ALPN");
|
|
}
|
|
}
|
|
|
|
if (enabledGroups) {
|
|
rv = SSL_NamedGroupConfig(model_sock, enabledGroups, enabledGroupsCount);
|
|
if (rv < 0) {
|
|
errExit("SSL_NamedGroupConfig failed");
|
|
}
|
|
}
|
|
|
|
/* This cipher is not on by default. The Acceptance test
|
|
* would like it to be. Turn this cipher on.
|
|
*/
|
|
|
|
secStatus = SSL_CipherPrefSetDefault(TLS_RSA_WITH_NULL_MD5, PR_TRUE);
|
|
if (secStatus != SECSuccess) {
|
|
errExit("SSL_CipherPrefSetDefault:TLS_RSA_WITH_NULL_MD5");
|
|
}
|
|
|
|
if (expectedHostNameVal) {
|
|
SSL_HandshakeCallback(model_sock, handshakeCallback,
|
|
(void *)expectedHostNameVal);
|
|
}
|
|
|
|
if (requestCert) {
|
|
SSL_AuthCertificateHook(model_sock, mySSLAuthCertificate,
|
|
(void *)CERT_GetDefaultCertDB());
|
|
if (requestCert <= 2) {
|
|
rv = SSL_OptionSet(model_sock, SSL_REQUEST_CERTIFICATE, 1);
|
|
if (rv < 0) {
|
|
errExit("first SSL_OptionSet SSL_REQUEST_CERTIFICATE");
|
|
}
|
|
rv = SSL_OptionSet(model_sock, SSL_REQUIRE_CERTIFICATE,
|
|
(requestCert == 2));
|
|
if (rv < 0) {
|
|
errExit("first SSL_OptionSet SSL_REQUIRE_CERTIFICATE");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MakeCertOK)
|
|
SSL_BadCertHook(model_sock, myBadCertHandler, NULL);
|
|
|
|
/* end of ssl configuration. */
|
|
|
|
/* Now, do the accepting, here in the main thread. */
|
|
rv = do_accepts(listen_sock, model_sock, requestCert);
|
|
|
|
terminateWorkerThreads();
|
|
|
|
if (useModelSocket && model_sock) {
|
|
if (model_sock) {
|
|
PR_Close(model_sock);
|
|
}
|
|
}
|
|
}
|
|
|
|
SECStatus
|
|
readBigFile(const char *fileName)
|
|
{
|
|
PRFileInfo info;
|
|
PRStatus status;
|
|
SECStatus rv = SECFailure;
|
|
int count;
|
|
int hdrLen;
|
|
PRFileDesc *local_file_fd = NULL;
|
|
|
|
status = PR_GetFileInfo(fileName, &info);
|
|
|
|
if (status == PR_SUCCESS &&
|
|
info.type == PR_FILE_FILE &&
|
|
info.size > 0 &&
|
|
NULL != (local_file_fd = PR_Open(fileName, PR_RDONLY, 0))) {
|
|
|
|
hdrLen = PORT_Strlen(outHeader);
|
|
bigBuf.len = hdrLen + info.size;
|
|
bigBuf.data = PORT_Malloc(bigBuf.len + 4095);
|
|
if (!bigBuf.data) {
|
|
errWarn("PORT_Malloc");
|
|
goto done;
|
|
}
|
|
|
|
PORT_Memcpy(bigBuf.data, outHeader, hdrLen);
|
|
|
|
count = PR_Read(local_file_fd, bigBuf.data + hdrLen, info.size);
|
|
if (count != info.size) {
|
|
errWarn("PR_Read local file");
|
|
goto done;
|
|
}
|
|
rv = SECSuccess;
|
|
done:
|
|
if (local_file_fd) {
|
|
PR_Close(local_file_fd);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
int numChildren;
|
|
PRProcess *child[MAX_PROCS];
|
|
|
|
PRProcess *
|
|
haveAChild(int argc, char **argv, PRProcessAttr *attr)
|
|
{
|
|
PRProcess *newProcess;
|
|
|
|
newProcess = PR_CreateProcess(argv[0], argv, NULL, attr);
|
|
if (!newProcess) {
|
|
errWarn("Can't create new process.");
|
|
} else {
|
|
child[numChildren++] = newProcess;
|
|
}
|
|
return newProcess;
|
|
}
|
|
|
|
void
|
|
beAGoodParent(int argc, char **argv, int maxProcs, PRFileDesc *listen_sock)
|
|
{
|
|
PRProcess *newProcess;
|
|
PRProcessAttr *attr;
|
|
int i;
|
|
PRInt32 exitCode;
|
|
PRStatus rv;
|
|
|
|
rv = PR_SetFDInheritable(listen_sock, PR_TRUE);
|
|
if (rv != PR_SUCCESS)
|
|
errExit("PR_SetFDInheritable");
|
|
|
|
attr = PR_NewProcessAttr();
|
|
if (!attr)
|
|
errExit("PR_NewProcessAttr");
|
|
|
|
rv = PR_ProcessAttrSetInheritableFD(attr, listen_sock, inheritableSockName);
|
|
if (rv != PR_SUCCESS)
|
|
errExit("PR_ProcessAttrSetInheritableFD");
|
|
|
|
for (i = 0; i < maxProcs; ++i) {
|
|
newProcess = haveAChild(argc, argv, attr);
|
|
if (!newProcess)
|
|
break;
|
|
}
|
|
|
|
rv = PR_SetFDInheritable(listen_sock, PR_FALSE);
|
|
if (rv != PR_SUCCESS)
|
|
errExit("PR_SetFDInheritable");
|
|
|
|
while (numChildren > 0) {
|
|
newProcess = child[numChildren - 1];
|
|
PR_WaitProcess(newProcess, &exitCode);
|
|
fprintf(stderr, "Child %d exited with exit code %x\n",
|
|
numChildren, exitCode);
|
|
numChildren--;
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
#define HEXCHAR_TO_INT(c, i) \
|
|
if (((c) >= '0') && ((c) <= '9')) { \
|
|
i = (c) - '0'; \
|
|
} else if (((c) >= 'a') && ((c) <= 'f')) { \
|
|
i = (c) - 'a' + 10; \
|
|
} else if (((c) >= 'A') && ((c) <= 'F')) { \
|
|
i = (c) - 'A' + 10; \
|
|
} else if ((c) == '\0') { \
|
|
fprintf(stderr, "Invalid length of cipher string (-c :WXYZ).\n"); \
|
|
exit(9); \
|
|
} else { \
|
|
fprintf(stderr, "Non-hex char in cipher string (-c :WXYZ).\n"); \
|
|
exit(9); \
|
|
}
|
|
|
|
SECStatus
|
|
enableOCSPStapling(const char *mode)
|
|
{
|
|
if (!strcmp(mode, "good")) {
|
|
ocspStaplingMode = osm_good;
|
|
return SECSuccess;
|
|
}
|
|
if (!strcmp(mode, "unknown")) {
|
|
ocspStaplingMode = osm_unknown;
|
|
return SECSuccess;
|
|
}
|
|
if (!strcmp(mode, "revoked")) {
|
|
ocspStaplingMode = osm_revoked;
|
|
return SECSuccess;
|
|
}
|
|
if (!strcmp(mode, "badsig")) {
|
|
ocspStaplingMode = osm_badsig;
|
|
return SECSuccess;
|
|
}
|
|
if (!strcmp(mode, "corrupted")) {
|
|
ocspStaplingMode = osm_corrupted;
|
|
return SECSuccess;
|
|
}
|
|
if (!strcmp(mode, "failure")) {
|
|
ocspStaplingMode = osm_failure;
|
|
return SECSuccess;
|
|
}
|
|
if (!strcmp(mode, "random")) {
|
|
ocspStaplingMode = osm_random;
|
|
return SECSuccess;
|
|
}
|
|
if (!strcmp(mode, "ocsp")) {
|
|
ocspStaplingMode = osm_ocsp;
|
|
return SECSuccess;
|
|
}
|
|
return SECFailure;
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
char *progName = NULL;
|
|
const char *fileName = NULL;
|
|
char *cipherString = NULL;
|
|
const char *dir = ".";
|
|
char *passwd = NULL;
|
|
char *pwfile = NULL;
|
|
const char *pidFile = NULL;
|
|
char *tmp;
|
|
char *envString;
|
|
PRFileDesc *listen_sock;
|
|
CERTCertificate *cert[MAX_CERT_NICKNAME_ARRAY_INDEX] = { NULL };
|
|
SECKEYPrivateKey *privKey[MAX_CERT_NICKNAME_ARRAY_INDEX] = { NULL };
|
|
int optionsFound = 0;
|
|
int maxProcs = 1;
|
|
unsigned short port = 0;
|
|
SECStatus rv = SECSuccess;
|
|
PRStatus prStatus;
|
|
PRBool bindOnly = PR_FALSE;
|
|
PRBool useLocalThreads = PR_FALSE;
|
|
PLOptState *optstate;
|
|
PLOptStatus status;
|
|
PRThread *loggerThread = NULL;
|
|
PRBool debugCache = PR_FALSE; /* bug 90518 */
|
|
char emptyString[] = { "" };
|
|
char *certPrefix = emptyString;
|
|
SSL3Statistics *ssl3stats;
|
|
PRUint32 i;
|
|
secuPWData pwdata = { PW_NONE, 0 };
|
|
char *expectedHostNameVal = NULL;
|
|
PLArenaPool *certStatusArena = NULL;
|
|
|
|
tmp = strrchr(argv[0], '/');
|
|
tmp = tmp ? tmp + 1 : argv[0];
|
|
progName = strrchr(tmp, '\\');
|
|
progName = progName ? progName + 1 : tmp;
|
|
|
|
PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
|
|
SSL_VersionRangeGetSupported(ssl_variant_stream, &enabledVersions);
|
|
|
|
/* please keep this list of options in ASCII collating sequence.
|
|
** numbers, then capital letters, then lower case, alphabetical.
|
|
** XXX: 'B', 'E', 'q', and 'x' were used in the past but removed
|
|
** in 3.28, please leave some time before resuing those. */
|
|
optstate = PL_CreateOptState(argc, argv,
|
|
"2:A:C:DGH:I:L:M:NP:QRS:T:U:V:W:YZa:bc:d:e:f:g:hi:jk:lmn:op:rst:uvw:yz");
|
|
while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
|
|
++optionsFound;
|
|
switch (optstate->option) {
|
|
case '2':
|
|
fileName = optstate->value;
|
|
break;
|
|
|
|
case 'A':
|
|
ocspStaplingCA = PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'C':
|
|
if (optstate->value)
|
|
NumSidCacheEntries = PORT_Atoi(optstate->value);
|
|
break;
|
|
|
|
case 'D':
|
|
noDelay = PR_TRUE;
|
|
break;
|
|
case 'H':
|
|
configureDHE = (PORT_Atoi(optstate->value) != 0);
|
|
break;
|
|
|
|
case 'G':
|
|
enableExtendedMasterSecret = PR_TRUE;
|
|
break;
|
|
|
|
case 'L':
|
|
logStats = PR_TRUE;
|
|
if (optstate->value == NULL) {
|
|
logPeriod = 30;
|
|
} else {
|
|
logPeriod = PORT_Atoi(optstate->value);
|
|
if (logPeriod <= 0)
|
|
logPeriod = 30;
|
|
}
|
|
break;
|
|
|
|
case 'M':
|
|
maxProcs = PORT_Atoi(optstate->value);
|
|
if (maxProcs < 1)
|
|
maxProcs = 1;
|
|
if (maxProcs > MAX_PROCS)
|
|
maxProcs = MAX_PROCS;
|
|
break;
|
|
|
|
case 'N':
|
|
NoReuse = PR_TRUE;
|
|
break;
|
|
|
|
case 'R':
|
|
disableRollBack = PR_TRUE;
|
|
break;
|
|
|
|
case 'S':
|
|
if (certNicknameIndex >= MAX_CERT_NICKNAME_ARRAY_INDEX) {
|
|
Usage(progName);
|
|
break;
|
|
}
|
|
certNicknameArray[certNicknameIndex++] = PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'T':
|
|
if (enableOCSPStapling(optstate->value) != SECSuccess) {
|
|
fprintf(stderr, "Invalid OCSP stapling mode.\n");
|
|
fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
|
|
exit(53);
|
|
}
|
|
break;
|
|
|
|
case 'U':
|
|
configureReuseECDHE = (PORT_Atoi(optstate->value) != 0);
|
|
break;
|
|
|
|
case 'V':
|
|
if (SECU_ParseSSLVersionRangeString(optstate->value,
|
|
enabledVersions, &enabledVersions) !=
|
|
SECSuccess) {
|
|
fprintf(stderr, "Bad version specified.\n");
|
|
Usage(progName);
|
|
exit(1);
|
|
}
|
|
break;
|
|
|
|
case 'W':
|
|
configureWeakDHE = (PORT_Atoi(optstate->value) != 0);
|
|
break;
|
|
|
|
case 'Y':
|
|
PrintCipherUsage(progName);
|
|
exit(0);
|
|
break;
|
|
|
|
case 'a':
|
|
if (virtServerNameIndex >= MAX_VIRT_SERVER_NAME_ARRAY_INDEX) {
|
|
Usage(progName);
|
|
break;
|
|
}
|
|
virtServerNameArray[virtServerNameIndex++] =
|
|
PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'b':
|
|
bindOnly = PR_TRUE;
|
|
break;
|
|
|
|
case 'c':
|
|
cipherString = PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'd':
|
|
dir = optstate->value;
|
|
break;
|
|
|
|
case 'e':
|
|
if (certNicknameIndex >= MAX_CERT_NICKNAME_ARRAY_INDEX) {
|
|
Usage(progName);
|
|
break;
|
|
}
|
|
certNicknameArray[certNicknameIndex++] = PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'f':
|
|
pwdata.source = PW_FROMFILE;
|
|
pwdata.data = pwfile = PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'g':
|
|
testBulk = PR_TRUE;
|
|
testBulkTotal = PORT_Atoi(optstate->value);
|
|
break;
|
|
|
|
case 'h':
|
|
Usage(progName);
|
|
exit(0);
|
|
break;
|
|
|
|
case 'i':
|
|
pidFile = optstate->value;
|
|
break;
|
|
|
|
case 'j':
|
|
initLoggingLayer();
|
|
loggingLayer = PR_TRUE;
|
|
break;
|
|
|
|
case 'k':
|
|
expectedHostNameVal = PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'l':
|
|
useLocalThreads = PR_TRUE;
|
|
break;
|
|
|
|
case 'm':
|
|
useModelSocket = PR_TRUE;
|
|
break;
|
|
|
|
case 'n':
|
|
if (certNicknameIndex >= MAX_CERT_NICKNAME_ARRAY_INDEX) {
|
|
Usage(progName);
|
|
break;
|
|
}
|
|
certNicknameArray[certNicknameIndex++] = PORT_Strdup(optstate->value);
|
|
virtServerNameArray[0] = PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'P':
|
|
certPrefix = PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'o':
|
|
MakeCertOK = 1;
|
|
break;
|
|
|
|
case 'p':
|
|
port = PORT_Atoi(optstate->value);
|
|
break;
|
|
|
|
case 'r':
|
|
++requestCert;
|
|
break;
|
|
|
|
case 's':
|
|
disableLocking = PR_TRUE;
|
|
break;
|
|
|
|
case 't':
|
|
maxThreads = PORT_Atoi(optstate->value);
|
|
if (maxThreads > MAX_THREADS)
|
|
maxThreads = MAX_THREADS;
|
|
if (maxThreads < MIN_THREADS)
|
|
maxThreads = MIN_THREADS;
|
|
break;
|
|
|
|
case 'u':
|
|
enableSessionTickets = PR_TRUE;
|
|
break;
|
|
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
|
|
case 'w':
|
|
pwdata.source = PW_PLAINTEXT;
|
|
pwdata.data = passwd = PORT_Strdup(optstate->value);
|
|
break;
|
|
|
|
case 'y':
|
|
debugCache = PR_TRUE;
|
|
break;
|
|
|
|
case 'z':
|
|
enableCompression = PR_TRUE;
|
|
break;
|
|
|
|
case 'Z':
|
|
zeroRTT = PR_TRUE;
|
|
break;
|
|
|
|
case 'Q':
|
|
enableALPN = PR_TRUE;
|
|
break;
|
|
|
|
case 'I':
|
|
rv = parseGroupList(optstate->value, &enabledGroups, &enabledGroupsCount);
|
|
if (rv != SECSuccess) {
|
|
PL_DestroyOptState(optstate);
|
|
fprintf(stderr, "Bad group specified.\n");
|
|
fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
|
|
exit(5);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
case '?':
|
|
fprintf(stderr, "Unrecognized or bad option specified.\n");
|
|
fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
|
|
exit(4);
|
|
break;
|
|
}
|
|
}
|
|
PL_DestroyOptState(optstate);
|
|
if (status == PL_OPT_BAD) {
|
|
fprintf(stderr, "Unrecognized or bad option specified.\n");
|
|
fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
|
|
exit(5);
|
|
}
|
|
if (!optionsFound) {
|
|
Usage(progName);
|
|
exit(51);
|
|
}
|
|
switch (ocspStaplingMode) {
|
|
case osm_good:
|
|
case osm_revoked:
|
|
case osm_unknown:
|
|
case osm_random:
|
|
if (!ocspStaplingCA) {
|
|
fprintf(stderr, "Selected stapling response requires the -A parameter.\n");
|
|
fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
|
|
exit(52);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* The -b (bindOnly) option is only used by the ssl.sh test
|
|
* script on Linux to determine whether a previous selfserv
|
|
* process has fully died and freed the port. (Bug 129701)
|
|
*/
|
|
if (bindOnly) {
|
|
listen_sock = getBoundListenSocket(port);
|
|
if (!listen_sock) {
|
|
exit(1);
|
|
}
|
|
if (listen_sock) {
|
|
PR_Close(listen_sock);
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
if (certNicknameIndex == 0) {
|
|
fprintf(stderr, "Must specify at least one certificate nickname using '-n' (RSA), '-S' (DSA), or 'e' (EC).\n");
|
|
fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
|
|
exit(6);
|
|
}
|
|
|
|
if (port == 0) {
|
|
fprintf(stderr, "Required argument 'port' must be non-zero value\n");
|
|
exit(7);
|
|
}
|
|
|
|
if (NoReuse && maxProcs > 1) {
|
|
fprintf(stderr, "-M and -N options are mutually exclusive.\n");
|
|
exit(14);
|
|
}
|
|
|
|
if (pidFile) {
|
|
FILE *tmpfile = fopen(pidFile, "w+");
|
|
|
|
if (tmpfile) {
|
|
fprintf(tmpfile, "%d", getpid());
|
|
fclose(tmpfile);
|
|
}
|
|
}
|
|
|
|
/* allocate and initialize app data for bulk encryption testing */
|
|
if (testBulk) {
|
|
testBulkBuf = PORT_Malloc(testBulkSize);
|
|
if (testBulkBuf == NULL)
|
|
errExit("Out of memory: testBulkBuf");
|
|
for (i = 0; i < testBulkSize; i++)
|
|
testBulkBuf[i] = i;
|
|
}
|
|
|
|
envString = PR_GetEnvSecure(envVarName);
|
|
tmp = PR_GetEnvSecure("TMP");
|
|
if (!tmp)
|
|
tmp = PR_GetEnvSecure("TMPDIR");
|
|
if (!tmp)
|
|
tmp = PR_GetEnvSecure("TEMP");
|
|
|
|
/* Call the NSS initialization routines */
|
|
rv = NSS_Initialize(dir, certPrefix, certPrefix, SECMOD_DB, NSS_INIT_READONLY);
|
|
if (rv != SECSuccess) {
|
|
fputs("NSS_Init failed.\n", stderr);
|
|
exit(8);
|
|
}
|
|
|
|
if (envString) {
|
|
/* we're one of the children in a multi-process server. */
|
|
listen_sock = PR_GetInheritedFD(inheritableSockName);
|
|
if (!listen_sock)
|
|
errExit("PR_GetInheritedFD");
|
|
#ifndef WINNT
|
|
/* we can't do this on NT because it breaks NSPR and
|
|
PR_Accept will fail on the socket in the child process if
|
|
the socket state is change to non inheritable
|
|
It is however a security issue to leave it accessible,
|
|
but it is OK for a test server such as selfserv.
|
|
NSPR should fix it eventually . see bugzilla 101617
|
|
and 102077
|
|
*/
|
|
prStatus = PR_SetFDInheritable(listen_sock, PR_FALSE);
|
|
if (prStatus != PR_SUCCESS)
|
|
errExit("PR_SetFDInheritable");
|
|
#endif
|
|
rv = SSL_InheritMPServerSIDCache(envString);
|
|
if (rv != SECSuccess)
|
|
errExit("SSL_InheritMPServerSIDCache");
|
|
hasSidCache = PR_TRUE;
|
|
} else if (maxProcs > 1) {
|
|
/* we're going to be the parent in a multi-process server. */
|
|
listen_sock = getBoundListenSocket(port);
|
|
rv = SSL_ConfigMPServerSIDCache(NumSidCacheEntries, 0, 0, tmp);
|
|
if (rv != SECSuccess)
|
|
errExit("SSL_ConfigMPServerSIDCache");
|
|
hasSidCache = PR_TRUE;
|
|
beAGoodParent(argc, argv, maxProcs, listen_sock);
|
|
exit(99); /* should never get here */
|
|
} else {
|
|
/* we're an ordinary single process server. */
|
|
listen_sock = getBoundListenSocket(port);
|
|
prStatus = PR_SetFDInheritable(listen_sock, PR_FALSE);
|
|
if (prStatus != PR_SUCCESS)
|
|
errExit("PR_SetFDInheritable");
|
|
if (!NoReuse) {
|
|
rv = SSL_ConfigServerSessionIDCache(NumSidCacheEntries,
|
|
0, 0, tmp);
|
|
if (rv != SECSuccess)
|
|
errExit("SSL_ConfigServerSessionIDCache");
|
|
hasSidCache = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
lm = PR_NewLogModule("TestCase");
|
|
|
|
if (fileName)
|
|
readBigFile(fileName);
|
|
|
|
/* set our password function */
|
|
PK11_SetPasswordFunc(SECU_GetModulePassword);
|
|
|
|
/* all SSL3 cipher suites are enabled by default. */
|
|
if (cipherString) {
|
|
char *cstringSaved = cipherString;
|
|
int ndx;
|
|
|
|
/* disable all the ciphers, then enable the ones we want. */
|
|
disableAllSSLCiphers();
|
|
|
|
while (0 != (ndx = *cipherString++)) {
|
|
int cipher = 0;
|
|
|
|
if (ndx == ':') {
|
|
int ctmp;
|
|
|
|
HEXCHAR_TO_INT(*cipherString, ctmp)
|
|
cipher |= (ctmp << 12);
|
|
cipherString++;
|
|
HEXCHAR_TO_INT(*cipherString, ctmp)
|
|
cipher |= (ctmp << 8);
|
|
cipherString++;
|
|
HEXCHAR_TO_INT(*cipherString, ctmp)
|
|
cipher |= (ctmp << 4);
|
|
cipherString++;
|
|
HEXCHAR_TO_INT(*cipherString, ctmp)
|
|
cipher |= ctmp;
|
|
cipherString++;
|
|
} else {
|
|
if (!isalpha(ndx)) {
|
|
fprintf(stderr,
|
|
"Non-alphabetic char in cipher string (-c arg).\n");
|
|
exit(9);
|
|
}
|
|
ndx = tolower(ndx) - 'a';
|
|
if (ndx < PR_ARRAY_SIZE(ssl3CipherSuites)) {
|
|
cipher = ssl3CipherSuites[ndx];
|
|
}
|
|
}
|
|
if (cipher > 0) {
|
|
SECStatus status;
|
|
status = SSL_CipherPrefSetDefault(cipher, SSL_ALLOWED);
|
|
if (status != SECSuccess)
|
|
SECU_PrintError(progName, "SSL_CipherPrefSet()");
|
|
} else {
|
|
fprintf(stderr,
|
|
"Invalid cipher specification (-c arg).\n");
|
|
exit(9);
|
|
}
|
|
}
|
|
PORT_Free(cstringSaved);
|
|
}
|
|
|
|
certStatusArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
|
|
if (!certStatusArena)
|
|
errExit("cannot allocate certStatusArena");
|
|
|
|
for (i = 0; i < certNicknameIndex; i++) {
|
|
cert[i] = PK11_FindCertFromNickname(certNicknameArray[i], &pwdata);
|
|
if (cert[i] == NULL) {
|
|
fprintf(stderr, "selfserv: Can't find certificate %s\n", certNicknameArray[i]);
|
|
exit(10);
|
|
}
|
|
privKey[i] = PK11_FindKeyByAnyCert(cert[i], &pwdata);
|
|
if (privKey[i] == NULL) {
|
|
fprintf(stderr, "selfserv: Can't find Private Key for cert %s\n",
|
|
certNicknameArray[i]);
|
|
exit(11);
|
|
}
|
|
if (privKey[i]->keyType != ecKey)
|
|
setupCertStatus(certStatusArena, ocspStaplingMode, cert[i], i, &pwdata);
|
|
}
|
|
|
|
if (configureWeakDHE > 0) {
|
|
fprintf(stderr, "selfserv: Creating dynamic weak DH parameters\n");
|
|
rv = SSL_EnableWeakDHEPrimeGroup(NULL, PR_TRUE);
|
|
if (rv != SECSuccess) {
|
|
goto cleanup;
|
|
}
|
|
fprintf(stderr, "selfserv: Done creating dynamic weak DH parameters\n");
|
|
}
|
|
|
|
/* allocate the array of thread slots, and launch the worker threads. */
|
|
rv = launch_threads(&jobLoop, 0, 0, requestCert, useLocalThreads);
|
|
|
|
if (rv == SECSuccess && logStats) {
|
|
loggerThread = PR_CreateThread(PR_SYSTEM_THREAD,
|
|
logger, NULL, PR_PRIORITY_NORMAL,
|
|
useLocalThreads ? PR_LOCAL_THREAD
|
|
: PR_GLOBAL_THREAD,
|
|
PR_JOINABLE_THREAD, 0);
|
|
if (loggerThread == NULL) {
|
|
fprintf(stderr, "selfserv: Failed to launch logger thread!\n");
|
|
rv = SECFailure;
|
|
}
|
|
}
|
|
|
|
if (rv == SECSuccess) {
|
|
server_main(listen_sock, requestCert, privKey, cert,
|
|
expectedHostNameVal);
|
|
}
|
|
|
|
VLOG(("selfserv: server_thread: exiting"));
|
|
|
|
cleanup:
|
|
printSSLStatistics();
|
|
ssl3stats = SSL_GetStatistics();
|
|
if (ssl3stats->hch_sid_ticket_parse_failures != 0) {
|
|
fprintf(stderr, "selfserv: Experienced ticket parse failure(s)\n");
|
|
exit(1);
|
|
}
|
|
if (failedToNegotiateName) {
|
|
fprintf(stderr, "selfserv: Failed properly negotiate server name\n");
|
|
exit(1);
|
|
}
|
|
|
|
{
|
|
int i;
|
|
for (i = 0; i < certNicknameIndex; i++) {
|
|
if (cert[i]) {
|
|
CERT_DestroyCertificate(cert[i]);
|
|
}
|
|
if (privKey[i]) {
|
|
SECKEY_DestroyPrivateKey(privKey[i]);
|
|
}
|
|
PORT_Free(certNicknameArray[i]);
|
|
}
|
|
for (i = 0; virtServerNameArray[i]; i++) {
|
|
PORT_Free(virtServerNameArray[i]);
|
|
}
|
|
}
|
|
|
|
if (debugCache) {
|
|
nss_DumpCertificateCacheInfo();
|
|
}
|
|
if (expectedHostNameVal) {
|
|
PORT_Free(expectedHostNameVal);
|
|
}
|
|
if (passwd) {
|
|
PORT_Free(passwd);
|
|
}
|
|
if (pwfile) {
|
|
PORT_Free(pwfile);
|
|
}
|
|
if (certPrefix && certPrefix != emptyString) {
|
|
PORT_Free(certPrefix);
|
|
}
|
|
|
|
if (hasSidCache) {
|
|
SSL_ShutdownServerSessionIDCache();
|
|
}
|
|
if (certStatusArena) {
|
|
PORT_FreeArena(certStatusArena, PR_FALSE);
|
|
}
|
|
if (enabledGroups) {
|
|
PORT_Free(enabledGroups);
|
|
}
|
|
if (NSS_Shutdown() != SECSuccess) {
|
|
SECU_PrintError(progName, "NSS_Shutdown");
|
|
if (loggerThread) {
|
|
PR_JoinThread(loggerThread);
|
|
}
|
|
PR_Cleanup();
|
|
exit(1);
|
|
}
|
|
PR_Cleanup();
|
|
printf("selfserv: normal termination\n");
|
|
return 0;
|
|
}
|