diff options
Diffstat (limited to 'security/nss/cmd/httpserv/httpserv.c')
-rw-r--r-- | security/nss/cmd/httpserv/httpserv.c | 1453 |
1 files changed, 1453 insertions, 0 deletions
diff --git a/security/nss/cmd/httpserv/httpserv.c b/security/nss/cmd/httpserv/httpserv.c new file mode 100644 index 000000000..7cf28c65a --- /dev/null +++ b/security/nss/cmd/httpserv/httpserv.c @@ -0,0 +1,1453 @@ +/* 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_Sprintf +#define PORT_Sprintf sprintf +#endif + +#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; + + 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); + } + if (!request || !request->tbsRequest || + !request->tbsRequest->requestList || + !request->tbsRequest->requestList[0]) { + PORT_Sprintf(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(); + PLArenaPool *arena = NULL; + CERTOCSPSingleResponse *sr; + CERTOCSPSingleResponse **singleResponses; + SECItem *ocspResponse; + + 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) { + PORT_Sprintf(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 */ + 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++; + } + + 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; + int rv = SECFailure; + 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) { + PRCList *caRevoIter; + + caRevoIter = &caRevoInfos->link; + do { + caRevoInfo *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; +} |