summaryrefslogtreecommitdiffstats
path: root/security/nss/cmd/httpserv/httpserv.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/cmd/httpserv/httpserv.c')
-rw-r--r--security/nss/cmd/httpserv/httpserv.c1447
1 files changed, 1447 insertions, 0 deletions
diff --git a/security/nss/cmd/httpserv/httpserv.c b/security/nss/cmd/httpserv/httpserv.c
new file mode 100644
index 0000000000..58014ac505
--- /dev/null
+++ b/security/nss/cmd/httpserv/httpserv.c
@@ -0,0 +1,1447 @@
+/* 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/. */
+
+#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 "nss.h"
+#include "nssb64.h"
+#include "sechash.h"
+#include "cert.h"
+#include "certdb.h"
+#include "ocsp.h"
+#include "ocspti.h"
+#include "ocspi.h"
+
+#ifndef PORT_Strstr
+#define PORT_Strstr strstr
+#endif
+
+#ifndef PORT_Malloc
+#define PORT_Malloc PR_Malloc
+#endif
+
+static int handle_connection(PRFileDesc *, PRFileDesc *, int);
+
+/* data and structures for shutdown */
+static int stopping;
+
+static PRBool noDelay;
+static int verbose;
+
+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
+Usage(const char *progName)
+{
+ fprintf(stderr,
+
+ "Usage: %s -p port [-Dbv]\n"
+ " [-t threads] [-i pid_file]\n"
+ " [-A nickname -C crl-filename]... [-O method]\n"
+ " [-d dbdir] [-f password_file] [-w password] [-P dbprefix]\n"
+ "-D means disable Nagle delays in TCP\n"
+ "-b means try binding to the port and exit\n"
+ "-v means verbose output\n"
+ "-t threads -- specify the number of threads to use for connections.\n"
+ "-i pid_file file to write the process id of httpserv\n"
+ "Parameters -A, -C and -O are used to provide an OCSP server at /ocsp?\n"
+ "-A a nickname of a CA certificate\n"
+ "-C a CRL filename corresponding to the preceding CA nickname\n"
+ "-O allowed HTTP methods for OCSP requests: get, post, all, random, get-unknown\n"
+ " random means: randomly fail if request method is GET, POST always works\n"
+ " get-unknown means: status unknown for GET, correct status for POST\n"
+ "Multiple pairs of parameters -A and -C are allowed.\n"
+ "If status for a cert from an unknown CA is requested, the cert from the\n"
+ "first -A parameter will be used to sign the unknown status response.\n"
+ "NSS database parameters are used only if OCSP parameters are used.\n",
+ progName);
+}
+
+static const char *
+errWarn(char *funcString)
+{
+ PRErrorCode perr = PR_GetError();
+ const char *errString = SECU_Strerror(perr);
+
+ fprintf(stderr, "httpserv: %s returned error %d:\n%s\n",
+ funcString, perr, errString);
+ return errString;
+}
+
+static void
+errExit(char *funcString)
+{
+ errWarn(funcString);
+ exit(3);
+}
+
+#define MAX_VIRT_SERVER_NAME_ARRAY_INDEX 10
+
+/**************************************************************************
+** 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("httpserv: 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(("httpserv: 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);
+}
+
+/**************************************************************************
+** End thread management routines.
+**************************************************************************/
+
+PRBool NoReuse = PR_FALSE;
+PRBool disableLocking = PR_FALSE;
+static secuPWData pwdata = { PW_NONE, 0 };
+
+struct caRevoInfoStr {
+ PRCList link;
+ char *nickname;
+ char *crlFilename;
+ CERTCertificate *cert;
+ CERTOCSPCertID *id;
+ CERTSignedCrl *crl;
+};
+typedef struct caRevoInfoStr caRevoInfo;
+/* Created during app init. No locks necessary,
+ * because later on, only read access will occur. */
+static caRevoInfo *caRevoInfos = NULL;
+
+static enum {
+ ocspGetOnly,
+ ocspPostOnly,
+ ocspGetAndPost,
+ ocspRandomGetFailure,
+ ocspGetUnknown
+} ocspMethodsAllowed = ocspGetAndPost;
+
+static const char stopCmd[] = { "GET /stop " };
+static const char getCmd[] = { "GET " };
+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 outOcspHeader[] = {
+ "HTTP/1.0 200 OK\r\n"
+ "Server: Generic OCSP Server\r\n"
+ "Content-type: application/ocsp-response\r\n"
+ "\r\n"
+};
+static const char outBadRequestHeader[] = {
+ "HTTP/1.0 400 Bad Request\r\n"
+ "Server: Generic OCSP Server\r\n"
+ "\r\n"
+};
+
+void
+stop_server()
+{
+ stopping = 1;
+ PR_Interrupt(acceptorThread);
+ PZ_TraceFlush();
+}
+
+/* Will only work if the original input to url encoding was
+ * a base64 encoded buffer. Will only decode the sequences used
+ * for encoding the special base64 characters, and fail if any
+ * other encoded chars are found.
+ * Will return SECSuccess if input could be processed.
+ * Coversion is done in place.
+ */
+static SECStatus
+urldecode_base64chars_inplace(char *buf)
+{
+ char *walk;
+ size_t remaining_bytes;
+
+ if (!buf || !*buf)
+ return SECFailure;
+
+ walk = buf;
+ remaining_bytes = strlen(buf) + 1; /* include terminator */
+
+ while (*walk) {
+ if (*walk == '%') {
+ if (!PL_strncasecmp(walk, "%2B", 3)) {
+ *walk = '+';
+ } else if (!PL_strncasecmp(walk, "%2F", 3)) {
+ *walk = '/';
+ } else if (!PL_strncasecmp(walk, "%3D", 3)) {
+ *walk = '=';
+ } else {
+ return SECFailure;
+ }
+ remaining_bytes -= 3;
+ ++walk;
+ memmove(walk, walk + 2, remaining_bytes);
+ } else {
+ ++walk;
+ --remaining_bytes;
+ }
+ }
+ return SECSuccess;
+}
+
+int
+handle_connection(
+ PRFileDesc *tcp_sock,
+ PRFileDesc *model_sock,
+ int requestCert)
+{
+ PRFileDesc *ssl_sock = NULL;
+ PRFileDesc *local_file_fd = NULL;
+ 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];
+ char fileName[513];
+ char *getData = NULL; /* inplace conversion */
+ SECItem postData;
+ PRBool isOcspRequest = PR_FALSE;
+ PRBool isPost = PR_FALSE;
+
+ postData.data = NULL;
+ postData.len = 0;
+
+ pBuf = buf;
+ bufRem = sizeof buf;
+
+ VLOG(("httpserv: handle_connection: starting"));
+ opt.option = PR_SockOpt_Nonblocking;
+ opt.value.non_blocking = PR_FALSE;
+ PR_SetSocketOption(tcp_sock, &opt);
+
+ VLOG(("httpserv: handle_connection: starting\n"));
+ ssl_sock = tcp_sock;
+
+ 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) {
+ const char *post;
+ const char *foundStr = NULL;
+ const char *tmp = NULL;
+
+ 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;
+ }
+
+ 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;
+
+ postData.data = (void *)(buf + reqLen);
+
+ tmp = "content-length: ";
+ foundStr = PL_strcasestr(buf, tmp);
+ if (foundStr) {
+ int expectedPostLen;
+ int havePostLen;
+
+ expectedPostLen = atoi(foundStr + strlen(tmp));
+ havePostLen = bufDat - reqLen;
+ if (havePostLen >= expectedPostLen) {
+ postData.len = expectedPostLen;
+ break;
+ }
+ } else {
+ /* use legacy hack */
+ /* It's a post, so look for the next and final CR/LF. */
+ 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) {
+ PRBool isGetOrPost = PR_FALSE;
+ unsigned skipChars = 0;
+ isPost = PR_FALSE;
+
+ if (!strncmp(buf, getCmd, sizeof getCmd - 1)) {
+ isGetOrPost = PR_TRUE;
+ skipChars = 4;
+ } else if (!strncmp(buf, "POST ", 5)) {
+ isGetOrPost = PR_TRUE;
+ isPost = PR_TRUE;
+ skipChars = 5;
+ }
+
+ if (isGetOrPost) {
+ char *fnBegin = buf;
+ char *fnEnd;
+ char *fnstart = NULL;
+ PRFileInfo info;
+
+ fnBegin += skipChars;
+
+ fnEnd = strpbrk(fnBegin, " \r\n");
+ if (fnEnd) {
+ int fnLen = fnEnd - fnBegin;
+ if (fnLen < sizeof fileName) {
+ strncpy(fileName, fnBegin, fnLen);
+ fileName[fnLen] = 0; /* null terminate */
+ fnstart = fileName;
+ /* strip initial / because our root is the current directory*/
+ while (*fnstart && *fnstart == '/')
+ ++fnstart;
+ }
+ }
+ if (fnstart) {
+ if (!strncmp(fnstart, "ocsp", 4)) {
+ if (isPost) {
+ if (postData.data) {
+ isOcspRequest = PR_TRUE;
+ }
+ } else {
+ if (!strncmp(fnstart, "ocsp/", 5)) {
+ isOcspRequest = PR_TRUE;
+ getData = fnstart + 5;
+ }
+ }
+ } else {
+ /* try to open the file named.
+ * If successful, then write it to the client.
+ */
+ status = PR_GetFileInfo(fnstart, &info);
+ if (status == PR_SUCCESS &&
+ info.type == PR_FILE_FILE &&
+ info.size >= 0) {
+ local_file_fd = PR_Open(fnstart, PR_RDONLY, 0);
+ }
+ }
+ }
+ }
+ }
+
+ numIOVs = 0;
+
+ iovs[numIOVs].iov_base = (char *)outHeader;
+ iovs[numIOVs].iov_len = (sizeof(outHeader)) - 1;
+ numIOVs++;
+
+ if (isOcspRequest && caRevoInfos) {
+ CERTOCSPRequest *request = NULL;
+ PRBool failThisRequest = PR_FALSE;
+ PLArenaPool *arena = NULL;
+
+ if (ocspMethodsAllowed == ocspGetOnly && postData.len) {
+ failThisRequest = PR_TRUE;
+ } else if (ocspMethodsAllowed == ocspPostOnly && getData) {
+ failThisRequest = PR_TRUE;
+ } else if (ocspMethodsAllowed == ocspRandomGetFailure && getData) {
+ if (!(rand() % 2)) {
+ failThisRequest = PR_TRUE;
+ }
+ }
+
+ if (failThisRequest) {
+ PR_Write(ssl_sock, outBadRequestHeader, strlen(outBadRequestHeader));
+ break;
+ }
+ /* get is base64, post is binary.
+ * If we have base64, convert into the (empty) postData array.
+ */
+ if (getData) {
+ if (urldecode_base64chars_inplace(getData) == SECSuccess) {
+ /* The code below can handle a NULL arena */
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ NSSBase64_DecodeBuffer(arena, &postData, getData, strlen(getData));
+ }
+ }
+ if (postData.len) {
+ request = CERT_DecodeOCSPRequest(&postData);
+ }
+ if (arena) {
+ PORT_FreeArena(arena, PR_FALSE);
+ arena = NULL;
+ }
+ if (!request || !request->tbsRequest ||
+ !request->tbsRequest->requestList ||
+ !request->tbsRequest->requestList[0]) {
+ snprintf(msgBuf, sizeof(msgBuf), "Cannot decode OCSP request.\r\n");
+
+ iovs[numIOVs].iov_base = msgBuf;
+ iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
+ numIOVs++;
+ } else {
+ /* TODO: support more than one request entry */
+ CERTOCSPCertID *reqid = request->tbsRequest->requestList[0]->reqCert;
+ const caRevoInfo *revoInfo = NULL;
+ PRBool unknown = PR_FALSE;
+ PRBool revoked = PR_FALSE;
+ PRTime nextUpdate = 0;
+ PRTime revoDate = 0;
+ PRCList *caRevoIter;
+
+ caRevoIter = &caRevoInfos->link;
+ do {
+ CERTOCSPCertID *caid;
+
+ revoInfo = (caRevoInfo *)caRevoIter;
+ caid = revoInfo->id;
+
+ if (SECOID_CompareAlgorithmID(&reqid->hashAlgorithm,
+ &caid->hashAlgorithm) == SECEqual &&
+ SECITEM_CompareItem(&reqid->issuerNameHash,
+ &caid->issuerNameHash) == SECEqual &&
+ SECITEM_CompareItem(&reqid->issuerKeyHash,
+ &caid->issuerKeyHash) == SECEqual) {
+ break;
+ }
+ revoInfo = NULL;
+ caRevoIter = PR_NEXT_LINK(caRevoIter);
+ } while (caRevoIter != &caRevoInfos->link);
+
+ if (!revoInfo) {
+ unknown = PR_TRUE;
+ revoInfo = caRevoInfos;
+ } else {
+ CERTCrl *crl = &revoInfo->crl->crl;
+ CERTCrlEntry *entry = NULL;
+ DER_DecodeTimeChoice(&nextUpdate, &crl->nextUpdate);
+ if (crl->entries) {
+ int iv = 0;
+ /* assign, not compare */
+ while ((entry = crl->entries[iv++])) {
+ if (SECITEM_CompareItem(&reqid->serialNumber,
+ &entry->serialNumber) == SECEqual) {
+ break;
+ }
+ }
+ }
+ if (entry) {
+ /* revoked status response */
+ revoked = PR_TRUE;
+ DER_DecodeTimeChoice(&revoDate, &entry->revocationDate);
+ } else {
+ /* else good status response */
+ if (!isPost && ocspMethodsAllowed == ocspGetUnknown) {
+ unknown = PR_TRUE;
+ nextUpdate = PR_Now() + (PRTime)60 * 60 * 24 * PR_USEC_PER_SEC; /*tomorrow*/
+ revoDate = PR_Now() - (PRTime)60 * 60 * 24 * PR_USEC_PER_SEC; /*yesterday*/
+ }
+ }
+ }
+
+ {
+ PRTime now = PR_Now();
+ CERTOCSPSingleResponse *sr;
+ CERTOCSPSingleResponse **singleResponses;
+ SECItem *ocspResponse;
+
+ PORT_Assert(!arena);
+ arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+
+ if (unknown) {
+ sr = CERT_CreateOCSPSingleResponseUnknown(arena, reqid, now,
+ &nextUpdate);
+ } else if (revoked) {
+ sr = CERT_CreateOCSPSingleResponseRevoked(arena, reqid, now,
+ &nextUpdate, revoDate, NULL);
+ } else {
+ sr = CERT_CreateOCSPSingleResponseGood(arena, reqid, now,
+ &nextUpdate);
+ }
+
+ /* meaning of value 2: one entry + one end marker */
+ singleResponses = PORT_ArenaNewArray(arena, CERTOCSPSingleResponse *, 2);
+ singleResponses[0] = sr;
+ singleResponses[1] = NULL;
+ ocspResponse = CERT_CreateEncodedOCSPSuccessResponse(arena,
+ revoInfo->cert, ocspResponderID_byName, now,
+ singleResponses, &pwdata);
+
+ if (!ocspResponse) {
+ snprintf(msgBuf, sizeof(msgBuf), "Failed to encode response\r\n");
+ iovs[numIOVs].iov_base = msgBuf;
+ iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
+ numIOVs++;
+ } else {
+ PR_Write(ssl_sock, outOcspHeader, strlen(outOcspHeader));
+ PR_Write(ssl_sock, ocspResponse->data, ocspResponse->len);
+ }
+ PORT_FreeArena(arena, PR_FALSE);
+ }
+ CERT_DestroyOCSPRequest(request);
+ break;
+ }
+ } else if (local_file_fd) {
+ PRInt32 bytes;
+ int errLen;
+ 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,
+ "httpserv: 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++;
+ } else if (reqLen <= 0) { /* hit eof */
+ snprintf(msgBuf, sizeof(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) {
+ snprintf(msgBuf, sizeof(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++;
+ }
+
+ rv = PR_Writev(ssl_sock, iovs, numIOVs, PR_INTERVAL_NO_TIMEOUT);
+ if (rv < 0) {
+ errWarn("PR_Writev");
+ 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(("httpserv: handle_connection: exiting\n"));
+
+ /* do a nice shutdown if asked. */
+ if (!strncmp(buf, stopCmd, sizeof stopCmd - 1)) {
+ VLOG(("httpserv: handle_connection: stop command"));
+ stop_server();
+ }
+ VLOG(("httpserv: handle_connection: exiting"));
+ return SECSuccess; /* success */
+}
+
+#ifdef XP_UNIX
+
+void
+sigusr1_handler(int sig)
+{
+ VLOG(("httpserv: 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(("httpserv: 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\nhttpserv: 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(("httpserv: do_accept: Got connection\n"));
+
+ 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, "httpserv: Closing listen socket.\n");
+ VLOG(("httpserv: 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;
+}
+
+void
+server_main(
+ PRFileDesc *listen_sock,
+ int requestCert,
+ SECKEYPrivateKey **privKey,
+ CERTCertificate **cert,
+ const char *expectedHostNameVal)
+{
+ PRFileDesc *model_sock = NULL;
+
+ /* Now, do the accepting, here in the main thread. */
+ do_accepts(listen_sock, model_sock, requestCert);
+
+ terminateWorkerThreads();
+
+ if (model_sock) {
+ PR_Close(model_sock);
+ }
+}
+
+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;
+}
+
+/* slightly adjusted version of ocsp_CreateCertID (not using issuer) */
+static CERTOCSPCertID *
+ocsp_CreateSelfCAID(PLArenaPool *arena, CERTCertificate *cert, PRTime time)
+{
+ CERTOCSPCertID *certID;
+ void *mark = PORT_ArenaMark(arena);
+ SECStatus rv;
+
+ PORT_Assert(arena != NULL);
+
+ certID = PORT_ArenaZNew(arena, CERTOCSPCertID);
+ if (certID == NULL) {
+ goto loser;
+ }
+
+ rv = SECOID_SetAlgorithmID(arena, &certID->hashAlgorithm, SEC_OID_SHA1,
+ NULL);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ if (CERT_GetSubjectNameDigest(arena, cert, SEC_OID_SHA1,
+ &(certID->issuerNameHash)) == NULL) {
+ goto loser;
+ }
+ certID->issuerSHA1NameHash.data = certID->issuerNameHash.data;
+ certID->issuerSHA1NameHash.len = certID->issuerNameHash.len;
+
+ if (CERT_GetSubjectNameDigest(arena, cert, SEC_OID_MD5,
+ &(certID->issuerMD5NameHash)) == NULL) {
+ goto loser;
+ }
+
+ if (CERT_GetSubjectNameDigest(arena, cert, SEC_OID_MD2,
+ &(certID->issuerMD2NameHash)) == NULL) {
+ goto loser;
+ }
+
+ if (CERT_GetSubjectPublicKeyDigest(arena, cert, SEC_OID_SHA1,
+ &certID->issuerKeyHash) == NULL) {
+ goto loser;
+ }
+ certID->issuerSHA1KeyHash.data = certID->issuerKeyHash.data;
+ certID->issuerSHA1KeyHash.len = certID->issuerKeyHash.len;
+ /* cache the other two hash algorithms as well */
+ if (CERT_GetSubjectPublicKeyDigest(arena, cert, SEC_OID_MD5,
+ &certID->issuerMD5KeyHash) == NULL) {
+ goto loser;
+ }
+ if (CERT_GetSubjectPublicKeyDigest(arena, cert, SEC_OID_MD2,
+ &certID->issuerMD2KeyHash) == NULL) {
+ goto loser;
+ }
+
+ PORT_ArenaUnmark(arena, mark);
+ return certID;
+
+loser:
+ PORT_ArenaRelease(arena, mark);
+ return NULL;
+}
+
+/* slightly adjusted version of CERT_CreateOCSPCertID */
+CERTOCSPCertID *
+cert_CreateSelfCAID(CERTCertificate *cert, PRTime time)
+{
+ PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
+ CERTOCSPCertID *certID;
+ PORT_Assert(arena != NULL);
+ if (!arena)
+ return NULL;
+
+ certID = ocsp_CreateSelfCAID(arena, cert, time);
+ if (!certID) {
+ PORT_FreeArena(arena, PR_FALSE);
+ return NULL;
+ }
+ certID->poolp = arena;
+ return certID;
+}
+
+int
+main(int argc, char **argv)
+{
+ char *progName = NULL;
+ const char *dir = ".";
+ char *passwd = NULL;
+ char *pwfile = NULL;
+ const char *pidFile = NULL;
+ char *tmp;
+ PRFileDesc *listen_sock;
+ int optionsFound = 0;
+ unsigned short port = 0;
+ SECStatus rv;
+ PRStatus prStatus;
+ PRBool bindOnly = PR_FALSE;
+ PRBool useLocalThreads = PR_FALSE;
+ PLOptState *optstate;
+ PLOptStatus status;
+ char emptyString[] = { "" };
+ char *certPrefix = emptyString;
+ caRevoInfo *revoInfo = NULL;
+ PRCList *caRevoIter = NULL;
+ PRBool provideOcsp = PR_FALSE;
+
+ 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);
+
+ /* please keep this list of options in ASCII collating sequence.
+ ** numbers, then capital letters, then lower case, alphabetical.
+ */
+ optstate = PL_CreateOptState(argc, argv,
+ "A:C:DO:P:bd:f:hi:p:t:vw:");
+ while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
+ ++optionsFound;
+ switch (optstate->option) {
+ /* A first, must be followed by C. Any other order is an error.
+ * A creates the object. C completes and moves into list.
+ */
+ case 'A':
+ provideOcsp = PR_TRUE;
+ if (revoInfo) {
+ Usage(progName);
+ exit(0);
+ }
+ revoInfo = PORT_New(caRevoInfo);
+ revoInfo->nickname = PORT_Strdup(optstate->value);
+ break;
+ case 'C':
+ if (!revoInfo) {
+ Usage(progName);
+ exit(0);
+ }
+ revoInfo->crlFilename = PORT_Strdup(optstate->value);
+ if (!caRevoInfos) {
+ PR_INIT_CLIST(&revoInfo->link);
+ caRevoInfos = revoInfo;
+ } else {
+ PR_APPEND_LINK(&revoInfo->link, &caRevoInfos->link);
+ }
+ revoInfo = NULL;
+ break;
+
+ case 'O':
+ if (!PL_strcasecmp(optstate->value, "all")) {
+ ocspMethodsAllowed = ocspGetAndPost;
+ } else if (!PL_strcasecmp(optstate->value, "get")) {
+ ocspMethodsAllowed = ocspGetOnly;
+ } else if (!PL_strcasecmp(optstate->value, "post")) {
+ ocspMethodsAllowed = ocspPostOnly;
+ } else if (!PL_strcasecmp(optstate->value, "random")) {
+ ocspMethodsAllowed = ocspRandomGetFailure;
+ } else if (!PL_strcasecmp(optstate->value, "get-unknown")) {
+ ocspMethodsAllowed = ocspGetUnknown;
+ } else {
+ Usage(progName);
+ exit(0);
+ }
+ break;
+
+ case 'D':
+ noDelay = PR_TRUE;
+ break;
+
+ case 'P':
+ certPrefix = PORT_Strdup(optstate->value);
+ break;
+
+ case 'b':
+ bindOnly = PR_TRUE;
+ break;
+
+ case 'd':
+ dir = optstate->value;
+ break;
+
+ case 'f':
+ pwdata.source = PW_FROMFILE;
+ pwdata.data = pwfile = PORT_Strdup(optstate->value);
+ break;
+
+ case 'h':
+ Usage(progName);
+ exit(0);
+ break;
+
+ case 'i':
+ pidFile = optstate->value;
+ break;
+
+ case 'p':
+ port = PORT_Atoi(optstate->value);
+ break;
+
+ case 't':
+ maxThreads = PORT_Atoi(optstate->value);
+ if (maxThreads > MAX_THREADS)
+ maxThreads = MAX_THREADS;
+ if (maxThreads < MIN_THREADS)
+ maxThreads = MIN_THREADS;
+ break;
+
+ case 'v':
+ verbose++;
+ break;
+
+ case 'w':
+ pwdata.source = PW_PLAINTEXT;
+ pwdata.data = passwd = PORT_Strdup(optstate->value);
+ 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);
+ }
+
+ /* The -b (bindOnly) option is only used by the ssl.sh test
+ * script on Linux to determine whether a previous httpserv
+ * 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 (port == 0) {
+ fprintf(stderr, "Required argument 'port' must be non-zero value\n");
+ exit(7);
+ }
+
+ if (pidFile) {
+ FILE *tmpfile = fopen(pidFile, "w+");
+
+ if (tmpfile) {
+ fprintf(tmpfile, "%d", getpid());
+ fclose(tmpfile);
+ }
+ }
+
+ tmp = PR_GetEnvSecure("TMP");
+ if (!tmp)
+ tmp = PR_GetEnvSecure("TMPDIR");
+ if (!tmp)
+ tmp = PR_GetEnvSecure("TEMP");
+ /* 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");
+
+ lm = PR_NewLogModule("TestCase");
+
+ /* set our password function */
+ PK11_SetPasswordFunc(SECU_GetModulePassword);
+
+ if (provideOcsp) {
+ /* 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 (caRevoInfos) {
+ caRevoIter = &caRevoInfos->link;
+ do {
+ PRFileDesc *inFile;
+ SECItem crlDER;
+ crlDER.data = NULL;
+
+ revoInfo = (caRevoInfo *)caRevoIter;
+ revoInfo->cert = CERT_FindCertByNickname(
+ CERT_GetDefaultCertDB(), revoInfo->nickname);
+ if (!revoInfo->cert) {
+ fprintf(stderr, "cannot find cert with nickname %s\n",
+ revoInfo->nickname);
+ exit(1);
+ }
+ inFile = PR_Open(revoInfo->crlFilename, PR_RDONLY, 0);
+ if (inFile) {
+ rv = SECU_ReadDERFromFile(&crlDER, inFile, PR_FALSE, PR_FALSE);
+ PR_Close(inFile);
+ inFile = NULL;
+ }
+ if (rv != SECSuccess) {
+ fprintf(stderr, "unable to read crl file %s\n",
+ revoInfo->crlFilename);
+ exit(1);
+ }
+ revoInfo->crl =
+ CERT_DecodeDERCrlWithFlags(NULL, &crlDER, SEC_CRL_TYPE,
+ CRL_DECODE_DEFAULT_OPTIONS);
+ SECITEM_FreeItem(&crlDER, PR_FALSE);
+ if (!revoInfo->crl) {
+ fprintf(stderr, "unable to decode crl file %s\n",
+ revoInfo->crlFilename);
+ exit(1);
+ }
+ if (CERT_CompareName(&revoInfo->crl->crl.name,
+ &revoInfo->cert->subject) != SECEqual) {
+ fprintf(stderr, "CRL %s doesn't match cert identified by preceding nickname %s\n",
+ revoInfo->crlFilename, revoInfo->nickname);
+ exit(1);
+ }
+ revoInfo->id = cert_CreateSelfCAID(revoInfo->cert, PR_Now());
+ caRevoIter = PR_NEXT_LINK(caRevoIter);
+ } while (caRevoIter != &caRevoInfos->link);
+ }
+ }
+
+ /* allocate the array of thread slots, and launch the worker threads. */
+ rv = launch_threads(&jobLoop, 0, 0, 0, useLocalThreads);
+
+ if (rv == SECSuccess) {
+ server_main(listen_sock, 0, 0, 0,
+ 0);
+ }
+
+ VLOG(("httpserv: server_thread: exiting"));
+
+ if (provideOcsp) {
+ if (caRevoInfos) {
+ caRevoIter = &caRevoInfos->link;
+ do {
+ revoInfo = (caRevoInfo *)caRevoIter;
+ if (revoInfo->nickname)
+ PORT_Free(revoInfo->nickname);
+ if (revoInfo->crlFilename)
+ PORT_Free(revoInfo->crlFilename);
+ if (revoInfo->cert)
+ CERT_DestroyCertificate(revoInfo->cert);
+ if (revoInfo->id)
+ CERT_DestroyOCSPCertID(revoInfo->id);
+ if (revoInfo->crl)
+ SEC_DestroyCrl(revoInfo->crl);
+
+ caRevoIter = PR_NEXT_LINK(caRevoIter);
+ } while (caRevoIter != &caRevoInfos->link);
+ }
+ if (NSS_Shutdown() != SECSuccess) {
+ SECU_PrintError(progName, "NSS_Shutdown");
+ PR_Cleanup();
+ exit(1);
+ }
+ }
+ if (passwd) {
+ PORT_Free(passwd);
+ }
+ if (pwfile) {
+ PORT_Free(pwfile);
+ }
+ if (certPrefix && certPrefix != emptyString) {
+ PORT_Free(certPrefix);
+ }
+ PR_Cleanup();
+ printf("httpserv: normal termination\n");
+ return 0;
+}