2 * Copyright (c) 1995-1997, Index Data
3 * See the file LICENSE for details.
4 * Sebastian Hammer, Adam Dickmeiss
6 * NT server based on threads by
7 * Chas Woodfield, Fretwell Downing Datasystem.
10 * Revision 1.42 1997-10-27 14:03:02 adam
11 * Added new member to statserver_options_block, pre_init, which
12 * specifies a callback to be invoked after command line parsing and
13 * before the server listens for the first time.
15 * Revision 1.41 1997/09/29 07:19:32 adam
16 * Server library uses nmem_init/nmem_exit. The log prefix no longer
17 * includes leading path on NT.
19 * Revision 1.40 1997/09/17 12:10:41 adam
22 * Revision 1.39 1997/09/09 10:10:19 adam
23 * Another MSV5.0 port. Changed projects to include proper
24 * library/include paths.
25 * Server starts server in test-mode when no options are given.
27 * Revision 1.38 1997/09/04 14:19:14 adam
30 * Revision 1.37 1997/09/01 08:53:01 adam
31 * New windows NT/95 port using MSV5.0. The test server 'ztest' was
32 * moved a separate directory. MSV5.0 project server.dsp created.
33 * As an option, the server can now operate as an NT service.
35 * Revision 1.36 1996/07/06 19:58:36 quinn
36 * System headerfiles gathered in yconfig
38 * Revision 1.35 1996/05/29 10:03:28 quinn
41 * Revision 1.34 1996/02/21 13:12:07 quinn
42 * *** empty log message ***
44 * Revision 1.33 1996/02/10 12:23:49 quinn
45 * Enable inetd operations fro TCP/IP stack
47 * Revision 1.32 1996/01/19 15:41:52 quinn
48 * *** empty log message ***
50 * Revision 1.31 1995/11/17 11:09:39 adam
51 * Added new option '-c' to specify configuration name in control block.
53 * Revision 1.30 1995/11/01 13:54:59 quinn
56 * Revision 1.29 1995/10/30 12:41:29 quinn
57 * Added hostname lookup for server.
59 * Revision 1.28 1995/09/29 17:12:30 quinn
62 * Revision 1.27 1995/09/27 15:03:02 quinn
63 * Modified function heads & prototypes.
65 * Revision 1.26 1995/08/29 14:44:51 quinn
68 * Revision 1.25 1995/08/29 11:18:02 quinn
69 * Added code to receive close
71 * Revision 1.24 1995/06/16 10:31:39 quinn
72 * Added session timeout.
74 * Revision 1.23 1995/06/15 12:30:48 quinn
77 * Revision 1.22 1995/06/15 07:45:17 quinn
80 * Revision 1.21 1995/06/06 08:15:40 quinn
83 * Revision 1.20 1995/05/29 08:12:09 quinn
86 * Revision 1.19 1995/05/16 09:37:27 quinn
89 * Revision 1.18 1995/05/16 08:51:09 quinn
90 * License, documentation, and memory fixes
92 * Revision 1.17 1995/05/15 11:56:42 quinn
93 * Asynchronous facilities. Restructuring of seshigh code.
95 * Revision 1.16 1995/04/10 10:23:40 quinn
96 * Some work to add scan and other things.
98 * Revision 1.15 1995/03/31 10:16:51 quinn
101 * Revision 1.14 1995/03/31 09:18:58 quinn
104 * Revision 1.13 1995/03/30 16:08:39 quinn
107 * Revision 1.12 1995/03/30 13:29:02 quinn
110 * Revision 1.11 1995/03/30 12:18:17 quinn
113 * Revision 1.10 1995/03/29 15:40:16 quinn
114 * Ongoing work. Statserv is now dynamic by default
116 * Revision 1.9 1995/03/27 08:34:30 quinn
117 * Added dynamic server functionality.
118 * Released bindings to session.c (is now redundant)
120 * Revision 1.8 1995/03/20 09:46:26 quinn
123 * Revision 1.7 1995/03/16 13:29:04 quinn
124 * Partitioned server.
126 * Revision 1.6 1995/03/15 15:18:52 quinn
127 * Little changes to better support nonblocking I/O
130 * Revision 1.5 1995/03/15 08:37:45 quinn
131 * Now we're pretty much set for nonblocking I/O.
133 * Revision 1.4 1995/03/14 16:59:48 quinn
136 * Revision 1.3 1995/03/14 11:30:15 quinn
139 * Revision 1.2 1995/03/14 10:28:03 quinn
140 * More work on demo server.
142 * Revision 1.1 1995/03/10 18:22:45 quinn
143 * The rudiments of an asynchronous server.
165 #include <comstack.h>
171 #include <statserv.h>
173 static IOCHAN pListener;
175 static char *me = "statserver";
179 static statserv_options_block control_block = {
180 1, /* dynamic mode */
181 LOG_DEFAULT_LEVEL, /* log level */
183 "", /* diagnostic output to stderr */
184 "tcp:@:9999", /* default listener port */
185 PROTO_Z3950, /* default application protocol */
186 60, /* idle timeout (minutes) */
187 1024*1024, /* maximum PDU size (approx.) to allow */
188 "default-config", /* configuration name to pass to backend */
189 "", /* set user id */
194 * handle incoming connect requests.
195 * The dynamic mode is a bit tricky mostly because we want to avoid
196 * doing all of the listening and accepting in the parent - it's
201 typedef struct _ThreadList ThreadList;
203 typedef struct _ThreadList
210 static ThreadList *pFirstThread;
211 static CRITICAL_SECTION Thread_CritSect;
212 static BOOL bInitialized = FALSE;
214 static void ThreadList_Initialize()
216 /* Initialize the critical Sections */
217 InitializeCriticalSection(&Thread_CritSect);
219 /* Set the first thraed */
222 /* we have been initialized */
226 static void statserv_add(HANDLE hThread, IOCHAN pIOChannel)
228 /* Only one thread can go through this section at a time */
229 EnterCriticalSection(&Thread_CritSect);
232 /* Lets create our new object */
233 ThreadList *pNewThread = (ThreadList *)malloc(sizeof(ThreadList));
234 pNewThread->hThread = hThread;
235 pNewThread->pIOChannel = pIOChannel;
236 pNewThread->pNext = pFirstThread;
237 pFirstThread = pNewThread;
239 /* Lets let somebody else create a new object now */
240 LeaveCriticalSection(&Thread_CritSect);
244 void statserv_remove(IOCHAN pIOChannel)
246 /* Only one thread can go through this section at a time */
247 EnterCriticalSection(&Thread_CritSect);
250 ThreadList *pCurrentThread = pFirstThread;
251 ThreadList *pNextThread;
252 ThreadList *pPrevThread =NULL;
254 /* Step through alll the threads */
255 for (; pCurrentThread != NULL; pCurrentThread = pNextThread)
257 /* We only need to compare on the IO Channel */
258 if (pCurrentThread->pIOChannel == pIOChannel)
260 /* We have found the thread we want to delete */
261 /* First of all reset the next pointers */
262 if (pPrevThread == NULL)
263 pFirstThread = pCurrentThread->pNext;
265 pPrevThread->pNext = pCurrentThread->pNext;
267 /* All we need todo now is delete the memory */
268 free(pCurrentThread);
270 /* No need to look at any more threads */
275 /* We need to look at another thread */
276 pNextThread = pCurrentThread->pNext;
280 /* Lets let somebody else remove an object now */
281 LeaveCriticalSection(&Thread_CritSect);
285 void statserv_closedown()
287 /* Shouldn't do anything if we are not initialized */
291 HANDLE *pThreadHandles = NULL;
293 /* We need to stop threads adding and removing while we */
294 /* start the closedown process */
295 EnterCriticalSection(&Thread_CritSect);
298 /* We have exclusive access to the thread stuff now */
299 /* Y didn't i use a semaphore - Oh well never mind */
300 ThreadList *pCurrentThread = pFirstThread;
302 /* Before we do anything else, we need to shutdown the listener */
303 if (pListener != NULL)
304 iochan_destroy(pListener);
306 for (; pCurrentThread != NULL; pCurrentThread = pCurrentThread->pNext)
308 /* Just destroy the IOCHAN, that should do the trick */
309 iochan_destroy(pCurrentThread->pIOChannel);
311 /* Keep a running count of our handles */
317 HANDLE *pCurrentHandle ;
319 /* Allocate the thread handle array */
320 pThreadHandles = (HANDLE *)malloc(sizeof(HANDLE) * iHandles);
321 pCurrentHandle = pThreadHandles;
323 for (pCurrentThread = pFirstThread;
324 pCurrentThread != NULL;
325 pCurrentThread = pCurrentThread->pNext, pCurrentHandle++)
327 /* Just the handle */
328 *pCurrentHandle = pCurrentThread->hThread;
332 /* We can now leave the critical section */
333 LeaveCriticalSection(&Thread_CritSect);
336 /* Now we can really do something */
339 /* This will now wait, until all the threads close */
340 WaitForMultipleObjects(iHandles, pThreadHandles, TRUE, INFINITE);
342 /* Free the memory we allocated for the handle array */
343 free(pThreadHandles);
346 /* No longer require the critical section, since all threads are dead */
347 DeleteCriticalSection(&Thread_CritSect);
351 static void listener(IOCHAN h, int event)
353 COMSTACK line = (COMSTACK) iochan_getdata(h);
358 if (event == EVENT_INPUT)
360 if ((res = cs_listen(line, 0, 0)) < 0)
362 logf(LOG_FATAL, "cs_listen failed.");
367 logf(LOG_DEBUG, "listen ok");
368 iochan_setevent(h, EVENT_OUTPUT);
369 iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
371 else if (event == EVENT_OUTPUT)
378 if (!(new_line = cs_accept(line)))
380 logf(LOG_FATAL, "Accept failed.");
381 iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
384 logf(LOG_DEBUG, "Accept ok");
386 if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session, EVENT_INPUT)))
388 logf(LOG_FATAL, "Failed to create iochan");
393 logf(LOG_DEBUG, "Creating association");
394 if (!(newas = create_association(new_chan, new_line)))
396 logf(LOG_FATAL, "Failed to create new assoc.");
400 logf(LOG_DEBUG, "Setting timeout %d", control_block.idle_timeout);
401 iochan_setdata(new_chan, newas);
402 iochan_settimeout(new_chan, control_block.idle_timeout * 60);
403 logf(LOG_DEBUG, "Determining client address");
404 a = cs_addrstr(new_line);
405 logf(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
406 /* Now what we need todo is create a new thread with this iochan as
408 /* if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)event_loop, new_chan,
409 0, &ThreadId) == NULL) */
410 /* Somehow, somewhere we need to store this thread id, otherwise we won't be
411 able to close cleanly */
412 NewHandle = (HANDLE)_beginthreadex(NULL, 0, event_loop, new_chan, 0, &ThreadId);
413 if (NewHandle == (HANDLE)-1)
416 logf(LOG_FATAL|LOG_ERRNO, "Failed to create new thread.");
420 /* We successfully created the thread, so add it to the list */
421 statserv_add(NewHandle, new_chan);
423 logf(LOG_DEBUG, "Created new thread, iochan %p", new_chan);
424 iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
428 logf(LOG_FATAL, "Bad event on listener.");
436 /* To save having an #ifdef in event_loop we need to define this empty function */
437 void statserv_remove(IOCHAN pIOChannel)
441 void statserv_closedown()
443 /* We don't need todoanything here - or do we */
444 if (pListener != NULL)
445 iochan_destroy(pListener);
448 static void listener(IOCHAN h, int event)
450 COMSTACK line = (COMSTACK) iochan_getdata(h);
453 static int child = 0;
456 if (event == EVENT_INPUT)
458 if (control_block.dynamic && !child)
464 logf(LOG_FATAL|LOG_ERRNO, "pipe");
468 if ((res = fork()) < 0)
470 logf(LOG_FATAL|LOG_ERRNO, "fork");
474 else if (res == 0) /* child */
481 for (pp = iochan_getchan(); pp; pp = iochan_getnext(pp))
485 COMSTACK l = iochan_getdata(pp);
490 sprintf(nbuf, "%s(%d)", me, getpid());
491 log_init(control_block.loglevel, nbuf, 0);
496 /* wait for child to take the call */
502 if ((res = read(hand[0], dummy, 1)) < 0 && errno != EINTR)
504 logf(LOG_FATAL|LOG_ERRNO, "handshake read");
510 logf(LOG_DEBUG, "P: Child has taken the call");
515 if ((res = cs_listen(line, 0, 0)) < 0)
517 logf(LOG_FATAL, "cs_listen failed.");
522 logf(LOG_DEBUG, "listen ok");
523 iochan_setevent(h, EVENT_OUTPUT);
524 iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
526 /* in dynamic mode, only the child ever comes down here */
527 else if (event == EVENT_OUTPUT)
533 if (!(new_line = cs_accept(line)))
535 logf(LOG_FATAL, "Accept failed.");
536 iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
539 logf(LOG_DEBUG, "accept ok");
540 if (control_block.dynamic)
543 /* close our half of the listener socket */
544 for (pp = iochan_getchan(); pp; pp = iochan_getnext(pp))
546 COMSTACK l = iochan_getdata(pp);
551 logf(LOG_DEBUG, "Releasing parent");
555 iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
557 if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
560 logf(LOG_FATAL, "Failed to create iochan");
564 if (!(newas = create_association(new_chan, new_line)))
566 logf(LOG_FATAL, "Failed to create new assoc.");
570 iochan_setdata(new_chan, newas);
571 iochan_settimeout(new_chan, control_block.idle_timeout * 60);
572 a = cs_addrstr(new_line);
573 logf(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
577 logf(LOG_FATAL, "Bad event on listener.");
585 static void inetd_connection(int what)
592 if ((line = cs_createbysocket(0, tcpip_type, 0, what)))
594 if ((chan = iochan_create(cs_fileno(line), ir_session, EVENT_INPUT)))
596 if ((assoc = create_association(chan, line)))
598 iochan_setdata(chan, assoc);
599 iochan_settimeout(chan, control_block.idle_timeout * 60);
600 addr = cs_addrstr(line);
601 logf(LOG_LOG, "Inetd association from %s", addr ? addr : "[UNKNOWN]");
605 logf(LOG_FATAL, "Failed to create association structure");
610 logf(LOG_FATAL, "Failed to create iochan");
615 logf(LOG_ERRNO|LOG_FATAL, "Failed to create comstack on socket 0");
620 * Set up a listening endpoint, and give it to the event-handler.
622 static void add_listener(char *where, int what)
626 char mode[100], addr[100];
630 if (!where || sscanf(where, "%[^:]:%s", mode, addr) != 2)
632 logf (LOG_WARN, "%s: Address format: ('tcp'|'osi')':'<address>", me);
635 if (!strcmp(mode, "tcp"))
637 else if (!strcmp(mode, "osi"))
642 logf (LOG_WARN, "OSI Transport not allowed by configuration.");
648 logf (LOG_WARN, "You must specify either 'osi:' or 'tcp:'");
651 logf(LOG_LOG, "Adding %s %s listener on %s",
652 control_block.dynamic ? "dynamic" : "static",
653 what == PROTO_SR ? "SR" : "Z3950", where);
654 if (!(l = cs_create(type, 0, what)))
656 logf(LOG_FATAL|LOG_ERRNO, "Failed to create listener");
658 ap = cs_straddr (l, addr);
661 fprintf(stderr, "Address resolution failed.\n");
665 if (cs_bind(l, ap, CS_SERVER) < 0)
667 logf(LOG_FATAL|LOG_ERRNO, "Failed to bind to %s", where);
669 if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
672 logf(LOG_FATAL|LOG_ERRNO, "Failed to create IOCHAN-type");
674 iochan_setdata(lst, l);
676 /* Ensure our listener chain is setup properly */
677 lst->next = pListener;
682 /* For windows we don't need to catch the signals */
683 static void catchchld(int num)
685 while (waitpid(-1, 0, WNOHANG) > 0)
687 signal(SIGCHLD, catchchld);
691 statserv_options_block *statserv_getcontrol(void)
693 static statserv_options_block cb;
695 memcpy(&cb, &control_block, sizeof(cb));
699 void statserv_setcontrol(statserv_options_block *block)
701 memcpy(&control_block, block, sizeof(*block));
704 int statserv_main(int argc, char **argv)
706 int ret, listeners = 0, inetd = 0, r;
708 int protocol = control_block.default_proto;
712 /* We need to initialize the thread list */
713 ThreadList_Initialize();
717 if ((me = strrchr (argv[0], '\\')))
724 while ((ret = options("a:iszSl:v:u:c:w:t:k:", argv, argc, &arg)) != -2)
729 add_listener(arg, protocol);
732 case 'z': protocol = PROTO_Z3950; break;
733 case 's': protocol = PROTO_SR; break;
734 case 'S': control_block.dynamic = 0; break;
736 strcpy(control_block.logfile, arg ? arg : "");
737 log_init(control_block.loglevel, me, control_block.logfile);
740 control_block.loglevel = log_mask_str(arg);
741 log_init(control_block.loglevel, me, control_block.logfile);
744 strcpy(control_block.apdufile, arg ? arg : ""); break;
746 strcpy(control_block.setuid, arg ? arg : ""); break;
748 strcpy(control_block.configname, arg ? arg : ""); break;
750 if (!arg || !(r = atoi(arg)))
752 fprintf(stderr, "%s: Specify positive timeout for -t.\n",
756 control_block.idle_timeout = r;
759 if (!arg || !(r = atoi(arg)))
761 fprintf(stderr, "%s: Specify positive timeout for -t.\n",
765 control_block.maxrecordsize = r * 1024;
778 fprintf(stderr, "Usage: %s [ -i -a <pdufile> -v <loglevel>"
779 " -l <logfile> -u <user> -c <config> -t <minutes>"
781 " -zsS <listener-addr> -w <directory> ... ]\n", me);
786 if ((pListener == NULL) && *control_block.default_listen)
787 add_listener(control_block.default_listen, protocol);
791 inetd_connection(protocol);
794 if (control_block.pre_init)
795 (*control_block.pre_init)(&control_block);
796 if (control_block.dynamic)
797 signal(SIGCHLD, catchchld);
799 if (*control_block.setuid)
803 if (!(pw = getpwnam(control_block.setuid)))
805 logf(LOG_FATAL, "%s: Unknown user", control_block.setuid);
808 if (setuid(pw->pw_uid) < 0)
810 logf(LOG_FATAL|LOG_ERRNO, "setuid");
816 logf(LOG_LOG, "Entering event loop.");
818 if (pListener == NULL)
821 ret = event_loop(pListener);