| /* 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 *); |
| |
| 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]\n" |
| " [-I groups] [-J signatureschemes] [-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 tls1.3\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" |
| "-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" |
| "-J comma separated list of enabled signature schemes in preference order.\n" |
| " The following values are valid:\n" |
| " rsa_pkcs1_sha1, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512,\n" |
| " ecdsa_sha1, ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384,\n" |
| " ecdsa_secp521r1_sha512,\n" |
| " rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512,\n" |
| " rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512,\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; |
| } 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); |
| |
| typedef enum { rs_idle = 0, |
| rs_running = 1, |
| rs_zombie = 2 } runState; |
| |
| typedef struct perThreadStr { |
| PRFileDesc *a; |
| PRFileDesc *b; |
| 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); |
| |
| /* 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) |
| { |
| 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); |
| 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, |
| 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->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 failedToNegotiateName = PR_FALSE; |
| PRBool enableExtendedMasterSecret = PR_FALSE; |
| PRBool zeroRTT = PR_FALSE; |
| PRBool enableALPN = PR_FALSE; |
| SSLNamedGroup *enabledGroups = NULL; |
| unsigned int enabledGroupsCount = 0; |
| const SSLSignatureScheme *enabledSigSchemes = NULL; |
| unsigned int enabledSigSchemeCount = 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 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) |
| { |
| 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); |
| |
| 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, |
| 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 (ocspStaplingMode) { |
| 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, |
| (ocspStaplingMode == 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, |
| 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, 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) |
| { |
| 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) { |
| 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) |
| { |
| 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; |
| } |
| |
| 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, |
| 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 (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"); |
| } |
| } |
| |
| if (enabledSigSchemes) { |
| rv = SSL_SignatureSchemePrefSet(model_sock, enabledSigSchemes, enabledSigSchemeCount); |
| if (rv < 0) { |
| errExit("SSL_SignatureSchemePrefSet 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); |
| |
| 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. |
| ** 'z' was removed in 3.39. */ |
| optstate = PL_CreateOptState(argc, argv, |
| "2:A:C:DGH:I:J:L:M:NP:QRS:T:U:V:W:YZa:bc:d:e:f:g:hi:jk:lmn:op:rst:uvw:y"); |
| 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': |
| 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; |
| |
| case 'J': |
| rv = parseSigSchemeList(optstate->value, &enabledSigSchemes, &enabledSigSchemeCount); |
| if (rv != SECSuccess) { |
| PL_DestroyOptState(optstate); |
| fprintf(stderr, "Bad signature scheme 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) { |
| rv = SSL_CipherPrefSetDefault(cipher, SSL_ALLOWED); |
| if (rv != 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, 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, 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, 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); |
| } |
| |
| { |
| 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; |
| } |