2 * Copyright (c) 1995-1998, 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 Datasystems.
10 * Revision 1.51 1998-07-07 15:51:03 adam
11 * Changed server so that it stops if bind fails - "address already in
12 * use" typically causes this.
14 * Revision 1.50 1998/06/22 11:32:39 adam
15 * Added 'conditional cs_listen' feature.
17 * Revision 1.49 1998/02/27 14:04:55 adam
18 * Fixed bug in statserv_remove.
20 * Revision 1.48 1998/02/11 11:53:36 adam
21 * Changed code so that it compiles as C++.
23 * Revision 1.47 1998/02/10 10:28:57 adam
24 * Added app_name, service_dependencies, service_display_name and
25 * options_func. options_func allows us to specify a different function
26 * to interogate the command line arguments. The other members allow us
27 * to pass the full service details accross to the service manager (CW).
30 * Revision 1.46 1998/01/30 15:24:57 adam
31 * Fixed bug in inetd code. The server listened on tcp:@:9999 even
32 * though it was started in inetd mode.
34 * Revision 1.45 1998/01/29 13:30:23 adam
35 * Better event handle system for NT/Unix.
37 * Revision 1.44 1997/11/07 13:31:52 adam
38 * Added NT Service name part of statserv_options_block. Moved NT
39 * service utility to server library.
41 * Revision 1.43 1997/10/31 12:20:09 adam
42 * Improved memory debugging for xmalloc/nmem.c. References to NMEM
43 * instead of ODR in n ESPEC-1 handling in source d1_espec.c.
44 * Bug fix: missing fclose in data1_read_espec1.
46 * Revision 1.42 1997/10/27 14:03:02 adam
47 * Added new member to statserver_options_block, pre_init, which
48 * specifies a callback to be invoked after command line parsing and
49 * before the server listens for the first time.
51 * Revision 1.41 1997/09/29 07:19:32 adam
52 * Server library uses nmem_init/nmem_exit. The log prefix no longer
53 * includes leading path on NT.
55 * Revision 1.40 1997/09/17 12:10:41 adam
58 * Revision 1.39 1997/09/09 10:10:19 adam
59 * Another MSV5.0 port. Changed projects to include proper
60 * library/include paths.
61 * Server starts server in test-mode when no options are given.
63 * Revision 1.38 1997/09/04 14:19:14 adam
66 * Revision 1.37 1997/09/01 08:53:01 adam
67 * New windows NT/95 port using MSV5.0. The test server 'ztest' was
68 * moved a separate directory. MSV5.0 project server.dsp created.
69 * As an option, the server can now operate as an NT service.
71 * Revision 1.36 1996/07/06 19:58:36 quinn
72 * System headerfiles gathered in yconfig
74 * Revision 1.35 1996/05/29 10:03:28 quinn
77 * Revision 1.34 1996/02/21 13:12:07 quinn
78 * *** empty log message ***
80 * Revision 1.33 1996/02/10 12:23:49 quinn
81 * Enable inetd operations fro TCP/IP stack
83 * Revision 1.32 1996/01/19 15:41:52 quinn
84 * *** empty log message ***
86 * Revision 1.31 1995/11/17 11:09:39 adam
87 * Added new option '-c' to specify configuration name in control block.
89 * Revision 1.30 1995/11/01 13:54:59 quinn
92 * Revision 1.29 1995/10/30 12:41:29 quinn
93 * Added hostname lookup for server.
95 * Revision 1.28 1995/09/29 17:12:30 quinn
98 * Revision 1.27 1995/09/27 15:03:02 quinn
99 * Modified function heads & prototypes.
101 * Revision 1.26 1995/08/29 14:44:51 quinn
104 * Revision 1.25 1995/08/29 11:18:02 quinn
105 * Added code to receive close
107 * Revision 1.24 1995/06/16 10:31:39 quinn
108 * Added session timeout.
110 * Revision 1.23 1995/06/15 12:30:48 quinn
113 * Revision 1.22 1995/06/15 07:45:17 quinn
116 * Revision 1.21 1995/06/06 08:15:40 quinn
119 * Revision 1.20 1995/05/29 08:12:09 quinn
122 * Revision 1.19 1995/05/16 09:37:27 quinn
125 * Revision 1.18 1995/05/16 08:51:09 quinn
126 * License, documentation, and memory fixes
128 * Revision 1.17 1995/05/15 11:56:42 quinn
129 * Asynchronous facilities. Restructuring of seshigh code.
131 * Revision 1.16 1995/04/10 10:23:40 quinn
132 * Some work to add scan and other things.
134 * Revision 1.15 1995/03/31 10:16:51 quinn
137 * Revision 1.14 1995/03/31 09:18:58 quinn
140 * Revision 1.13 1995/03/30 16:08:39 quinn
143 * Revision 1.12 1995/03/30 13:29:02 quinn
146 * Revision 1.11 1995/03/30 12:18:17 quinn
149 * Revision 1.10 1995/03/29 15:40:16 quinn
150 * Ongoing work. Statserv is now dynamic by default
152 * Revision 1.9 1995/03/27 08:34:30 quinn
153 * Added dynamic server functionality.
154 * Released bindings to session.c (is now redundant)
156 * Revision 1.8 1995/03/20 09:46:26 quinn
159 * Revision 1.7 1995/03/16 13:29:04 quinn
160 * Partitioned server.
162 * Revision 1.6 1995/03/15 15:18:52 quinn
163 * Little changes to better support nonblocking I/O
166 * Revision 1.5 1995/03/15 08:37:45 quinn
167 * Now we're pretty much set for nonblocking I/O.
169 * Revision 1.4 1995/03/14 16:59:48 quinn
172 * Revision 1.3 1995/03/14 11:30:15 quinn
175 * Revision 1.2 1995/03/14 10:28:03 quinn
176 * More work on demo server.
178 * Revision 1.1 1995/03/10 18:22:45 quinn
179 * The rudiments of an asynchronous server.
200 #include <comstack.h>
208 #include <statserv.h>
210 static IOCHAN pListener = NULL;
212 static char *me = "statserver";
216 int check_options(int argc, char **argv);
217 statserv_options_block control_block = {
218 1, /* dynamic mode */
219 LOG_DEFAULT_LEVEL, /* log level */
221 "", /* diagnostic output to stderr */
222 "tcp:@:9999", /* default listener port */
223 PROTO_Z3950, /* default application protocol */
224 60, /* idle timeout (minutes) */
225 1024*1024, /* maximum PDU size (approx.) to allow */
226 "default-config", /* configuration name to pass to backend */
227 "", /* set user id */
228 NULL, /* pre init handler */
229 check_options, /* Default routine, for checking the run-time arguments */
230 0 /* default value for inet deamon */
233 ,"Z39.50 Server", /* NT Service Name */
234 "Server", /* NT application Name */
235 "", /* NT Service Dependencies */
236 "Z39.50 Server" /* NT Service Display Name */
241 * handle incoming connect requests.
242 * The dynamic mode is a bit tricky mostly because we want to avoid
243 * doing all of the listening and accepting in the parent - it's
248 typedef struct _ThreadList ThreadList;
257 static ThreadList *pFirstThread;
258 static CRITICAL_SECTION Thread_CritSect;
259 static BOOL bInitialized = FALSE;
261 static void ThreadList_Initialize()
263 /* Initialize the critical Sections */
264 InitializeCriticalSection(&Thread_CritSect);
266 /* Set the first thraed */
269 /* we have been initialized */
273 static void statserv_add(HANDLE hThread, IOCHAN pIOChannel)
275 /* Only one thread can go through this section at a time */
276 EnterCriticalSection(&Thread_CritSect);
279 /* Lets create our new object */
280 ThreadList *pNewThread = (ThreadList *)malloc(sizeof(ThreadList));
281 pNewThread->hThread = hThread;
282 pNewThread->pIOChannel = pIOChannel;
283 pNewThread->pNext = pFirstThread;
284 pFirstThread = pNewThread;
286 /* Lets let somebody else create a new object now */
287 LeaveCriticalSection(&Thread_CritSect);
291 void statserv_remove(IOCHAN pIOChannel)
293 /* Only one thread can go through this section at a time */
294 EnterCriticalSection(&Thread_CritSect);
297 ThreadList *pCurrentThread = pFirstThread;
298 ThreadList *pNextThread;
299 ThreadList *pPrevThread =NULL;
301 /* Step through alll the threads */
302 for (; pCurrentThread != NULL; pCurrentThread = pNextThread)
304 /* We only need to compare on the IO Channel */
305 if (pCurrentThread->pIOChannel == pIOChannel)
307 /* We have found the thread we want to delete */
308 /* First of all reset the next pointers */
309 if (pPrevThread == NULL)
310 pFirstThread = pCurrentThread->pNext;
312 pPrevThread->pNext = pCurrentThread->pNext;
314 /* All we need todo now is delete the memory */
315 free(pCurrentThread);
317 /* No need to look at any more threads */
322 /* We need to look at another thread */
323 pNextThread = pCurrentThread->pNext;
324 pPrevThread = pCurrentThread;
328 /* Lets let somebody else remove an object now */
329 LeaveCriticalSection(&Thread_CritSect);
333 void statserv_closedown()
335 /* Shouldn't do anything if we are not initialized */
339 HANDLE *pThreadHandles = NULL;
341 /* We need to stop threads adding and removing while we */
342 /* start the closedown process */
343 EnterCriticalSection(&Thread_CritSect);
346 /* We have exclusive access to the thread stuff now */
347 /* Y didn't i use a semaphore - Oh well never mind */
348 ThreadList *pCurrentThread = pFirstThread;
350 /* Before we do anything else, we need to shutdown the listener */
351 if (pListener != NULL)
352 iochan_destroy(pListener);
354 for (; pCurrentThread != NULL; pCurrentThread = pCurrentThread->pNext)
356 /* Just destroy the IOCHAN, that should do the trick */
357 iochan_destroy(pCurrentThread->pIOChannel);
359 /* Keep a running count of our handles */
365 HANDLE *pCurrentHandle ;
367 /* Allocate the thread handle array */
368 pThreadHandles = (HANDLE *)malloc(sizeof(HANDLE) * iHandles);
369 pCurrentHandle = pThreadHandles;
371 for (pCurrentThread = pFirstThread;
372 pCurrentThread != NULL;
373 pCurrentThread = pCurrentThread->pNext, pCurrentHandle++)
375 /* Just the handle */
376 *pCurrentHandle = pCurrentThread->hThread;
380 /* We can now leave the critical section */
381 LeaveCriticalSection(&Thread_CritSect);
384 /* Now we can really do something */
387 /* This will now wait, until all the threads close */
388 WaitForMultipleObjects(iHandles, pThreadHandles, TRUE, INFINITE);
390 /* Free the memory we allocated for the handle array */
391 free(pThreadHandles);
394 /* No longer require the critical section, since all threads are dead */
395 DeleteCriticalSection(&Thread_CritSect);
399 int __stdcall event_loop_thread (IOCHAN iochan)
401 return event_loop (&iochan);
404 static void listener(IOCHAN h, int event)
406 COMSTACK line = (COMSTACK) iochan_getdata(h);
411 if (event == EVENT_INPUT)
413 if ((res = cs_listen(line, 0, 0)) < 0)
415 logf(LOG_FATAL, "cs_listen failed");
420 logf(LOG_DEBUG, "listen ok");
421 iochan_setevent(h, EVENT_OUTPUT);
422 iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
424 else if (event == EVENT_OUTPUT)
431 if (!(new_line = cs_accept(line)))
433 logf(LOG_FATAL, "Accept failed.");
434 iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
437 logf(LOG_DEBUG, "Accept ok");
439 if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
442 logf(LOG_FATAL, "Failed to create iochan");
447 logf(LOG_DEBUG, "Creating association");
448 if (!(newas = create_association(new_chan, new_line)))
450 logf(LOG_FATAL, "Failed to create new assoc.");
454 logf(LOG_DEBUG, "Setting timeout %d", control_block.idle_timeout);
455 iochan_setdata(new_chan, newas);
456 iochan_settimeout(new_chan, control_block.idle_timeout * 60);
458 logf(LOG_DEBUG, "Determining client address");
459 a = cs_addrstr(new_line);
460 logf(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
462 /* Now what we need todo is create a new thread with this iochan as
464 /* if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)event_loop_thread,
465 new_chan, 0, &ThreadId) == NULL) */
466 /* Somehow, somewhere we need to store this thread id, otherwise we
467 won't be able to close cleanly */
468 NewHandle = (HANDLE)_beginthreadex(NULL, 0, event_loop_thread,
469 new_chan, 0, &ThreadId);
470 if (NewHandle == (HANDLE)-1)
473 logf(LOG_FATAL|LOG_ERRNO, "Failed to create new thread.");
477 /* We successfully created the thread, so add it to the list */
478 statserv_add(NewHandle, new_chan);
480 logf(LOG_DEBUG, "Created new thread, iochan %p", new_chan);
481 iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
485 logf(LOG_FATAL, "Bad event on listener.");
493 /* To save having an #ifdef in event_loop we need to define this empty function */
494 void statserv_remove(IOCHAN pIOChannel)
498 void statserv_closedown()
501 for (p = pListener; p; p = p->next)
505 static int check_ip(void *cd, const char *addr, int len, int type)
507 const unsigned char *ip = (const unsigned char *) addr;
511 sprintf (str, "%u", *ip);
512 for (i = 1; i<4; i++)
513 sprintf (str + strlen(str), ".%u", ip[i]);
514 logf (LOG_DEBUG, "ip %s", str);
518 static void listener(IOCHAN h, int event)
520 COMSTACK line = (COMSTACK) iochan_getdata(h);
523 static int child = 0;
526 if (event == EVENT_INPUT)
528 if (control_block.dynamic && !child)
534 logf(LOG_FATAL|LOG_ERRNO, "pipe");
538 if ((res = fork()) < 0)
540 logf(LOG_FATAL|LOG_ERRNO, "fork");
544 else if (res == 0) /* child */
551 for (pp = pListener; pp; pp = iochan_getnext(pp))
555 COMSTACK l = (COMSTACK)iochan_getdata(pp);
560 sprintf(nbuf, "%s(%d)", me, getpid());
561 log_init(control_block.loglevel, nbuf, 0);
566 /* wait for child to take the call */
572 if ((res = read(hand[0], dummy, 1)) < 0 && errno != EINTR)
574 logf(LOG_FATAL|LOG_ERRNO, "handshake read");
580 logf(LOG_DEBUG, "P: Child has taken the call");
585 if ((res = cs_listen_check(line, 0, 0, check_ip, 0)) < 0)
587 logf(LOG_WARN, "cs_listen failed");
592 logf(LOG_DEBUG, "listen ok");
593 iochan_setevent(h, EVENT_OUTPUT);
594 iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
596 /* in dynamic mode, only the child ever comes down here */
597 else if (event == EVENT_OUTPUT)
603 if (!(new_line = cs_accept(line)))
605 logf(LOG_FATAL, "Accept failed.");
606 iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
609 logf(LOG_DEBUG, "accept ok");
610 if (control_block.dynamic)
613 /* close our half of the listener socket */
614 for (pp = pListener; pp; pp = iochan_getnext(pp))
616 COMSTACK l = (COMSTACK)iochan_getdata(pp);
621 logf(LOG_DEBUG, "Releasing parent");
625 iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
627 if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
630 logf(LOG_FATAL, "Failed to create iochan");
634 new_chan->next = pListener;
635 pListener = new_chan;
636 if (!(newas = create_association(new_chan, new_line)))
638 logf(LOG_FATAL, "Failed to create new assoc.");
642 iochan_setdata(new_chan, newas);
643 iochan_settimeout(new_chan, control_block.idle_timeout * 60);
644 a = cs_addrstr(new_line);
645 logf(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
649 logf(LOG_FATAL, "Bad event on listener.");
657 static void inetd_connection(int what)
664 if ((line = cs_createbysocket(0, tcpip_type, 0, what)))
666 if ((chan = iochan_create(cs_fileno(line), ir_session, EVENT_INPUT)))
668 if ((assoc = create_association(chan, line)))
670 iochan_setdata(chan, assoc);
671 iochan_settimeout(chan, control_block.idle_timeout * 60);
672 addr = cs_addrstr(line);
673 logf(LOG_LOG, "Inetd association from %s", addr ? addr : "[UNKNOWN]");
677 logf(LOG_FATAL, "Failed to create association structure");
679 chan->next = pListener;
684 logf(LOG_FATAL, "Failed to create iochan");
689 logf(LOG_ERRNO|LOG_FATAL, "Failed to create comstack on socket 0");
694 * Set up a listening endpoint, and give it to the event-handler.
696 static void add_listener(char *where, int what)
700 char mode[100], addr[100];
704 if (!where || sscanf(where, "%[^:]:%s", mode, addr) != 2)
706 logf (LOG_WARN, "%s: Address format: ('tcp'|'osi')':'<address>", me);
709 if (!strcmp(mode, "tcp"))
711 else if (!strcmp(mode, "osi"))
716 logf (LOG_WARN, "OSI Transport not allowed by configuration.");
722 logf (LOG_WARN, "You must specify either 'osi:' or 'tcp:'");
725 logf(LOG_LOG, "Adding %s %s listener on %s",
726 control_block.dynamic ? "dynamic" : "static",
727 what == PROTO_SR ? "SR" : "Z3950", where);
728 if (!(l = cs_create(type, 0, what)))
730 logf(LOG_FATAL|LOG_ERRNO, "Failed to create listener");
732 ap = cs_straddr (l, addr);
735 fprintf(stderr, "Address resolution failed.\n");
739 if (cs_bind(l, ap, CS_SERVER) < 0)
741 logf(LOG_FATAL|LOG_ERRNO, "Failed to bind to %s", where);
745 if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
748 logf(LOG_FATAL|LOG_ERRNO, "Failed to create IOCHAN-type");
752 iochan_setdata(lst, l);
754 /* Ensure our listener chain is setup properly */
755 lst->next = pListener;
760 /* For windows we don't need to catch the signals */
761 static void catchchld(int num)
763 while (waitpid(-1, 0, WNOHANG) > 0)
765 signal(SIGCHLD, catchchld);
769 statserv_options_block *statserv_getcontrol(void)
771 static statserv_options_block cb;
773 memcpy(&cb, &control_block, sizeof(cb));
777 void statserv_setcontrol(statserv_options_block *block)
779 memcpy(&control_block, block, sizeof(*block));
782 int statserv_start(int argc, char **argv)
788 /* We need to initialize the thread list */
789 ThreadList_Initialize();
793 if ((me = strrchr (argv[0], '\\')))
800 if (control_block.options_func(argc, argv))
804 if (control_block.inetd)
805 inetd_connection(control_block.default_proto);
808 if (control_block.pre_init)
809 (*control_block.pre_init)(&control_block);
810 if (control_block.dynamic)
811 signal(SIGCHLD, catchchld);
813 if (*control_block.setuid)
817 if (!(pw = getpwnam(control_block.setuid)))
819 logf(LOG_FATAL, "%s: Unknown user", control_block.setuid);
822 if (setuid(pw->pw_uid) < 0)
824 logf(LOG_FATAL|LOG_ERRNO, "setuid");
830 if ((pListener == NULL) && *control_block.default_listen)
831 add_listener(control_block.default_listen,
832 control_block.default_proto);
834 if (pListener == NULL)
838 logf(LOG_LOG, "Entering event loop.");
839 ret = event_loop(&pListener);
845 int check_options(int argc, char **argv)
850 while ((ret = options("a:iszSl:v:u:c:w:t:k:", argv, argc, &arg)) != -2)
855 add_listener(arg, control_block.default_proto);
858 control_block.default_proto = PROTO_Z3950;
861 control_block.default_proto = PROTO_SR;
864 control_block.dynamic = 0;
867 strcpy(control_block.logfile, arg ? arg : "");
868 log_init(control_block.loglevel, me, control_block.logfile);
871 control_block.loglevel = log_mask_str(arg);
872 log_init(control_block.loglevel, me, control_block.logfile);
875 strcpy(control_block.apdufile, arg ? arg : "");
878 strcpy(control_block.setuid, arg ? arg : "");
881 strcpy(control_block.configname, arg ? arg : "");
884 if (!arg || !(r = atoi(arg)))
886 fprintf(stderr, "%s: Specify positive timeout for -t.\n", me);
889 control_block.idle_timeout = r;
892 if (!arg || !(r = atoi(arg)))
894 fprintf(stderr, "%s: Specify positive timeout for -t.\n", me);
897 control_block.maxrecordsize = r * 1024;
900 control_block.inetd = 1;
910 fprintf(stderr, "Usage: %s [ -i -a <pdufile> -v <loglevel>"
911 " -l <logfile> -u <user> -c <config> -t <minutes>"
913 " -zsS <listener-addr> -w <directory> ... ]\n", me);
927 static Args ArgDetails;
929 /* name of the executable */
930 #define SZAPPNAME "server"
932 /* list of service dependencies - "dep1\0dep2\0\0" */
933 #define SZDEPENDENCIES ""
935 int statserv_main(int argc, char **argv)
937 statserv_options_block *cb = statserv_getcontrol();
939 /* Lets setup the Arg structure */
940 ArgDetails.argc = argc;
941 ArgDetails.argv = argv;
943 /* Now setup the service with the service controller */
944 SetupService(argc, argv, &ArgDetails, SZAPPNAME,
945 cb->service_name, /* internal service name */
946 cb->service_name, /* displayed name of the service */
951 int StartAppService(void *pHandle, int argc, char **argv)
953 /* Initializes the App */
957 void RunAppService(void *pHandle)
959 Args *pArgs = (Args *)pHandle;
961 /* Starts the app running */
962 statserv_start(pArgs->argc, pArgs->argv);
965 void StopAppService(void *pHandle)
968 statserv_closedown();
971 int statserv_main(int argc, char **argv)
973 int ret = statserv_start (argc, argv);
974 statserv_closedown ();