mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 08:35:26 +00:00
d0d221e8d4
bug 1013088, bug 996237, bug 970539, bug 1016567, bug 485732, bug 334013, bug 959864, bug 1016836, bug 1016811, bug 1018536, bug 996250, bug 1009227, bug 963150, bug 1007126, bug 1021102.
1395 lines
37 KiB
C
1395 lines
37 KiB
C
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#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_UNJOINABLE_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)
|
|
{
|
|
VLOG(("httpserv: server_thead: waiting on stopping"));
|
|
PZ_Lock(qLock);
|
|
PZ_NotifyAllCondVar(jobQNotEmptyCv);
|
|
while (threadCount > 0) {
|
|
PZ_WaitCondVar(threadCountChangeCv, PR_INTERVAL_NO_TIMEOUT);
|
|
}
|
|
/* The worker threads empty the jobQ before they terminate. */
|
|
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 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 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;
|
|
|
|
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) {
|
|
NSSBase64_DecodeBuffer(NULL, &postData, getData, strlen(getData));
|
|
}
|
|
}
|
|
if (postData.len) {
|
|
request = CERT_DecodeOCSPRequest(&postData);
|
|
}
|
|
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() + 60*60*24 * PR_USEC_PER_SEC; /*tomorrow*/
|
|
revoDate = PR_Now() - 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);
|
|
}
|
|
}
|
|
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 = getenv("TMP");
|
|
if (!tmp)
|
|
tmp = getenv("TMPDIR");
|
|
if (!tmp)
|
|
tmp = getenv("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);
|
|
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;
|
|
}
|
|
|