Wed, 31 Dec 2014 07:16:47 +0100
Revert simplistic fix pending revisit of Mozilla integration attempt.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include <stdio.h>
6 #include <string.h>
8 #include "secutil.h"
10 #if defined(XP_UNIX)
11 #include <unistd.h>
12 #endif
14 #if defined(_WINDOWS)
15 #include <process.h> /* for getpid() */
16 #endif
18 #include <signal.h>
19 #include <stdlib.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <stdarg.h>
24 #include "nspr.h"
25 #include "prio.h"
26 #include "prerror.h"
27 #include "prnetdb.h"
28 #include "prclist.h"
29 #include "plgetopt.h"
30 #include "pk11func.h"
31 #include "nss.h"
32 #include "nssb64.h"
33 #include "sechash.h"
34 #include "cert.h"
35 #include "certdb.h"
36 #include "ocsp.h"
37 #include "ocspti.h"
38 #include "ocspi.h"
40 #ifndef PORT_Sprintf
41 #define PORT_Sprintf sprintf
42 #endif
44 #ifndef PORT_Strstr
45 #define PORT_Strstr strstr
46 #endif
48 #ifndef PORT_Malloc
49 #define PORT_Malloc PR_Malloc
50 #endif
52 static int handle_connection( PRFileDesc *, PRFileDesc *, int );
54 /* data and structures for shutdown */
55 static int stopping;
57 static PRBool noDelay;
58 static int verbose;
60 static PRThread * acceptorThread;
62 static PRLogModuleInfo *lm;
64 #define PRINTF if (verbose) printf
65 #define FPRINTF if (verbose) fprintf
66 #define FLUSH if (verbose) { fflush(stdout); fflush(stderr); }
67 #define VLOG(arg) PR_LOG(lm,PR_LOG_DEBUG,arg)
69 static void
70 Usage(const char *progName)
71 {
72 fprintf(stderr,
74 "Usage: %s -p port [-Dbv]\n"
75 " [-t threads] [-i pid_file]\n"
76 " [-A nickname -C crl-filename]... [-O method]\n"
77 " [-d dbdir] [-f password_file] [-w password] [-P dbprefix]\n"
78 "-D means disable Nagle delays in TCP\n"
79 "-b means try binding to the port and exit\n"
80 "-v means verbose output\n"
81 "-t threads -- specify the number of threads to use for connections.\n"
82 "-i pid_file file to write the process id of httpserv\n"
83 "Parameters -A, -C and -O are used to provide an OCSP server at /ocsp?\n"
84 "-A a nickname of a CA certificate\n"
85 "-C a CRL filename corresponding to the preceding CA nickname\n"
86 "-O allowed HTTP methods for OCSP requests: get, post, all, random, get-unknown\n"
87 " random means: randomly fail if request method is GET, POST always works\n"
88 " get-unknown means: status unknown for GET, correct status for POST\n"
89 "Multiple pairs of parameters -A and -C are allowed.\n"
90 "If status for a cert from an unknown CA is requested, the cert from the\n"
91 "first -A parameter will be used to sign the unknown status response.\n"
92 "NSS database parameters are used only if OCSP parameters are used.\n"
93 ,progName);
94 }
96 static const char *
97 errWarn(char * funcString)
98 {
99 PRErrorCode perr = PR_GetError();
100 const char * errString = SECU_Strerror(perr);
102 fprintf(stderr, "httpserv: %s returned error %d:\n%s\n",
103 funcString, perr, errString);
104 return errString;
105 }
107 static void
108 errExit(char * funcString)
109 {
110 errWarn(funcString);
111 exit(3);
112 }
114 #define MAX_VIRT_SERVER_NAME_ARRAY_INDEX 10
116 /**************************************************************************
117 ** Begin thread management routines and data.
118 **************************************************************************/
119 #define MIN_THREADS 3
120 #define DEFAULT_THREADS 8
121 #define MAX_THREADS 4096
122 #define MAX_PROCS 25
123 static int maxThreads = DEFAULT_THREADS;
126 typedef struct jobStr {
127 PRCList link;
128 PRFileDesc *tcp_sock;
129 PRFileDesc *model_sock;
130 int requestCert;
131 } JOB;
133 static PZLock * qLock; /* this lock protects all data immediately below */
134 static PRLock * lastLoadedCrlLock; /* this lock protects lastLoadedCrl variable */
135 static PZCondVar * jobQNotEmptyCv;
136 static PZCondVar * freeListNotEmptyCv;
137 static PZCondVar * threadCountChangeCv;
138 static int threadCount;
139 static PRCList jobQ;
140 static PRCList freeJobs;
141 static JOB *jobTable;
143 SECStatus
144 setupJobs(int maxJobs)
145 {
146 int i;
148 jobTable = (JOB *)PR_Calloc(maxJobs, sizeof(JOB));
149 if (!jobTable)
150 return SECFailure;
152 PR_INIT_CLIST(&jobQ);
153 PR_INIT_CLIST(&freeJobs);
155 for (i = 0; i < maxJobs; ++i) {
156 JOB * pJob = jobTable + i;
157 PR_APPEND_LINK(&pJob->link, &freeJobs);
158 }
159 return SECSuccess;
160 }
162 typedef int startFn(PRFileDesc *a, PRFileDesc *b, int c);
164 typedef enum { rs_idle = 0, rs_running = 1, rs_zombie = 2 } runState;
166 typedef struct perThreadStr {
167 PRFileDesc *a;
168 PRFileDesc *b;
169 int c;
170 int rv;
171 startFn * startFunc;
172 PRThread * prThread;
173 runState state;
174 } perThread;
176 static perThread *threads;
178 void
179 thread_wrapper(void * arg)
180 {
181 perThread * slot = (perThread *)arg;
183 slot->rv = (* slot->startFunc)(slot->a, slot->b, slot->c);
185 /* notify the thread exit handler. */
186 PZ_Lock(qLock);
187 slot->state = rs_zombie;
188 --threadCount;
189 PZ_NotifyAllCondVar(threadCountChangeCv);
190 PZ_Unlock(qLock);
191 }
193 int
194 jobLoop(PRFileDesc *a, PRFileDesc *b, int c)
195 {
196 PRCList * myLink = 0;
197 JOB * myJob;
199 PZ_Lock(qLock);
200 do {
201 myLink = 0;
202 while (PR_CLIST_IS_EMPTY(&jobQ) && !stopping) {
203 PZ_WaitCondVar(jobQNotEmptyCv, PR_INTERVAL_NO_TIMEOUT);
204 }
205 if (!PR_CLIST_IS_EMPTY(&jobQ)) {
206 myLink = PR_LIST_HEAD(&jobQ);
207 PR_REMOVE_AND_INIT_LINK(myLink);
208 }
209 PZ_Unlock(qLock);
210 myJob = (JOB *)myLink;
211 /* myJob will be null when stopping is true and jobQ is empty */
212 if (!myJob)
213 break;
214 handle_connection( myJob->tcp_sock, myJob->model_sock,
215 myJob->requestCert);
216 PZ_Lock(qLock);
217 PR_APPEND_LINK(myLink, &freeJobs);
218 PZ_NotifyCondVar(freeListNotEmptyCv);
219 } while (PR_TRUE);
220 return 0;
221 }
224 SECStatus
225 launch_threads(
226 startFn *startFunc,
227 PRFileDesc *a,
228 PRFileDesc *b,
229 int c,
230 PRBool local)
231 {
232 int i;
233 SECStatus rv = SECSuccess;
235 /* create the thread management serialization structs */
236 qLock = PZ_NewLock(nssILockSelfServ);
237 jobQNotEmptyCv = PZ_NewCondVar(qLock);
238 freeListNotEmptyCv = PZ_NewCondVar(qLock);
239 threadCountChangeCv = PZ_NewCondVar(qLock);
241 /* create monitor for crl reload procedure */
242 lastLoadedCrlLock = PR_NewLock();
244 /* allocate the array of thread slots */
245 threads = PR_Calloc(maxThreads, sizeof(perThread));
246 if ( NULL == threads ) {
247 fprintf(stderr, "Oh Drat! Can't allocate the perThread array\n");
248 return SECFailure;
249 }
250 /* 5 is a little extra, intended to keep the jobQ from underflowing.
251 ** That is, from going empty while not stopping and clients are still
252 ** trying to contact us.
253 */
254 rv = setupJobs(maxThreads + 5);
255 if (rv != SECSuccess)
256 return rv;
258 PZ_Lock(qLock);
259 for (i = 0; i < maxThreads; ++i) {
260 perThread * slot = threads + i;
262 slot->state = rs_running;
263 slot->a = a;
264 slot->b = b;
265 slot->c = c;
266 slot->startFunc = startFunc;
267 slot->prThread = PR_CreateThread(PR_USER_THREAD,
268 thread_wrapper, slot, PR_PRIORITY_NORMAL,
269 (PR_TRUE==local)?PR_LOCAL_THREAD:PR_GLOBAL_THREAD,
270 PR_UNJOINABLE_THREAD, 0);
271 if (slot->prThread == NULL) {
272 printf("httpserv: Failed to launch thread!\n");
273 slot->state = rs_idle;
274 rv = SECFailure;
275 break;
276 }
278 ++threadCount;
279 }
280 PZ_Unlock(qLock);
282 return rv;
283 }
285 #define DESTROY_CONDVAR(name) if (name) { \
286 PZ_DestroyCondVar(name); name = NULL; }
287 #define DESTROY_LOCK(name) if (name) { \
288 PZ_DestroyLock(name); name = NULL; }
291 void
292 terminateWorkerThreads(void)
293 {
294 VLOG(("httpserv: server_thead: waiting on stopping"));
295 PZ_Lock(qLock);
296 PZ_NotifyAllCondVar(jobQNotEmptyCv);
297 while (threadCount > 0) {
298 PZ_WaitCondVar(threadCountChangeCv, PR_INTERVAL_NO_TIMEOUT);
299 }
300 /* The worker threads empty the jobQ before they terminate. */
301 PORT_Assert(PR_CLIST_IS_EMPTY(&jobQ));
302 PZ_Unlock(qLock);
304 DESTROY_CONDVAR(jobQNotEmptyCv);
305 DESTROY_CONDVAR(freeListNotEmptyCv);
306 DESTROY_CONDVAR(threadCountChangeCv);
308 PR_DestroyLock(lastLoadedCrlLock);
309 DESTROY_LOCK(qLock);
310 PR_Free(jobTable);
311 PR_Free(threads);
312 }
314 /**************************************************************************
315 ** End thread management routines.
316 **************************************************************************/
318 PRBool NoReuse = PR_FALSE;
319 PRBool disableLocking = PR_FALSE;
320 static secuPWData pwdata = { PW_NONE, 0 };
322 struct caRevoInfoStr
323 {
324 PRCList link;
325 char *nickname;
326 char *crlFilename;
327 CERTCertificate *cert;
328 CERTOCSPCertID *id;
329 CERTSignedCrl *crl;
330 };
331 typedef struct caRevoInfoStr caRevoInfo;
332 /* Created during app init. No locks necessary,
333 * because later on, only read access will occur. */
334 static caRevoInfo *caRevoInfos = NULL;
336 static enum {
337 ocspGetOnly, ocspPostOnly, ocspGetAndPost, ocspRandomGetFailure, ocspGetUnknown
338 } ocspMethodsAllowed = ocspGetAndPost;
340 static const char stopCmd[] = { "GET /stop " };
341 static const char getCmd[] = { "GET " };
342 static const char EOFmsg[] = { "EOF\r\n\r\n\r\n" };
343 static const char outHeader[] = {
344 "HTTP/1.0 200 OK\r\n"
345 "Server: Generic Web Server\r\n"
346 "Date: Tue, 26 Aug 1997 22:10:05 GMT\r\n"
347 "Content-type: text/plain\r\n"
348 "\r\n"
349 };
350 static const char outOcspHeader[] = {
351 "HTTP/1.0 200 OK\r\n"
352 "Server: Generic OCSP Server\r\n"
353 "Content-type: application/ocsp-response\r\n"
354 "\r\n"
355 };
356 static const char outBadRequestHeader[] = {
357 "HTTP/1.0 400 Bad Request\r\n"
358 "Server: Generic OCSP Server\r\n"
359 "\r\n"
360 };
362 void stop_server()
363 {
364 stopping = 1;
365 PR_Interrupt(acceptorThread);
366 PZ_TraceFlush();
367 }
369 /* Will only work if the original input to url encoding was
370 * a base64 encoded buffer. Will only decode the sequences used
371 * for encoding the special base64 characters, and fail if any
372 * other encoded chars are found.
373 * Will return SECSuccess if input could be processed.
374 * Coversion is done in place.
375 */
376 static SECStatus
377 urldecode_base64chars_inplace(char *buf)
378 {
379 char *walk;
380 size_t remaining_bytes;
382 if (!buf || !*buf)
383 return SECFailure;
385 walk = buf;
386 remaining_bytes = strlen(buf) + 1; /* include terminator */
388 while (*walk) {
389 if (*walk == '%') {
390 if (!PL_strncasecmp(walk, "%2B", 3)) {
391 *walk = '+';
392 } else if (!PL_strncasecmp(walk, "%2F", 3)) {
393 *walk = '/';
394 } else if (!PL_strncasecmp(walk, "%3D", 3)) {
395 *walk = '=';
396 } else {
397 return SECFailure;
398 }
399 remaining_bytes -= 3;
400 ++walk;
401 memmove(walk, walk+2, remaining_bytes);
402 } else {
403 ++walk;
404 --remaining_bytes;
405 }
406 }
407 return SECSuccess;
408 }
410 int
411 handle_connection(
412 PRFileDesc *tcp_sock,
413 PRFileDesc *model_sock,
414 int requestCert
415 )
416 {
417 PRFileDesc * ssl_sock = NULL;
418 PRFileDesc * local_file_fd = NULL;
419 char * pBuf; /* unused space at end of buf */
420 const char * errString;
421 PRStatus status;
422 int bufRem; /* unused bytes at end of buf */
423 int bufDat; /* characters received in buf */
424 int newln = 0; /* # of consecutive newlns */
425 int firstTime = 1;
426 int reqLen;
427 int rv;
428 int numIOVs;
429 PRSocketOptionData opt;
430 PRIOVec iovs[16];
431 char msgBuf[160];
432 char buf[10240];
433 char fileName[513];
434 char *getData = NULL; /* inplace conversion */
435 SECItem postData;
436 PRBool isOcspRequest = PR_FALSE;
437 PRBool isPost;
439 postData.data = NULL;
440 postData.len = 0;
442 pBuf = buf;
443 bufRem = sizeof buf;
445 VLOG(("httpserv: handle_connection: starting"));
446 opt.option = PR_SockOpt_Nonblocking;
447 opt.value.non_blocking = PR_FALSE;
448 PR_SetSocketOption(tcp_sock, &opt);
450 VLOG(("httpserv: handle_connection: starting\n"));
451 ssl_sock = tcp_sock;
453 if (noDelay) {
454 opt.option = PR_SockOpt_NoDelay;
455 opt.value.no_delay = PR_TRUE;
456 status = PR_SetSocketOption(ssl_sock, &opt);
457 if (status != PR_SUCCESS) {
458 errWarn("PR_SetSocketOption(PR_SockOpt_NoDelay, PR_TRUE)");
459 if (ssl_sock) {
460 PR_Close(ssl_sock);
461 }
462 return SECFailure;
463 }
464 }
466 while (1) {
467 const char *post;
468 const char *foundStr = NULL;
469 const char *tmp = NULL;
471 newln = 0;
472 reqLen = 0;
474 rv = PR_Read(ssl_sock, pBuf, bufRem - 1);
475 if (rv == 0 ||
476 (rv < 0 && PR_END_OF_FILE_ERROR == PR_GetError())) {
477 if (verbose)
478 errWarn("HDX PR_Read hit EOF");
479 break;
480 }
481 if (rv < 0) {
482 errWarn("HDX PR_Read");
483 goto cleanup;
484 }
485 /* NULL termination */
486 pBuf[rv] = 0;
487 if (firstTime) {
488 firstTime = 0;
489 }
491 pBuf += rv;
492 bufRem -= rv;
493 bufDat = pBuf - buf;
494 /* Parse the input, starting at the beginning of the buffer.
495 * Stop when we detect two consecutive \n's (or \r\n's)
496 * as this signifies the end of the GET or POST portion.
497 * The posted data follows.
498 */
499 while (reqLen < bufDat && newln < 2) {
500 int octet = buf[reqLen++];
501 if (octet == '\n') {
502 newln++;
503 } else if (octet != '\r') {
504 newln = 0;
505 }
506 }
508 /* came to the end of the buffer, or second newln
509 * If we didn't get an empty line (CRLFCRLF) then keep on reading.
510 */
511 if (newln < 2)
512 continue;
514 /* we're at the end of the HTTP request.
515 * If the request is a POST, then there will be one more
516 * line of data.
517 * This parsing is a hack, but ok for SSL test purposes.
518 */
519 post = PORT_Strstr(buf, "POST ");
520 if (!post || *post != 'P')
521 break;
523 postData.data = (void*)(buf + reqLen);
525 tmp = "content-length: ";
526 foundStr = PL_strcasestr(buf, tmp);
527 if (foundStr) {
528 int expectedPostLen;
529 int havePostLen;
531 expectedPostLen = atoi(foundStr+strlen(tmp));
532 havePostLen = bufDat - reqLen;
533 if (havePostLen >= expectedPostLen) {
534 postData.len = expectedPostLen;
535 break;
536 }
537 } else {
538 /* use legacy hack */
539 /* It's a post, so look for the next and final CR/LF. */
540 while (reqLen < bufDat && newln < 3) {
541 int octet = buf[reqLen++];
542 if (octet == '\n') {
543 newln++;
544 }
545 }
546 if (newln == 3)
547 break;
548 }
549 } /* read loop */
551 bufDat = pBuf - buf;
552 if (bufDat) do { /* just close if no data */
553 /* Have either (a) a complete get, (b) a complete post, (c) EOF */
554 if (reqLen > 0) {
555 PRBool isGetOrPost = PR_FALSE;
556 unsigned skipChars = 0;
557 isPost = PR_FALSE;
559 if (!strncmp(buf, getCmd, sizeof getCmd - 1)) {
560 isGetOrPost = PR_TRUE;
561 skipChars = 4;
562 }
563 else if (!strncmp(buf, "POST ", 5)) {
564 isGetOrPost = PR_TRUE;
565 isPost = PR_TRUE;
566 skipChars = 5;
567 }
569 if (isGetOrPost) {
570 char * fnBegin = buf;
571 char * fnEnd;
572 char * fnstart = NULL;
573 PRFileInfo info;
575 fnBegin += skipChars;
577 fnEnd = strpbrk(fnBegin, " \r\n");
578 if (fnEnd) {
579 int fnLen = fnEnd - fnBegin;
580 if (fnLen < sizeof fileName) {
581 strncpy(fileName, fnBegin, fnLen);
582 fileName[fnLen] = 0; /* null terminate */
583 fnstart = fileName;
584 /* strip initial / because our root is the current directory*/
585 while (*fnstart && *fnstart=='/')
586 ++fnstart;
587 }
588 }
589 if (fnstart) {
590 if (!strncmp(fnstart, "ocsp", 4)) {
591 if (isPost) {
592 if (postData.data) {
593 isOcspRequest = PR_TRUE;
594 }
595 } else {
596 if (!strncmp(fnstart, "ocsp/", 5)) {
597 isOcspRequest = PR_TRUE;
598 getData = fnstart + 5;
599 }
600 }
601 } else {
602 /* try to open the file named.
603 * If successful, then write it to the client.
604 */
605 status = PR_GetFileInfo(fnstart, &info);
606 if (status == PR_SUCCESS &&
607 info.type == PR_FILE_FILE &&
608 info.size >= 0 ) {
609 local_file_fd = PR_Open(fnstart, PR_RDONLY, 0);
610 }
611 }
612 }
613 }
614 }
616 numIOVs = 0;
618 iovs[numIOVs].iov_base = (char *)outHeader;
619 iovs[numIOVs].iov_len = (sizeof(outHeader)) - 1;
620 numIOVs++;
622 if (isOcspRequest && caRevoInfos) {
623 CERTOCSPRequest *request = NULL;
624 PRBool failThisRequest = PR_FALSE;
626 if (ocspMethodsAllowed == ocspGetOnly && postData.len) {
627 failThisRequest = PR_TRUE;
628 } else if (ocspMethodsAllowed == ocspPostOnly && getData) {
629 failThisRequest = PR_TRUE;
630 } else if (ocspMethodsAllowed == ocspRandomGetFailure && getData) {
631 if (!(rand() % 2)) {
632 failThisRequest = PR_TRUE;
633 }
634 }
636 if (failThisRequest) {
637 PR_Write(ssl_sock, outBadRequestHeader, strlen(outBadRequestHeader));
638 break;
639 }
640 /* get is base64, post is binary.
641 * If we have base64, convert into the (empty) postData array.
642 */
643 if (getData) {
644 if (urldecode_base64chars_inplace(getData) == SECSuccess) {
645 NSSBase64_DecodeBuffer(NULL, &postData, getData, strlen(getData));
646 }
647 }
648 if (postData.len) {
649 request = CERT_DecodeOCSPRequest(&postData);
650 }
651 if (!request || !request->tbsRequest ||
652 !request->tbsRequest->requestList ||
653 !request->tbsRequest->requestList[0]) {
654 PORT_Sprintf(msgBuf, "Cannot decode OCSP request.\r\n");
656 iovs[numIOVs].iov_base = msgBuf;
657 iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
658 numIOVs++;
659 } else {
660 /* TODO: support more than one request entry */
661 CERTOCSPCertID *reqid = request->tbsRequest->requestList[0]->reqCert;
662 const caRevoInfo *revoInfo = NULL;
663 PRBool unknown = PR_FALSE;
664 PRBool revoked = PR_FALSE;
665 PRTime nextUpdate = 0;
666 PRTime revoDate = 0;
667 PRCList *caRevoIter;
669 caRevoIter = &caRevoInfos->link;
670 do {
671 CERTOCSPCertID *caid;
673 revoInfo = (caRevoInfo*)caRevoIter;
674 caid = revoInfo->id;
676 if (SECOID_CompareAlgorithmID(&reqid->hashAlgorithm,
677 &caid->hashAlgorithm) == SECEqual
678 &&
679 SECITEM_CompareItem(&reqid->issuerNameHash,
680 &caid->issuerNameHash) == SECEqual
681 &&
682 SECITEM_CompareItem(&reqid->issuerKeyHash,
683 &caid->issuerKeyHash) == SECEqual) {
684 break;
685 }
686 revoInfo = NULL;
687 caRevoIter = PR_NEXT_LINK(caRevoIter);
688 } while (caRevoIter != &caRevoInfos->link);
690 if (!revoInfo) {
691 unknown = PR_TRUE;
692 revoInfo = caRevoInfos;
693 } else {
694 CERTCrl *crl = &revoInfo->crl->crl;
695 CERTCrlEntry *entry = NULL;
696 DER_DecodeTimeChoice(&nextUpdate, &crl->nextUpdate);
697 if (crl->entries) {
698 int iv = 0;
699 /* assign, not compare */
700 while ((entry = crl->entries[iv++])) {
701 if (SECITEM_CompareItem(&reqid->serialNumber,
702 &entry->serialNumber) == SECEqual) {
703 break;
704 }
705 }
706 }
707 if (entry) {
708 /* revoked status response */
709 revoked = PR_TRUE;
710 DER_DecodeTimeChoice(&revoDate, &entry->revocationDate);
711 } else {
712 /* else good status response */
713 if (!isPost && ocspMethodsAllowed == ocspGetUnknown) {
714 unknown = PR_TRUE;
715 nextUpdate = PR_Now() + 60*60*24 * PR_USEC_PER_SEC; /*tomorrow*/
716 revoDate = PR_Now() - 60*60*24 * PR_USEC_PER_SEC; /*yesterday*/
717 }
718 }
719 }
721 {
722 PRTime now = PR_Now();
723 PLArenaPool *arena = NULL;
724 CERTOCSPSingleResponse *sr;
725 CERTOCSPSingleResponse **singleResponses;
726 SECItem *ocspResponse;
728 arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
730 if (unknown) {
731 sr = CERT_CreateOCSPSingleResponseUnknown(arena, reqid, now,
732 &nextUpdate);
733 } else if (revoked) {
734 sr = CERT_CreateOCSPSingleResponseRevoked(arena, reqid, now,
735 &nextUpdate, revoDate, NULL);
736 } else {
737 sr = CERT_CreateOCSPSingleResponseGood(arena, reqid, now,
738 &nextUpdate);
739 }
741 /* meaning of value 2: one entry + one end marker */
742 singleResponses = PORT_ArenaNewArray(arena, CERTOCSPSingleResponse*, 2);
743 singleResponses[0] = sr;
744 singleResponses[1] = NULL;
745 ocspResponse = CERT_CreateEncodedOCSPSuccessResponse(arena,
746 revoInfo->cert, ocspResponderID_byName, now,
747 singleResponses, &pwdata);
749 if (!ocspResponse) {
750 PORT_Sprintf(msgBuf, "Failed to encode response\r\n");
751 iovs[numIOVs].iov_base = msgBuf;
752 iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
753 numIOVs++;
754 } else {
755 PR_Write(ssl_sock, outOcspHeader, strlen(outOcspHeader));
756 PR_Write(ssl_sock, ocspResponse->data, ocspResponse->len);
757 PORT_FreeArena(arena, PR_FALSE);
758 }
759 }
760 break;
761 }
762 } else if (local_file_fd) {
763 PRInt32 bytes;
764 int errLen;
765 bytes = PR_TransmitFile(ssl_sock, local_file_fd, outHeader,
766 sizeof outHeader - 1,
767 PR_TRANSMITFILE_KEEP_OPEN,
768 PR_INTERVAL_NO_TIMEOUT);
769 if (bytes >= 0) {
770 bytes -= sizeof outHeader - 1;
771 FPRINTF(stderr,
772 "httpserv: PR_TransmitFile wrote %d bytes from %s\n",
773 bytes, fileName);
774 break;
775 }
776 errString = errWarn("PR_TransmitFile");
777 errLen = PORT_Strlen(errString);
778 errLen = PR_MIN(errLen, sizeof msgBuf - 1);
779 PORT_Memcpy(msgBuf, errString, errLen);
780 msgBuf[errLen] = 0;
782 iovs[numIOVs].iov_base = msgBuf;
783 iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
784 numIOVs++;
785 } else if (reqLen <= 0) { /* hit eof */
786 PORT_Sprintf(msgBuf, "Get or Post incomplete after %d bytes.\r\n",
787 bufDat);
789 iovs[numIOVs].iov_base = msgBuf;
790 iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
791 numIOVs++;
792 } else if (reqLen < bufDat) {
793 PORT_Sprintf(msgBuf, "Discarded %d characters.\r\n",
794 bufDat - reqLen);
796 iovs[numIOVs].iov_base = msgBuf;
797 iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
798 numIOVs++;
799 }
801 if (reqLen > 0) {
802 if (verbose > 1)
803 fwrite(buf, 1, reqLen, stdout); /* display it */
805 iovs[numIOVs].iov_base = buf;
806 iovs[numIOVs].iov_len = reqLen;
807 numIOVs++;
808 }
810 rv = PR_Writev(ssl_sock, iovs, numIOVs, PR_INTERVAL_NO_TIMEOUT);
811 if (rv < 0) {
812 errWarn("PR_Writev");
813 break;
814 }
816 } while (0);
818 cleanup:
819 if (ssl_sock) {
820 PR_Close(ssl_sock);
821 } else if (tcp_sock) {
822 PR_Close(tcp_sock);
823 }
824 if (local_file_fd)
825 PR_Close(local_file_fd);
826 VLOG(("httpserv: handle_connection: exiting\n"));
828 /* do a nice shutdown if asked. */
829 if (!strncmp(buf, stopCmd, sizeof stopCmd - 1)) {
830 VLOG(("httpserv: handle_connection: stop command"));
831 stop_server();
832 }
833 VLOG(("httpserv: handle_connection: exiting"));
834 return SECSuccess; /* success */
835 }
837 #ifdef XP_UNIX
839 void sigusr1_handler(int sig)
840 {
841 VLOG(("httpserv: sigusr1_handler: stop server"));
842 stop_server();
843 }
845 #endif
847 SECStatus
848 do_accepts(
849 PRFileDesc *listen_sock,
850 PRFileDesc *model_sock,
851 int requestCert
852 )
853 {
854 PRNetAddr addr;
855 PRErrorCode perr;
856 #ifdef XP_UNIX
857 struct sigaction act;
858 #endif
860 VLOG(("httpserv: do_accepts: starting"));
861 PR_SetThreadPriority( PR_GetCurrentThread(), PR_PRIORITY_HIGH);
863 acceptorThread = PR_GetCurrentThread();
864 #ifdef XP_UNIX
865 /* set up the signal handler */
866 act.sa_handler = sigusr1_handler;
867 sigemptyset(&act.sa_mask);
868 act.sa_flags = 0;
869 if (sigaction(SIGUSR1, &act, NULL)) {
870 fprintf(stderr, "Error installing signal handler.\n");
871 exit(1);
872 }
873 #endif
874 while (!stopping) {
875 PRFileDesc *tcp_sock;
876 PRCList *myLink;
878 FPRINTF(stderr, "\n\n\nhttpserv: About to call accept.\n");
879 tcp_sock = PR_Accept(listen_sock, &addr, PR_INTERVAL_NO_TIMEOUT);
880 if (tcp_sock == NULL) {
881 perr = PR_GetError();
882 if ((perr != PR_CONNECT_RESET_ERROR &&
883 perr != PR_PENDING_INTERRUPT_ERROR) || verbose) {
884 errWarn("PR_Accept");
885 }
886 if (perr == PR_CONNECT_RESET_ERROR) {
887 FPRINTF(stderr,
888 "Ignoring PR_CONNECT_RESET_ERROR error - continue\n");
889 continue;
890 }
891 stopping = 1;
892 break;
893 }
895 VLOG(("httpserv: do_accept: Got connection\n"));
897 PZ_Lock(qLock);
898 while (PR_CLIST_IS_EMPTY(&freeJobs) && !stopping) {
899 PZ_WaitCondVar(freeListNotEmptyCv, PR_INTERVAL_NO_TIMEOUT);
900 }
901 if (stopping) {
902 PZ_Unlock(qLock);
903 if (tcp_sock) {
904 PR_Close(tcp_sock);
905 }
906 break;
907 }
908 myLink = PR_LIST_HEAD(&freeJobs);
909 PR_REMOVE_AND_INIT_LINK(myLink);
910 /* could release qLock here and reaquire it 7 lines below, but
911 ** why bother for 4 assignment statements?
912 */
913 {
914 JOB * myJob = (JOB *)myLink;
915 myJob->tcp_sock = tcp_sock;
916 myJob->model_sock = model_sock;
917 myJob->requestCert = requestCert;
918 }
920 PR_APPEND_LINK(myLink, &jobQ);
921 PZ_NotifyCondVar(jobQNotEmptyCv);
922 PZ_Unlock(qLock);
923 }
925 FPRINTF(stderr, "httpserv: Closing listen socket.\n");
926 VLOG(("httpserv: do_accepts: exiting"));
927 if (listen_sock) {
928 PR_Close(listen_sock);
929 }
930 return SECSuccess;
931 }
933 PRFileDesc *
934 getBoundListenSocket(unsigned short port)
935 {
936 PRFileDesc * listen_sock;
937 int listenQueueDepth = 5 + (2 * maxThreads);
938 PRStatus prStatus;
939 PRNetAddr addr;
940 PRSocketOptionData opt;
942 addr.inet.family = PR_AF_INET;
943 addr.inet.ip = PR_INADDR_ANY;
944 addr.inet.port = PR_htons(port);
946 listen_sock = PR_NewTCPSocket();
947 if (listen_sock == NULL) {
948 errExit("PR_NewTCPSocket");
949 }
951 opt.option = PR_SockOpt_Nonblocking;
952 opt.value.non_blocking = PR_FALSE;
953 prStatus = PR_SetSocketOption(listen_sock, &opt);
954 if (prStatus < 0) {
955 PR_Close(listen_sock);
956 errExit("PR_SetSocketOption(PR_SockOpt_Nonblocking)");
957 }
959 opt.option=PR_SockOpt_Reuseaddr;
960 opt.value.reuse_addr = PR_TRUE;
961 prStatus = PR_SetSocketOption(listen_sock, &opt);
962 if (prStatus < 0) {
963 PR_Close(listen_sock);
964 errExit("PR_SetSocketOption(PR_SockOpt_Reuseaddr)");
965 }
967 #ifndef WIN95
968 /* Set PR_SockOpt_Linger because it helps prevent a server bind issue
969 * after clean shutdown . See bug 331413 .
970 * Don't do it in the WIN95 build configuration because clean shutdown is
971 * not implemented, and PR_SockOpt_Linger causes a hang in ssl.sh .
972 * See bug 332348 */
973 opt.option=PR_SockOpt_Linger;
974 opt.value.linger.polarity = PR_TRUE;
975 opt.value.linger.linger = PR_SecondsToInterval(1);
976 prStatus = PR_SetSocketOption(listen_sock, &opt);
977 if (prStatus < 0) {
978 PR_Close(listen_sock);
979 errExit("PR_SetSocketOption(PR_SockOpt_Linger)");
980 }
981 #endif
983 prStatus = PR_Bind(listen_sock, &addr);
984 if (prStatus < 0) {
985 PR_Close(listen_sock);
986 errExit("PR_Bind");
987 }
989 prStatus = PR_Listen(listen_sock, listenQueueDepth);
990 if (prStatus < 0) {
991 PR_Close(listen_sock);
992 errExit("PR_Listen");
993 }
994 return listen_sock;
995 }
997 void
998 server_main(
999 PRFileDesc * listen_sock,
1000 int requestCert,
1001 SECKEYPrivateKey ** privKey,
1002 CERTCertificate ** cert,
1003 const char *expectedHostNameVal)
1004 {
1005 PRFileDesc *model_sock = NULL;
1007 /* Now, do the accepting, here in the main thread. */
1008 do_accepts(listen_sock, model_sock, requestCert);
1010 terminateWorkerThreads();
1012 if (model_sock) {
1013 PR_Close(model_sock);
1014 }
1016 }
1018 int numChildren;
1019 PRProcess * child[MAX_PROCS];
1021 PRProcess *
1022 haveAChild(int argc, char **argv, PRProcessAttr * attr)
1023 {
1024 PRProcess * newProcess;
1026 newProcess = PR_CreateProcess(argv[0], argv, NULL, attr);
1027 if (!newProcess) {
1028 errWarn("Can't create new process.");
1029 } else {
1030 child[numChildren++] = newProcess;
1031 }
1032 return newProcess;
1033 }
1035 /* slightly adjusted version of ocsp_CreateCertID (not using issuer) */
1036 static CERTOCSPCertID *
1037 ocsp_CreateSelfCAID(PLArenaPool *arena, CERTCertificate *cert, PRTime time)
1038 {
1039 CERTOCSPCertID *certID;
1040 void *mark = PORT_ArenaMark(arena);
1041 SECStatus rv;
1043 PORT_Assert(arena != NULL);
1045 certID = PORT_ArenaZNew(arena, CERTOCSPCertID);
1046 if (certID == NULL) {
1047 goto loser;
1048 }
1050 rv = SECOID_SetAlgorithmID(arena, &certID->hashAlgorithm, SEC_OID_SHA1,
1051 NULL);
1052 if (rv != SECSuccess) {
1053 goto loser;
1054 }
1056 if (CERT_GetSubjectNameDigest(arena, cert, SEC_OID_SHA1,
1057 &(certID->issuerNameHash)) == NULL) {
1058 goto loser;
1059 }
1060 certID->issuerSHA1NameHash.data = certID->issuerNameHash.data;
1061 certID->issuerSHA1NameHash.len = certID->issuerNameHash.len;
1063 if (CERT_GetSubjectNameDigest(arena, cert, SEC_OID_MD5,
1064 &(certID->issuerMD5NameHash)) == NULL) {
1065 goto loser;
1066 }
1068 if (CERT_GetSubjectNameDigest(arena, cert, SEC_OID_MD2,
1069 &(certID->issuerMD2NameHash)) == NULL) {
1070 goto loser;
1071 }
1073 if (CERT_GetSubjectPublicKeyDigest(arena, cert, SEC_OID_SHA1,
1074 &certID->issuerKeyHash) == NULL) {
1075 goto loser;
1076 }
1077 certID->issuerSHA1KeyHash.data = certID->issuerKeyHash.data;
1078 certID->issuerSHA1KeyHash.len = certID->issuerKeyHash.len;
1079 /* cache the other two hash algorithms as well */
1080 if (CERT_GetSubjectPublicKeyDigest(arena, cert, SEC_OID_MD5,
1081 &certID->issuerMD5KeyHash) == NULL) {
1082 goto loser;
1083 }
1084 if (CERT_GetSubjectPublicKeyDigest(arena, cert, SEC_OID_MD2,
1085 &certID->issuerMD2KeyHash) == NULL) {
1086 goto loser;
1087 }
1089 PORT_ArenaUnmark(arena, mark);
1090 return certID;
1092 loser:
1093 PORT_ArenaRelease(arena, mark);
1094 return NULL;
1095 }
1097 /* slightly adjusted version of CERT_CreateOCSPCertID */
1098 CERTOCSPCertID*
1099 cert_CreateSelfCAID(CERTCertificate *cert, PRTime time)
1100 {
1101 PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
1102 CERTOCSPCertID *certID;
1103 PORT_Assert(arena != NULL);
1104 if (!arena)
1105 return NULL;
1107 certID = ocsp_CreateSelfCAID(arena, cert, time);
1108 if (!certID) {
1109 PORT_FreeArena(arena, PR_FALSE);
1110 return NULL;
1111 }
1112 certID->poolp = arena;
1113 return certID;
1114 }
1116 int
1117 main(int argc, char **argv)
1118 {
1119 char * progName = NULL;
1120 const char * dir = ".";
1121 char * passwd = NULL;
1122 char * pwfile = NULL;
1123 const char * pidFile = NULL;
1124 char * tmp;
1125 PRFileDesc * listen_sock;
1126 int optionsFound = 0;
1127 unsigned short port = 0;
1128 SECStatus rv;
1129 PRStatus prStatus;
1130 PRBool bindOnly = PR_FALSE;
1131 PRBool useLocalThreads = PR_FALSE;
1132 PLOptState *optstate;
1133 PLOptStatus status;
1134 char emptyString[] = { "" };
1135 char* certPrefix = emptyString;
1136 caRevoInfo *revoInfo = NULL;
1137 PRCList *caRevoIter = NULL;
1138 PRBool provideOcsp = PR_FALSE;
1140 tmp = strrchr(argv[0], '/');
1141 tmp = tmp ? tmp + 1 : argv[0];
1142 progName = strrchr(tmp, '\\');
1143 progName = progName ? progName + 1 : tmp;
1145 PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
1147 /* please keep this list of options in ASCII collating sequence.
1148 ** numbers, then capital letters, then lower case, alphabetical.
1149 */
1150 optstate = PL_CreateOptState(argc, argv,
1151 "A:C:DO:P:bd:f:hi:p:t:vw:");
1152 while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
1153 ++optionsFound;
1154 switch(optstate->option) {
1155 /* A first, must be followed by C. Any other order is an error.
1156 * A creates the object. C completes and moves into list.
1157 */
1158 case 'A':
1159 provideOcsp = PR_TRUE;
1160 if (revoInfo) { Usage(progName); exit(0); }
1161 revoInfo = PORT_New(caRevoInfo);
1162 revoInfo->nickname = PORT_Strdup(optstate->value);
1163 break;
1164 case 'C':
1165 if (!revoInfo) { Usage(progName); exit(0); }
1166 revoInfo->crlFilename = PORT_Strdup(optstate->value);
1167 if (!caRevoInfos) {
1168 PR_INIT_CLIST(&revoInfo->link);
1169 caRevoInfos = revoInfo;
1170 } else {
1171 PR_APPEND_LINK(&revoInfo->link, &caRevoInfos->link);
1172 }
1173 revoInfo = NULL;
1174 break;
1176 case 'O':
1177 if (!PL_strcasecmp(optstate->value, "all")) {
1178 ocspMethodsAllowed = ocspGetAndPost;
1179 } else if (!PL_strcasecmp(optstate->value, "get")) {
1180 ocspMethodsAllowed = ocspGetOnly;
1181 } else if (!PL_strcasecmp(optstate->value, "post")) {
1182 ocspMethodsAllowed = ocspPostOnly;
1183 } else if (!PL_strcasecmp(optstate->value, "random")) {
1184 ocspMethodsAllowed = ocspRandomGetFailure;
1185 } else if (!PL_strcasecmp(optstate->value, "get-unknown")) {
1186 ocspMethodsAllowed = ocspGetUnknown;
1187 } else {
1188 Usage(progName); exit(0);
1189 }
1190 break;
1192 case 'D': noDelay = PR_TRUE; break;
1194 case 'P': certPrefix = PORT_Strdup(optstate->value); break;
1196 case 'b': bindOnly = PR_TRUE; break;
1198 case 'd': dir = optstate->value; break;
1200 case 'f':
1201 pwdata.source = PW_FROMFILE;
1202 pwdata.data = pwfile = PORT_Strdup(optstate->value);
1203 break;
1205 case 'h': Usage(progName); exit(0); break;
1207 case 'i': pidFile = optstate->value; break;
1209 case 'p': port = PORT_Atoi(optstate->value); break;
1211 case 't':
1212 maxThreads = PORT_Atoi(optstate->value);
1213 if ( maxThreads > MAX_THREADS ) maxThreads = MAX_THREADS;
1214 if ( maxThreads < MIN_THREADS ) maxThreads = MIN_THREADS;
1215 break;
1217 case 'v': verbose++; break;
1219 case 'w':
1220 pwdata.source = PW_PLAINTEXT;
1221 pwdata.data = passwd = PORT_Strdup(optstate->value);
1222 break;
1224 default:
1225 case '?':
1226 fprintf(stderr, "Unrecognized or bad option specified.\n");
1227 fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
1228 exit(4);
1229 break;
1230 }
1231 }
1232 PL_DestroyOptState(optstate);
1233 if (status == PL_OPT_BAD) {
1234 fprintf(stderr, "Unrecognized or bad option specified.\n");
1235 fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
1236 exit(5);
1237 }
1238 if (!optionsFound) {
1239 Usage(progName);
1240 exit(51);
1241 }
1243 /* The -b (bindOnly) option is only used by the ssl.sh test
1244 * script on Linux to determine whether a previous httpserv
1245 * process has fully died and freed the port. (Bug 129701)
1246 */
1247 if (bindOnly) {
1248 listen_sock = getBoundListenSocket(port);
1249 if (!listen_sock) {
1250 exit(1);
1251 }
1252 if (listen_sock) {
1253 PR_Close(listen_sock);
1254 }
1255 exit(0);
1256 }
1258 if (port == 0) {
1259 fprintf(stderr, "Required argument 'port' must be non-zero value\n");
1260 exit(7);
1261 }
1263 if (pidFile) {
1264 FILE *tmpfile=fopen(pidFile,"w+");
1266 if (tmpfile) {
1267 fprintf(tmpfile,"%d",getpid());
1268 fclose(tmpfile);
1269 }
1270 }
1272 tmp = getenv("TMP");
1273 if (!tmp)
1274 tmp = getenv("TMPDIR");
1275 if (!tmp)
1276 tmp = getenv("TEMP");
1277 /* we're an ordinary single process server. */
1278 listen_sock = getBoundListenSocket(port);
1279 prStatus = PR_SetFDInheritable(listen_sock, PR_FALSE);
1280 if (prStatus != PR_SUCCESS)
1281 errExit("PR_SetFDInheritable");
1283 lm = PR_NewLogModule("TestCase");
1285 /* set our password function */
1286 PK11_SetPasswordFunc(SECU_GetModulePassword);
1288 if (provideOcsp) {
1289 /* Call the NSS initialization routines */
1290 rv = NSS_Initialize(dir, certPrefix, certPrefix, SECMOD_DB, NSS_INIT_READONLY);
1291 if (rv != SECSuccess) {
1292 fputs("NSS_Init failed.\n", stderr);
1293 exit(8);
1294 }
1296 if (caRevoInfos) {
1297 caRevoIter = &caRevoInfos->link;
1298 do {
1299 PRFileDesc *inFile;
1300 int rv = SECFailure;
1301 SECItem crlDER;
1302 crlDER.data = NULL;
1304 revoInfo = (caRevoInfo*)caRevoIter;
1305 revoInfo->cert = CERT_FindCertByNickname(
1306 CERT_GetDefaultCertDB(), revoInfo->nickname);
1307 if (!revoInfo->cert) {
1308 fprintf(stderr, "cannot find cert with nickname %s\n",
1309 revoInfo->nickname);
1310 exit(1);
1311 }
1312 inFile = PR_Open(revoInfo->crlFilename, PR_RDONLY, 0);
1313 if (inFile) {
1314 rv = SECU_ReadDERFromFile(&crlDER, inFile, PR_FALSE, PR_FALSE);
1315 PR_Close(inFile);
1316 inFile = NULL;
1317 }
1318 if (rv != SECSuccess) {
1319 fprintf(stderr, "unable to read crl file %s\n",
1320 revoInfo->crlFilename);
1321 exit(1);
1322 }
1323 revoInfo->crl =
1324 CERT_DecodeDERCrlWithFlags(NULL, &crlDER, SEC_CRL_TYPE,
1325 CRL_DECODE_DEFAULT_OPTIONS);
1326 if (!revoInfo->crl) {
1327 fprintf(stderr, "unable to decode crl file %s\n",
1328 revoInfo->crlFilename);
1329 exit(1);
1330 }
1331 if (CERT_CompareName(&revoInfo->crl->crl.name,
1332 &revoInfo->cert->subject) != SECEqual) {
1333 fprintf(stderr, "CRL %s doesn't match cert identified by preceding nickname %s\n",
1334 revoInfo->crlFilename, revoInfo->nickname);
1335 exit(1);
1336 }
1337 revoInfo->id = cert_CreateSelfCAID(revoInfo->cert, PR_Now());
1338 caRevoIter = PR_NEXT_LINK(caRevoIter);
1339 } while (caRevoIter != &caRevoInfos->link);
1340 }
1341 }
1343 /* allocate the array of thread slots, and launch the worker threads. */
1344 rv = launch_threads(&jobLoop, 0, 0, 0, useLocalThreads);
1346 if (rv == SECSuccess) {
1347 server_main(listen_sock, 0, 0, 0,
1348 0);
1349 }
1351 VLOG(("httpserv: server_thread: exiting"));
1353 if (provideOcsp) {
1354 if (caRevoInfos) {
1355 PRCList *caRevoIter;
1357 caRevoIter = &caRevoInfos->link;
1358 do {
1359 caRevoInfo *revoInfo = (caRevoInfo*)caRevoIter;
1360 if (revoInfo->nickname)
1361 PORT_Free(revoInfo->nickname);
1362 if (revoInfo->crlFilename)
1363 PORT_Free(revoInfo->crlFilename);
1364 if (revoInfo->cert)
1365 CERT_DestroyCertificate(revoInfo->cert);
1366 if (revoInfo->id)
1367 CERT_DestroyOCSPCertID(revoInfo->id);
1368 if (revoInfo->crl)
1369 SEC_DestroyCrl(revoInfo->crl);
1371 caRevoIter = PR_NEXT_LINK(caRevoIter);
1372 } while (caRevoIter != &caRevoInfos->link);
1374 }
1375 if (NSS_Shutdown() != SECSuccess) {
1376 SECU_PrintError(progName, "NSS_Shutdown");
1377 PR_Cleanup();
1378 exit(1);
1379 }
1380 }
1381 if (passwd) {
1382 PORT_Free(passwd);
1383 }
1384 if (pwfile) {
1385 PORT_Free(pwfile);
1386 }
1387 if (certPrefix && certPrefix != emptyString) {
1388 PORT_Free(certPrefix);
1389 }
1390 PR_Cleanup();
1391 printf("httpserv: normal termination\n");
1392 return 0;
1393 }