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/. */
15 #include <process.h> /* for getpid() */
32 #define PORT_Sprintf sprintf
36 #define PORT_Strstr strstr
40 #define PORT_Malloc PR_Malloc
43 static int handle_connection( PRFileDesc *, PRFileDesc *, int );
45 static const char inheritableSockName[] = { "SELFSERV_LISTEN_SOCKET" };
47 #define DEFAULT_BULK_TEST 16384
48 #define MAX_BULK_TEST 1048576 /* 1 MB */
49 static PRBool testBulk;
51 /* data and structures for shutdown */
54 static PRBool noDelay;
57 static PRThread * acceptorThread;
59 static PRLogModuleInfo *lm;
61 #define PRINTF if (verbose) printf
62 #define FPRINTF if (verbose) fprintf
63 #define FLUSH if (verbose) { fflush(stdout); fflush(stderr); }
64 #define VLOG(arg) PR_LOG(lm,PR_LOG_DEBUG,arg)
67 Usage(const char *progName)
71 "Usage: %s -p port [-Dbv]\n"
72 " [-t threads] [-i pid_file]\n"
73 "-D means disable Nagle delays in TCP\n"
74 "-b means try binding to the port and exit\n"
75 "-v means verbose output\n"
76 "-t threads -- specify the number of threads to use for connections.\n"
77 "-i pid_file file to write the process id of selfserve\n"
82 errWarn(char * funcString)
84 PRErrorCode perr = PR_GetError();
85 const char * errString = SECU_Strerror(perr);
87 fprintf(stderr, "selfserv: %s returned error %d:\n%s\n",
88 funcString, perr, errString);
93 errExit(char * funcString)
100 #define MAX_VIRT_SERVER_NAME_ARRAY_INDEX 10
102 /**************************************************************************
103 ** Begin thread management routines and data.
104 **************************************************************************/
105 #define MIN_THREADS 3
106 #define DEFAULT_THREADS 8
107 #define MAX_THREADS 4096
109 static int maxThreads = DEFAULT_THREADS;
112 typedef struct jobStr {
114 PRFileDesc *tcp_sock;
115 PRFileDesc *model_sock;
119 static PZLock * qLock; /* this lock protects all data immediately below */
120 static PRLock * lastLoadedCrlLock; /* this lock protects lastLoadedCrl variable */
121 static PZCondVar * jobQNotEmptyCv;
122 static PZCondVar * freeListNotEmptyCv;
123 static PZCondVar * threadCountChangeCv;
124 static int threadCount;
126 static PRCList freeJobs;
127 static JOB *jobTable;
130 setupJobs(int maxJobs)
134 jobTable = (JOB *)PR_Calloc(maxJobs, sizeof(JOB));
138 PR_INIT_CLIST(&jobQ);
139 PR_INIT_CLIST(&freeJobs);
141 for (i = 0; i < maxJobs; ++i) {
142 JOB * pJob = jobTable + i;
143 PR_APPEND_LINK(&pJob->link, &freeJobs);
148 typedef int startFn(PRFileDesc *a, PRFileDesc *b, int c);
150 typedef enum { rs_idle = 0, rs_running = 1, rs_zombie = 2 } runState;
152 typedef struct perThreadStr {
162 static perThread *threads;
165 thread_wrapper(void * arg)
167 perThread * slot = (perThread *)arg;
169 slot->rv = (* slot->startFunc)(slot->a, slot->b, slot->c);
171 /* notify the thread exit handler. */
173 slot->state = rs_zombie;
175 PZ_NotifyAllCondVar(threadCountChangeCv);
180 jobLoop(PRFileDesc *a, PRFileDesc *b, int c)
182 PRCList * myLink = 0;
188 while (PR_CLIST_IS_EMPTY(&jobQ) && !stopping) {
189 PZ_WaitCondVar(jobQNotEmptyCv, PR_INTERVAL_NO_TIMEOUT);
191 if (!PR_CLIST_IS_EMPTY(&jobQ)) {
192 myLink = PR_LIST_HEAD(&jobQ);
193 PR_REMOVE_AND_INIT_LINK(myLink);
196 myJob = (JOB *)myLink;
197 /* myJob will be null when stopping is true and jobQ is empty */
200 handle_connection( myJob->tcp_sock, myJob->model_sock,
203 PR_APPEND_LINK(myLink, &freeJobs);
204 PZ_NotifyCondVar(freeListNotEmptyCv);
219 SECStatus rv = SECSuccess;
221 /* create the thread management serialization structs */
222 qLock = PZ_NewLock(nssILockSelfServ);
223 jobQNotEmptyCv = PZ_NewCondVar(qLock);
224 freeListNotEmptyCv = PZ_NewCondVar(qLock);
225 threadCountChangeCv = PZ_NewCondVar(qLock);
227 /* create monitor for crl reload procedure */
228 lastLoadedCrlLock = PR_NewLock();
230 /* allocate the array of thread slots */
231 threads = PR_Calloc(maxThreads, sizeof(perThread));
232 if ( NULL == threads ) {
233 fprintf(stderr, "Oh Drat! Can't allocate the perThread array\n");
236 /* 5 is a little extra, intended to keep the jobQ from underflowing.
237 ** That is, from going empty while not stopping and clients are still
238 ** trying to contact us.
240 rv = setupJobs(maxThreads + 5);
241 if (rv != SECSuccess)
245 for (i = 0; i < maxThreads; ++i) {
246 perThread * slot = threads + i;
248 slot->state = rs_running;
252 slot->startFunc = startFunc;
253 slot->prThread = PR_CreateThread(PR_USER_THREAD,
254 thread_wrapper, slot, PR_PRIORITY_NORMAL,
255 (PR_TRUE==local)?PR_LOCAL_THREAD:PR_GLOBAL_THREAD,
256 PR_UNJOINABLE_THREAD, 0);
257 if (slot->prThread == NULL) {
258 printf("selfserv: Failed to launch thread!\n");
259 slot->state = rs_idle;
271 #define DESTROY_CONDVAR(name) if (name) { \
272 PZ_DestroyCondVar(name); name = NULL; }
273 #define DESTROY_LOCK(name) if (name) { \
274 PZ_DestroyLock(name); name = NULL; }
278 terminateWorkerThreads(void)
280 VLOG(("selfserv: server_thead: waiting on stopping"));
282 PZ_NotifyAllCondVar(jobQNotEmptyCv);
283 while (threadCount > 0) {
284 PZ_WaitCondVar(threadCountChangeCv, PR_INTERVAL_NO_TIMEOUT);
286 /* The worker threads empty the jobQ before they terminate. */
287 PORT_Assert(PR_CLIST_IS_EMPTY(&jobQ));
290 DESTROY_CONDVAR(jobQNotEmptyCv);
291 DESTROY_CONDVAR(freeListNotEmptyCv);
292 DESTROY_CONDVAR(threadCountChangeCv);
294 PR_DestroyLock(lastLoadedCrlLock);
300 /**************************************************************************
301 ** End thread management routines.
302 **************************************************************************/
304 PRBool NoReuse = PR_FALSE;
305 PRBool disableLocking = PR_FALSE;
306 PRBool failedToNegotiateName = PR_FALSE;
309 static const char stopCmd[] = { "GET /stop " };
310 static const char getCmd[] = { "GET " };
311 static const char EOFmsg[] = { "EOF\r\n\r\n\r\n" };
312 static const char outHeader[] = {
313 "HTTP/1.0 200 OK\r\n"
314 "Server: Generic Web Server\r\n"
315 "Date: Tue, 26 Aug 1997 22:10:05 GMT\r\n"
316 "Content-type: text/plain\r\n"
323 PR_Interrupt(acceptorThread);
329 PRFileDesc *tcp_sock,
330 PRFileDesc *model_sock,
334 PRFileDesc * ssl_sock = NULL;
335 PRFileDesc * local_file_fd = NULL;
337 char * pBuf; /* unused space at end of buf */
338 const char * errString;
340 int bufRem; /* unused bytes at end of buf */
341 int bufDat; /* characters received in buf */
342 int newln = 0; /* # of consecutive newlns */
347 PRSocketOptionData opt;
356 VLOG(("selfserv: handle_connection: starting"));
357 opt.option = PR_SockOpt_Nonblocking;
358 opt.value.non_blocking = PR_FALSE;
359 PR_SetSocketOption(tcp_sock, &opt);
361 VLOG(("selfserv: handle_connection: starting\n"));
365 opt.option = PR_SockOpt_NoDelay;
366 opt.value.no_delay = PR_TRUE;
367 status = PR_SetSocketOption(ssl_sock, &opt);
368 if (status != PR_SUCCESS) {
369 errWarn("PR_SetSocketOption(PR_SockOpt_NoDelay, PR_TRUE)");
380 rv = PR_Read(ssl_sock, pBuf, bufRem - 1);
382 (rv < 0 && PR_END_OF_FILE_ERROR == PR_GetError())) {
384 errWarn("HDX PR_Read hit EOF");
388 errWarn("HDX PR_Read");
391 /* NULL termination */
400 /* Parse the input, starting at the beginning of the buffer.
401 * Stop when we detect two consecutive \n's (or \r\n's)
402 * as this signifies the end of the GET or POST portion.
403 * The posted data follows.
405 while (reqLen < bufDat && newln < 2) {
406 int octet = buf[reqLen++];
409 } else if (octet != '\r') {
414 /* came to the end of the buffer, or second newln
415 * If we didn't get an empty line (CRLFCRLF) then keep on reading.
420 /* we're at the end of the HTTP request.
421 * If the request is a POST, then there will be one more
423 * This parsing is a hack, but ok for SSL test purposes.
425 post = PORT_Strstr(buf, "POST ");
426 if (!post || *post != 'P')
429 /* It's a post, so look for the next and final CR/LF. */
430 /* We should parse content length here, but ... */
431 while (reqLen < bufDat && newln < 3) {
432 int octet = buf[reqLen++];
442 if (bufDat) do { /* just close if no data */
443 /* Have either (a) a complete get, (b) a complete post, (c) EOF */
444 if (reqLen > 0 && !strncmp(buf, getCmd, sizeof getCmd - 1)) {
445 char * fnBegin = buf + 4;
448 /* try to open the file named.
449 * If successful, then write it to the client.
451 fnEnd = strpbrk(fnBegin, " \r\n");
453 int fnLen = fnEnd - fnBegin;
454 if (fnLen < sizeof fileName) {
456 strncpy(fileName, fnBegin, fnLen);
457 fileName[fnLen] = 0; /* null terminate */
459 /* strip initial / because our root is the current directory*/
460 while (*fnstart && *fnstart=='/')
462 status = PR_GetFileInfo(fnstart, &info);
463 if (status == PR_SUCCESS &&
464 info.type == PR_FILE_FILE &&
466 local_file_fd = PR_Open(fnstart, PR_RDONLY, 0);
474 iovs[numIOVs].iov_base = (char *)outHeader;
475 iovs[numIOVs].iov_len = (sizeof(outHeader)) - 1;
481 bytes = PR_TransmitFile(ssl_sock, local_file_fd, outHeader,
482 sizeof outHeader - 1,
483 PR_TRANSMITFILE_KEEP_OPEN,
484 PR_INTERVAL_NO_TIMEOUT);
486 bytes -= sizeof outHeader - 1;
488 "selfserv: PR_TransmitFile wrote %d bytes from %s\n",
492 errString = errWarn("PR_TransmitFile");
493 errLen = PORT_Strlen(errString);
494 errLen = PR_MIN(errLen, sizeof msgBuf - 1);
495 PORT_Memcpy(msgBuf, errString, errLen);
498 iovs[numIOVs].iov_base = msgBuf;
499 iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
501 } else if (reqLen <= 0) { /* hit eof */
502 PORT_Sprintf(msgBuf, "Get or Post incomplete after %d bytes.\r\n",
505 iovs[numIOVs].iov_base = msgBuf;
506 iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
508 } else if (reqLen < bufDat) {
509 PORT_Sprintf(msgBuf, "Discarded %d characters.\r\n",
512 iovs[numIOVs].iov_base = msgBuf;
513 iovs[numIOVs].iov_len = PORT_Strlen(msgBuf);
519 fwrite(buf, 1, reqLen, stdout); /* display it */
521 iovs[numIOVs].iov_base = buf;
522 iovs[numIOVs].iov_len = reqLen;
526 /* Don't add the EOF if we want to test bulk encryption */
528 iovs[numIOVs].iov_base = (char *)EOFmsg;
529 iovs[numIOVs].iov_len = sizeof EOFmsg - 1;
533 rv = PR_Writev(ssl_sock, iovs, numIOVs, PR_INTERVAL_NO_TIMEOUT);
535 errWarn("PR_Writev");
544 } else if (tcp_sock) {
548 PR_Close(local_file_fd);
549 VLOG(("selfserv: handle_connection: exiting\n"));
551 /* do a nice shutdown if asked. */
552 if (!strncmp(buf, stopCmd, sizeof stopCmd - 1)) {
553 VLOG(("selfserv: handle_connection: stop command"));
556 VLOG(("selfserv: handle_connection: exiting"));
557 return SECSuccess; /* success */
562 void sigusr1_handler(int sig)
564 VLOG(("selfserv: sigusr1_handler: stop server"));
572 PRFileDesc *listen_sock,
573 PRFileDesc *model_sock,
580 struct sigaction act;
583 VLOG(("selfserv: do_accepts: starting"));
584 PR_SetThreadPriority( PR_GetCurrentThread(), PR_PRIORITY_HIGH);
586 acceptorThread = PR_GetCurrentThread();
588 /* set up the signal handler */
589 act.sa_handler = sigusr1_handler;
590 sigemptyset(&act.sa_mask);
592 if (sigaction(SIGUSR1, &act, NULL)) {
593 fprintf(stderr, "Error installing signal handler.\n");
598 PRFileDesc *tcp_sock;
601 FPRINTF(stderr, "\n\n\nselfserv: About to call accept.\n");
602 tcp_sock = PR_Accept(listen_sock, &addr, PR_INTERVAL_NO_TIMEOUT);
603 if (tcp_sock == NULL) {
604 perr = PR_GetError();
605 if ((perr != PR_CONNECT_RESET_ERROR &&
606 perr != PR_PENDING_INTERRUPT_ERROR) || verbose) {
607 errWarn("PR_Accept");
609 if (perr == PR_CONNECT_RESET_ERROR) {
611 "Ignoring PR_CONNECT_RESET_ERROR error - continue\n");
618 VLOG(("selfserv: do_accept: Got connection\n"));
621 while (PR_CLIST_IS_EMPTY(&freeJobs) && !stopping) {
622 PZ_WaitCondVar(freeListNotEmptyCv, PR_INTERVAL_NO_TIMEOUT);
631 myLink = PR_LIST_HEAD(&freeJobs);
632 PR_REMOVE_AND_INIT_LINK(myLink);
633 /* could release qLock here and reaquire it 7 lines below, but
634 ** why bother for 4 assignment statements?
637 JOB * myJob = (JOB *)myLink;
638 myJob->tcp_sock = tcp_sock;
639 myJob->model_sock = model_sock;
640 myJob->requestCert = requestCert;
643 PR_APPEND_LINK(myLink, &jobQ);
644 PZ_NotifyCondVar(jobQNotEmptyCv);
648 FPRINTF(stderr, "selfserv: Closing listen socket.\n");
649 VLOG(("selfserv: do_accepts: exiting"));
651 PR_Close(listen_sock);
657 getBoundListenSocket(unsigned short port)
659 PRFileDesc * listen_sock;
660 int listenQueueDepth = 5 + (2 * maxThreads);
663 PRSocketOptionData opt;
665 addr.inet.family = PR_AF_INET;
666 addr.inet.ip = PR_INADDR_ANY;
667 addr.inet.port = PR_htons(port);
669 listen_sock = PR_NewTCPSocket();
670 if (listen_sock == NULL) {
671 errExit("PR_NewTCPSocket");
674 opt.option = PR_SockOpt_Nonblocking;
675 opt.value.non_blocking = PR_FALSE;
676 prStatus = PR_SetSocketOption(listen_sock, &opt);
678 PR_Close(listen_sock);
679 errExit("PR_SetSocketOption(PR_SockOpt_Nonblocking)");
682 opt.option=PR_SockOpt_Reuseaddr;
683 opt.value.reuse_addr = PR_TRUE;
684 prStatus = PR_SetSocketOption(listen_sock, &opt);
686 PR_Close(listen_sock);
687 errExit("PR_SetSocketOption(PR_SockOpt_Reuseaddr)");
691 /* Set PR_SockOpt_Linger because it helps prevent a server bind issue
692 * after clean shutdown . See bug 331413 .
693 * Don't do it in the WIN95 build configuration because clean shutdown is
694 * not implemented, and PR_SockOpt_Linger causes a hang in ssl.sh .
696 opt.option=PR_SockOpt_Linger;
697 opt.value.linger.polarity = PR_TRUE;
698 opt.value.linger.linger = PR_SecondsToInterval(1);
699 prStatus = PR_SetSocketOption(listen_sock, &opt);
701 PR_Close(listen_sock);
702 errExit("PR_SetSocketOption(PR_SockOpt_Linger)");
706 prStatus = PR_Bind(listen_sock, &addr);
708 PR_Close(listen_sock);
712 prStatus = PR_Listen(listen_sock, listenQueueDepth);
714 PR_Close(listen_sock);
715 errExit("PR_Listen");
722 PRFileDesc * listen_sock,
724 SECKEYPrivateKey ** privKey,
725 CERTCertificate ** cert,
726 const char *expectedHostNameVal)
728 PRFileDesc *model_sock = NULL;
730 /* Now, do the accepting, here in the main thread. */
731 do_accepts(listen_sock, model_sock, requestCert);
733 terminateWorkerThreads();
736 PR_Close(model_sock);
742 PRProcess * child[MAX_PROCS];
745 haveAChild(int argc, char **argv, PRProcessAttr * attr)
747 PRProcess * newProcess;
749 newProcess = PR_CreateProcess(argv[0], argv, NULL, attr);
751 errWarn("Can't create new process.");
753 child[numChildren++] = newProcess;
759 main(int argc, char **argv)
761 char * progName = NULL;
762 const char * pidFile = NULL;
764 PRFileDesc * listen_sock;
765 int optionsFound = 0;
766 unsigned short port = 0;
769 PRBool bindOnly = PR_FALSE;
770 PRBool useLocalThreads = PR_FALSE;
771 PLOptState *optstate;
774 tmp = strrchr(argv[0], '/');
775 tmp = tmp ? tmp + 1 : argv[0];
776 progName = strrchr(tmp, '\\');
777 progName = progName ? progName + 1 : tmp;
779 PR_Init( PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
781 /* please keep this list of options in ASCII collating sequence.
782 ** numbers, then capital letters, then lower case, alphabetical.
784 optstate = PL_CreateOptState(argc, argv,
786 while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
788 switch(optstate->option) {
789 case 'D': noDelay = PR_TRUE; break;
791 case 'b': bindOnly = PR_TRUE; break;
793 case 'h': Usage(progName); exit(0); break;
795 case 'i': pidFile = optstate->value; break;
797 case 'p': port = PORT_Atoi(optstate->value); break;
800 maxThreads = PORT_Atoi(optstate->value);
801 if ( maxThreads > MAX_THREADS ) maxThreads = MAX_THREADS;
802 if ( maxThreads < MIN_THREADS ) maxThreads = MIN_THREADS;
805 case 'v': verbose++; break;
809 fprintf(stderr, "Unrecognized or bad option specified.\n");
810 fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
815 PL_DestroyOptState(optstate);
816 if (status == PL_OPT_BAD) {
817 fprintf(stderr, "Unrecognized or bad option specified.\n");
818 fprintf(stderr, "Run '%s -h' for usage information.\n", progName);
826 /* The -b (bindOnly) option is only used by the ssl.sh test
827 * script on Linux to determine whether a previous selfserv
828 * process has fully died and freed the port. (Bug 129701)
831 listen_sock = getBoundListenSocket(port);
836 PR_Close(listen_sock);
842 fprintf(stderr, "Required argument 'port' must be non-zero value\n");
847 FILE *tmpfile=fopen(pidFile,"w+");
850 fprintf(tmpfile,"%d",getpid());
857 tmp = getenv("TMPDIR");
859 tmp = getenv("TEMP");
860 /* we're an ordinary single process server. */
861 listen_sock = getBoundListenSocket(port);
862 prStatus = PR_SetFDInheritable(listen_sock, PR_FALSE);
863 if (prStatus != PR_SUCCESS)
864 errExit("PR_SetFDInheritable");
866 lm = PR_NewLogModule("TestCase");
868 /* allocate the array of thread slots, and launch the worker threads. */
869 rv = launch_threads(&jobLoop, 0, 0, 0, useLocalThreads);
871 if (rv == SECSuccess) {
872 server_main(listen_sock, 0, 0, 0,
876 VLOG(("selfserv: server_thread: exiting"));
878 if (failedToNegotiateName) {
879 fprintf(stderr, "selfserv: Failed properly negotiate server name\n");
884 printf("selfserv: normal termination\n");