Minor changes to admin client.
[yaz-moved-to-github.git] / server / statserv.c
1 /*
2  * Copyright (c) 1995-1999, Index Data
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * NT server based on threads by
7  *   Chas Woodfield, Fretwell Downing Datasystems.
8  *
9  * $Log: statserv.c,v $
10  * Revision 1.62  2000-03-17 12:47:02  adam
11  * Minor changes to admin client.
12  *
13  * Revision 1.61  2000/03/15 12:59:49  adam
14  * Added handle member to statserv_control.
15  *
16  * Revision 1.60  2000/03/14 09:06:11  adam
17  * Added POSIX threads support for frontend server.
18  *
19  * Revision 1.59  1999/11/30 13:47:12  adam
20  * Improved installation. Moved header files to include/yaz.
21  *
22  * Revision 1.58  1999/08/27 09:40:32  adam
23  * Renamed logf function to yaz_log. Removed VC++ project files.
24  *
25  * Revision 1.57  1999/07/06 12:17:15  adam
26  * Added option -1 that runs server once (for profiling purposes).
27  *
28  * Revision 1.56  1999/06/10 11:45:30  adam
29  * Added bend_start, bend_stop handlers and removed pre_init.
30  * Handlers bend_start/bend_stop are called when service/daemon is
31  * started/stopped.
32  *
33  * Revision 1.55  1999/06/10 09:18:54  adam
34  * Modified so that pre_init is called when service/server is started.
35  *
36  * Revision 1.54  1999/04/16 14:45:55  adam
37  * Added interface for tcpd wrapper for access control.
38  *
39  * Revision 1.53  1999/02/02 13:57:39  adam
40  * Uses preprocessor define WIN32 instead of WINDOWS to build code
41  * for Microsoft WIN32.
42  *
43  * Revision 1.52  1998/08/21 14:13:34  adam
44  * Added GNU Configure script to build Makefiles.
45  *
46  * Revision 1.51  1998/07/07 15:51:03  adam
47  * Changed server so that it stops if bind fails - "address already in
48  * use" typically causes this.
49  *
50  * Revision 1.50  1998/06/22 11:32:39  adam
51  * Added 'conditional cs_listen' feature.
52  *
53  * Revision 1.49  1998/02/27 14:04:55  adam
54  * Fixed bug in statserv_remove.
55  *
56  * Revision 1.48  1998/02/11 11:53:36  adam
57  * Changed code so that it compiles as C++.
58  *
59  * Revision 1.47  1998/02/10 10:28:57  adam
60  * Added app_name, service_dependencies, service_display_name and
61  * options_func. options_func allows us to specify a different function
62  * to interogate the command line arguments. The other members allow us
63  * to pass the full service details accross to the service manager (CW).
64  *
65  *
66  * Revision 1.46  1998/01/30 15:24:57  adam
67  * Fixed bug in inetd code. The server listened on tcp:@:9999 even
68  * though it was started in inetd mode.
69  *
70  * Revision 1.45  1998/01/29 13:30:23  adam
71  * Better event handle system for NT/Unix.
72  *
73  * Revision 1.44  1997/11/07 13:31:52  adam
74  * Added NT Service name part of statserv_options_block. Moved NT
75  * service utility to server library.
76  *
77  * Revision 1.43  1997/10/31 12:20:09  adam
78  * Improved memory debugging for xmalloc/nmem.c. References to NMEM
79  * instead of ODR in n ESPEC-1 handling in source d1_espec.c.
80  * Bug fix: missing fclose in data1_read_espec1.
81  *
82  * Revision 1.42  1997/10/27 14:03:02  adam
83  * Added new member to statserver_options_block, pre_init, which
84  * specifies a callback to be invoked after command line parsing and
85  * before the server listens for the first time.
86  *
87  * Revision 1.41  1997/09/29 07:19:32  adam
88  * Server library uses nmem_init/nmem_exit. The log prefix no longer
89  * includes leading path on NT.
90  *
91  * Revision 1.40  1997/09/17 12:10:41  adam
92  * YAZ version 1.4.
93  *
94  * Revision 1.39  1997/09/09 10:10:19  adam
95  * Another MSV5.0 port. Changed projects to include proper
96  * library/include paths.
97  * Server starts server in test-mode when no options are given.
98  *
99  * Revision 1.38  1997/09/04 14:19:14  adam
100  * Added credits.
101  *
102  * Revision 1.37  1997/09/01 08:53:01  adam
103  * New windows NT/95 port using MSV5.0. The test server 'ztest' was
104  * moved a separate directory. MSV5.0 project server.dsp created.
105  * As an option, the server can now operate as an NT service.
106  *
107  * Revision 1.36  1996/07/06 19:58:36  quinn
108  * System headerfiles gathered in yconfig
109  *
110  * Revision 1.35  1996/05/29  10:03:28  quinn
111  * Options work
112  *
113  * Revision 1.34  1996/02/21  13:12:07  quinn
114  * *** empty log message ***
115  *
116  * Revision 1.33  1996/02/10  12:23:49  quinn
117  * Enable inetd operations fro TCP/IP stack
118  *
119  * Revision 1.32  1996/01/19  15:41:52  quinn
120  * *** empty log message ***
121  *
122  * Revision 1.31  1995/11/17  11:09:39  adam
123  * Added new option '-c' to specify configuration name in control block.
124  *
125  * Revision 1.30  1995/11/01  13:54:59  quinn
126  * Minor adjustments
127  *
128  * Revision 1.29  1995/10/30  12:41:29  quinn
129  * Added hostname lookup for server.
130  *
131  * Revision 1.28  1995/09/29  17:12:30  quinn
132  * Smallish
133  *
134  * Revision 1.27  1995/09/27  15:03:02  quinn
135  * Modified function heads & prototypes.
136  *
137  * Revision 1.26  1995/08/29  14:44:51  quinn
138  * Reset timeouts.
139  *
140  * Revision 1.25  1995/08/29  11:18:02  quinn
141  * Added code to receive close
142  *
143  * Revision 1.24  1995/06/16  10:31:39  quinn
144  * Added session timeout.
145  *
146  * Revision 1.23  1995/06/15  12:30:48  quinn
147  * Setuid-facility.
148  *
149  * Revision 1.22  1995/06/15  07:45:17  quinn
150  * Moving to v3.
151  *
152  * Revision 1.21  1995/06/06  08:15:40  quinn
153  * Cosmetic.
154  *
155  * Revision 1.20  1995/05/29  08:12:09  quinn
156  * Moved oid to util
157  *
158  * Revision 1.19  1995/05/16  09:37:27  quinn
159  * Fixed bug
160  *
161  * Revision 1.18  1995/05/16  08:51:09  quinn
162  * License, documentation, and memory fixes
163  *
164  * Revision 1.17  1995/05/15  11:56:42  quinn
165  * Asynchronous facilities. Restructuring of seshigh code.
166  *
167  * Revision 1.16  1995/04/10  10:23:40  quinn
168  * Some work to add scan and other things.
169  *
170  * Revision 1.15  1995/03/31  10:16:51  quinn
171  * Fixed logging.
172  *
173  * Revision 1.14  1995/03/31  09:18:58  quinn
174  * Added logging.
175  *
176  * Revision 1.13  1995/03/30  16:08:39  quinn
177  * Little mods.
178  *
179  * Revision 1.12  1995/03/30  13:29:02  quinn
180  * Smallish
181  *
182  * Revision 1.11  1995/03/30  12:18:17  quinn
183  * Fixed bug.
184  *
185  * Revision 1.10  1995/03/29  15:40:16  quinn
186  * Ongoing work. Statserv is now dynamic by default
187  *
188  * Revision 1.9  1995/03/27  08:34:30  quinn
189  * Added dynamic server functionality.
190  * Released bindings to session.c (is now redundant)
191  *
192  * Revision 1.8  1995/03/20  09:46:26  quinn
193  * Added osi support.
194  *
195  * Revision 1.7  1995/03/16  13:29:04  quinn
196  * Partitioned server.
197  *
198  * Revision 1.6  1995/03/15  15:18:52  quinn
199  * Little changes to better support nonblocking I/O
200  * Added backend.h
201  *
202  * Revision 1.5  1995/03/15  08:37:45  quinn
203  * Now we're pretty much set for nonblocking I/O.
204  *
205  * Revision 1.4  1995/03/14  16:59:48  quinn
206  * Bug-fixes
207  *
208  * Revision 1.3  1995/03/14  11:30:15  quinn
209  * Works better now.
210  *
211  * Revision 1.2  1995/03/14  10:28:03  quinn
212  * More work on demo server.
213  *
214  * Revision 1.1  1995/03/10  18:22:45  quinn
215  * The rudiments of an asynchronous server.
216  *
217  */
218
219 #include <stdio.h>
220 #include <string.h>
221 #ifdef WIN32
222 #include <process.h>
223 #include <winsock.h>
224 #include <direct.h>
225 #include "service.h"
226 #else
227 #if HAVE_PTHREAD_H
228 #include <pthread.h>
229 #endif
230 #include <unistd.h>
231 #include <pwd.h>
232 #endif
233 #include <fcntl.h>
234 #include <signal.h>
235 #include <errno.h>
236
237 #include <yaz/comstack.h>
238 #include <yaz/tcpip.h>
239 #include <yaz/options.h>
240 #ifdef USE_XTIMOSI
241 #include <yaz/xmosi.h>
242 #endif
243 #include <yaz/log.h>
244 #include "eventl.h"
245 #include "session.h"
246 #include <yaz/statserv.h>
247
248 static IOCHAN pListener = NULL;
249
250 static char *me = "statserver";
251 /*
252  * default behavior.
253  */
254 int check_options(int argc, char **argv);
255 statserv_options_block control_block = {
256     1,                          /* dynamic mode */
257     0,                          /* threaded mode */
258     0,                          /* one shot (single session) */
259     LOG_DEFAULT_LEVEL,          /* log level */
260     "",                         /* no PDUs */
261     "",                         /* diagnostic output to stderr */
262     "tcp:@:9999",               /* default listener port */
263     PROTO_Z3950,                /* default application protocol */
264     60,                         /* idle timeout (minutes) */
265     1024*1024,                  /* maximum PDU size (approx.) to allow */
266     "default-config",           /* configuration name to pass to backend */
267     "",                         /* set user id */
268     0,                          /* bend_start handler */
269     0,                          /* bend_stop handler */
270     check_options,              /* Default routine, for checking the run-time arguments */
271     check_ip_tcpd,
272     "",
273     0,                          /* default value for inet deamon */
274     0,                          /* handle (for service, etc) */
275
276 #ifdef WIN32
277     ,"Z39.50 Server",           /* NT Service Name */
278     "Server",                   /* NT application Name */
279     "",                         /* NT Service Dependencies */
280     "Z39.50 Server"             /* NT Service Display Name */
281 #endif /* WIN32 */
282 };
283
284 /*
285  * handle incoming connect requests.
286  * The dynamic mode is a bit tricky mostly because we want to avoid
287  * doing all of the listening and accepting in the parent - it's
288  * safer that way.
289  */
290 #ifdef WIN32
291
292 typedef struct _ThreadList ThreadList;
293
294 struct _ThreadList
295 {
296     HANDLE hThread;
297     IOCHAN pIOChannel;
298     ThreadList *pNext;
299 };
300
301 static ThreadList *pFirstThread;
302 static CRITICAL_SECTION Thread_CritSect;
303 static BOOL bInitialized = FALSE;
304
305 static void ThreadList_Initialize()
306 {
307     /* Initialize the critical Sections */
308     InitializeCriticalSection(&Thread_CritSect);
309
310      /* Set the first thraed */
311     pFirstThread = NULL;
312
313     /* we have been initialized */
314     bInitialized = TRUE;
315 }
316
317 static void statserv_add(HANDLE hThread, IOCHAN pIOChannel)
318 {
319     /* Only one thread can go through this section at a time */
320     EnterCriticalSection(&Thread_CritSect);
321
322     {
323         /* Lets create our new object */
324         ThreadList *pNewThread = (ThreadList *)malloc(sizeof(ThreadList));
325         pNewThread->hThread = hThread;
326         pNewThread->pIOChannel = pIOChannel;
327         pNewThread->pNext = pFirstThread;
328         pFirstThread = pNewThread;
329
330         /* Lets let somebody else create a new object now */
331         LeaveCriticalSection(&Thread_CritSect);
332     }
333 }
334
335 void statserv_remove(IOCHAN pIOChannel)
336 {
337     /* Only one thread can go through this section at a time */
338     EnterCriticalSection(&Thread_CritSect);
339
340     {
341         ThreadList *pCurrentThread = pFirstThread;
342         ThreadList *pNextThread;
343         ThreadList *pPrevThread =NULL;
344
345         /* Step through alll the threads */
346         for (; pCurrentThread != NULL; pCurrentThread = pNextThread)
347         {
348             /* We only need to compare on the IO Channel */
349             if (pCurrentThread->pIOChannel == pIOChannel)
350             {
351                 /* We have found the thread we want to delete */
352                 /* First of all reset the next pointers */
353                 if (pPrevThread == NULL)
354                     pFirstThread = pCurrentThread->pNext;
355                 else
356                     pPrevThread->pNext = pCurrentThread->pNext;
357
358                 /* All we need todo now is delete the memory */
359                 free(pCurrentThread);
360
361                 /* No need to look at any more threads */
362                 pNextThread = NULL;
363             }
364             else
365             {
366                 /* We need to look at another thread */
367                 pNextThread = pCurrentThread->pNext;
368                 pPrevThread = pCurrentThread;
369             }
370         }
371
372         /* Lets let somebody else remove an object now */
373         LeaveCriticalSection(&Thread_CritSect);
374     }
375 }
376
377 void statserv_closedown()
378 {
379     /* Shouldn't do anything if we are not initialized */
380     if (bInitialized)
381     {
382         int iHandles = 0;
383         HANDLE *pThreadHandles = NULL;
384
385         /* We need to stop threads adding and removing while we */
386         /* start the closedown process */
387         EnterCriticalSection(&Thread_CritSect);
388
389         {
390             /* We have exclusive access to the thread stuff now */
391             /* Y didn't i use a semaphore - Oh well never mind */
392             ThreadList *pCurrentThread = pFirstThread;
393
394             /* Before we do anything else, we need to shutdown the listener */
395             if (pListener != NULL)
396                 iochan_destroy(pListener);
397
398             for (; pCurrentThread != NULL; pCurrentThread = pCurrentThread->pNext)
399             {
400                 /* Just destroy the IOCHAN, that should do the trick */
401                 iochan_destroy(pCurrentThread->pIOChannel);
402
403                 /* Keep a running count of our handles */
404                 iHandles++;
405             }
406
407             if (iHandles > 0)
408             {
409                 HANDLE *pCurrentHandle ;
410
411                 /* Allocate the thread handle array */
412                 pThreadHandles = (HANDLE *)malloc(sizeof(HANDLE) * iHandles);
413                 pCurrentHandle = pThreadHandles; 
414
415                 for (pCurrentThread = pFirstThread;
416                      pCurrentThread != NULL;
417                      pCurrentThread = pCurrentThread->pNext, pCurrentHandle++)
418                 {
419                     /* Just the handle */
420                     *pCurrentHandle = pCurrentThread->hThread;
421                 }
422             }
423
424             /* We can now leave the critical section */
425             LeaveCriticalSection(&Thread_CritSect);
426         }
427
428         /* Now we can really do something */
429         if (iHandles > 0)
430         {
431             /* This will now wait, until all the threads close */
432             WaitForMultipleObjects(iHandles, pThreadHandles, TRUE, INFINITE);
433
434             /* Free the memory we allocated for the handle array */
435             free(pThreadHandles);
436         }
437
438         if (control_block.bend_stop)
439             (*control_block.bend_stop)(&control_block);
440         /* No longer require the critical section, since all threads are dead */
441         DeleteCriticalSection(&Thread_CritSect);
442     }
443 }
444
445 int __stdcall event_loop_thread (IOCHAN iochan)
446 {
447     return event_loop (&iochan);
448 }
449
450 static void listener(IOCHAN h, int event)
451 {
452     COMSTACK line = (COMSTACK) iochan_getdata(h);
453     association *newas;
454     int res;
455     HANDLE NewHandle;
456
457     if (event == EVENT_INPUT)
458     {
459         if ((res = cs_listen(line, 0, 0)) < 0)
460         {
461             yaz_log(LOG_FATAL, "cs_listen failed");
462             return;
463         }
464         else if (res == 1)
465             return;
466         yaz_log(LOG_DEBUG, "listen ok");
467         iochan_setevent(h, EVENT_OUTPUT);
468         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
469     }
470     else if (event == EVENT_OUTPUT)
471     {
472         COMSTACK new_line;
473         IOCHAN new_chan;
474         char *a = NULL;
475         DWORD ThreadId;
476
477         if (!(new_line = cs_accept(line)))
478         {
479             yaz_log(LOG_FATAL, "Accept failed.");
480             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
481             return;
482         }
483         yaz_log(LOG_DEBUG, "Accept ok");
484
485         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
486                                        EVENT_INPUT)))
487         {
488             yaz_log(LOG_FATAL, "Failed to create iochan");
489             iochan_destroy(h);
490             return;
491         }
492
493         yaz_log(LOG_DEBUG, "Creating association");
494         if (!(newas = create_association(new_chan, new_line)))
495         {
496             yaz_log(LOG_FATAL, "Failed to create new assoc.");
497             iochan_destroy(h);
498             return;
499         }
500         yaz_log(LOG_DEBUG, "Setting timeout %d", control_block.idle_timeout);
501         iochan_setdata(new_chan, newas);
502         iochan_settimeout(new_chan, control_block.idle_timeout * 60);
503 #ifndef WIN32
504         yaz_log(LOG_DEBUG, "Determining client address");
505         a = cs_addrstr(new_line);
506         yaz_log(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
507 #endif
508         /* Now what we need todo is create a new thread with this iochan as
509            the parameter */
510         /* if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)event_loop_thread,
511            new_chan, 0, &ThreadId) == NULL) */
512         /* Somehow, somewhere we need to store this thread id, otherwise we
513            won't be able to close cleanly */
514         NewHandle = (HANDLE)_beginthreadex(NULL, 0, event_loop_thread,
515                                            new_chan, 0, &ThreadId);
516         if (NewHandle == (HANDLE)-1)
517         {
518             
519             yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to create new thread.");
520             iochan_destroy(h);
521             return;
522         }
523         /* We successfully created the thread, so add it to the list */
524         statserv_add(NewHandle, new_chan);
525
526         yaz_log(LOG_DEBUG, "Created new thread, iochan %p", new_chan);
527         iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
528     }
529     else
530     {
531         yaz_log(LOG_FATAL, "Bad event on listener.");
532         iochan_destroy(h);
533         return;
534     }
535 }
536
537 #else /* WIN32 */
538
539 /* To save having an #ifdef in event_loop we need to define this empty function */
540 void statserv_remove(IOCHAN pIOChannel)
541 {
542 }
543
544 void statserv_closedown()
545 {
546     IOCHAN p;
547
548     if (control_block.bend_stop)
549         (*control_block.bend_stop)(&control_block);
550
551     for (p = pListener; p; p = p->next)
552         iochan_destroy(p);
553 }
554
555 void sigterm(int sig)
556 {
557     statserv_closedown();
558     exit (0);
559 }
560
561 static void *new_session (void *vp);
562
563 static void listener(IOCHAN h, int event)
564 {
565     COMSTACK line = (COMSTACK) iochan_getdata(h);
566     static int hand[2];
567     static int child = 0;
568     int res;
569
570     if (event == EVENT_INPUT)
571     {
572         if (control_block.dynamic && !child) 
573         {
574             int res;
575
576             if (pipe(hand) < 0)
577             {
578                 yaz_log(LOG_FATAL|LOG_ERRNO, "pipe");
579                 iochan_destroy(h);
580                 return;
581             }
582             if ((res = fork()) < 0)
583             {
584                 yaz_log(LOG_FATAL|LOG_ERRNO, "fork");
585                 iochan_destroy(h);
586                 return;
587             }
588             else if (res == 0) /* child */
589             {
590                 char nbuf[100];
591                 IOCHAN pp;
592
593                 close(hand[0]);
594                 child = 1;
595                 for (pp = pListener; pp; pp = iochan_getnext(pp))
596                 {
597                     if (pp != h)
598                     {
599                         COMSTACK l = (COMSTACK)iochan_getdata(pp);
600                         cs_close(l);
601                         iochan_destroy(pp);
602                     }
603                 }
604                 sprintf(nbuf, "%s(%d)", me, getpid());
605                 log_init(control_block.loglevel, nbuf, 0);
606             }
607             else /* parent */
608             {
609                 close(hand[1]);
610                 /* wait for child to take the call */
611                 for (;;)
612                 {
613                     char dummy[1];
614                     int res;
615                     
616                     if ((res = read(hand[0], dummy, 1)) < 0 && errno != EINTR)
617                     {
618                         yaz_log(LOG_FATAL|LOG_ERRNO, "handshake read");
619                         return;
620                     }
621                     else if (res >= 0)
622                         break;
623                 }
624                 yaz_log(LOG_DEBUG, "P: Child has taken the call");
625                 close(hand[0]);
626                 return;
627             }
628         }
629         if ((res = cs_listen_check(line, 0, 0, control_block.check_ip,
630                                    control_block.daemon_name)) < 0)
631         {
632             yaz_log(LOG_WARN, "cs_listen failed");
633             return;
634         }
635         else if (res == 1)
636             return;
637         yaz_log(LOG_DEBUG, "listen ok");
638         iochan_setevent(h, EVENT_OUTPUT);
639         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
640     }
641     /* in dynamic mode, only the child ever comes down here */
642     else if (event == EVENT_OUTPUT)
643     {
644         COMSTACK new_line;
645
646         if (!(new_line = cs_accept(line)))
647         {
648             yaz_log(LOG_FATAL, "Accept failed.");
649             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
650             return;
651         }
652         yaz_log(LOG_DEBUG, "accept ok");
653         if (control_block.dynamic)
654         {
655             IOCHAN pp;
656             /* close our half of the listener socket */
657             for (pp = pListener; pp; pp = iochan_getnext(pp))
658             {
659                 COMSTACK l = (COMSTACK)iochan_getdata(pp);
660                 cs_close(l);
661                 iochan_destroy(pp);
662             }
663             /* release dad */
664             yaz_log(LOG_DEBUG, "Releasing parent");
665             close(hand[1]);
666         }
667         else
668             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
669
670 #if HAVE_PTHREAD_H
671         if (control_block.threads)
672         {
673             pthread_t child_thread;
674             pthread_create (&child_thread, 0, new_session, new_line);
675             pthread_detach (child_thread);
676         }
677         else
678             new_session(new_line);
679 #else
680         new_session(new_line);
681 #endif
682     }
683     else
684     {
685         yaz_log(LOG_FATAL, "Bad event on listener.");
686         iochan_destroy(h);
687         return;
688     }
689 }
690
691 static void *new_session (void *vp)
692 {
693     char *a;
694     association *newas;
695     IOCHAN new_chan;
696     COMSTACK new_line = (COMSTACK) vp;
697     if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
698                                    EVENT_INPUT)))
699     {
700         yaz_log(LOG_FATAL, "Failed to create iochan");
701         return 0;
702     }
703     if (!(newas = create_association(new_chan, new_line)))
704     {
705         yaz_log(LOG_FATAL, "Failed to create new assoc.");
706         return 0;
707     }
708     iochan_setdata(new_chan, newas);
709     iochan_settimeout(new_chan, control_block.idle_timeout * 60);
710     a = cs_addrstr(new_line);
711     yaz_log(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
712     if (control_block.threads)
713     {
714         event_loop(&new_chan);
715     }
716     else
717     {
718         new_chan->next = pListener;
719         pListener = new_chan;
720     }
721     return 0;
722 }
723
724 #endif /* WIN32 */
725
726 static void inetd_connection(int what)
727 {
728     COMSTACK line;
729     IOCHAN chan;
730     association *assoc;
731     char *addr;
732
733     if ((line = cs_createbysocket(0, tcpip_type, 0, what)))
734     {
735         if ((chan = iochan_create(cs_fileno(line), ir_session, EVENT_INPUT)))
736         {
737             if ((assoc = create_association(chan, line)))
738             {
739                 iochan_setdata(chan, assoc);
740                 iochan_settimeout(chan, control_block.idle_timeout * 60);
741                 addr = cs_addrstr(line);
742                 yaz_log(LOG_LOG, "Inetd association from %s", addr ? addr : "[UNKNOWN]");
743             }
744             else
745             {
746                 yaz_log(LOG_FATAL, "Failed to create association structure");
747             }
748             chan->next = pListener;
749             pListener = chan;
750         }
751         else
752         {
753             yaz_log(LOG_FATAL, "Failed to create iochan");
754         }
755     }
756     else
757     {
758         yaz_log(LOG_ERRNO|LOG_FATAL, "Failed to create comstack on socket 0");
759     }
760 }
761
762 /*
763  * Set up a listening endpoint, and give it to the event-handler.
764  */
765 static void add_listener(char *where, int what)
766 {
767     COMSTACK l;
768     CS_TYPE type;
769     char mode[100], addr[100];
770     void *ap;
771     IOCHAN lst = NULL;
772
773     if (!where || sscanf(where, "%[^:]:%s", mode, addr) != 2)
774     {
775         yaz_log (LOG_WARN, "%s: Address format: ('tcp'|'osi')':'<address>", me);
776         return;
777     }
778     if (!strcmp(mode, "tcp"))
779         type = tcpip_type;
780     else if (!strcmp(mode, "osi"))
781     {
782 #ifdef USE_XTIMOSI
783         type = mosi_type;
784 #else
785         yaz_log (LOG_WARN, "OSI Transport not allowed by configuration.");
786         return;
787 #endif
788     }
789     else
790     {
791         yaz_log (LOG_WARN, "You must specify either 'osi:' or 'tcp:'");
792         return;
793     }
794     yaz_log(LOG_LOG, "Adding %s %s listener on %s",
795             control_block.dynamic ? "dynamic" : 
796             (control_block.threads ? "threaded" : "static"),
797         what == PROTO_SR ? "SR" : "Z3950", where);
798     if (!(l = cs_create(type, 0, what)))
799     {
800         yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to create listener");
801         return;
802     }
803     ap = cs_straddr (l, addr);
804     if (!ap)
805     {
806         fprintf(stderr, "Address resolution failed.\n");
807         cs_close (l);
808         return;
809     }
810     if (cs_bind(l, ap, CS_SERVER) < 0)
811     {
812         yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to bind to %s", where);
813         cs_close (l);
814         return;
815     }
816     if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
817          EVENT_EXCEPT)))
818     {
819         yaz_log(LOG_FATAL|LOG_ERRNO, "Failed to create IOCHAN-type");
820         cs_close (l);
821         return;
822     }
823     iochan_setdata(lst, l);
824
825     /* Ensure our listener chain is setup properly */
826     lst->next = pListener;
827     pListener = lst;
828 }
829
830 #ifndef WIN32
831 /* For windows we don't need to catch the signals */
832 static void catchchld(int num)
833 {
834     while (waitpid(-1, 0, WNOHANG) > 0)
835         ;
836     signal(SIGCHLD, catchchld);
837 }
838 #endif /* WIN32 */
839
840 statserv_options_block *statserv_getcontrol(void)
841 {
842     static statserv_options_block cb;
843
844     memcpy(&cb, &control_block, sizeof(cb));
845     return &cb;
846 }
847
848 void statserv_setcontrol(statserv_options_block *block)
849 {
850     memcpy(&control_block, block, sizeof(*block));
851 }
852
853 int statserv_start(int argc, char **argv)
854 {
855     int ret;
856
857     nmem_init ();
858 #ifdef WIN32
859     /* We need to initialize the thread list */
860     ThreadList_Initialize();
861 #endif /* WIN32 */
862
863 #ifdef WIN32
864     if ((me = strrchr (argv[0], '\\')))
865         me++;
866     else
867         me = argv[0];
868     logf (LOG_LOG, "Starting server %s", me);
869 #else
870     me = argv[0];
871     logf (LOG_LOG, "Starting server %s pid=%d", me, getpid());
872 #endif
873     if (control_block.options_func(argc, argv))
874         return(1);
875
876     if (control_block.bend_start)
877         (*control_block.bend_start)(&control_block);
878 #ifndef WIN32
879     if (control_block.inetd)
880         inetd_connection(control_block.default_proto);
881     else
882     {
883         if (control_block.dynamic)
884             signal(SIGCHLD, catchchld);
885     }
886     signal (SIGTERM, sigterm);
887     if (*control_block.setuid)
888     {
889         struct passwd *pw;
890         
891         if (!(pw = getpwnam(control_block.setuid)))
892         {
893             yaz_log(LOG_FATAL, "%s: Unknown user", control_block.setuid);
894             return(1);
895         }
896         if (setuid(pw->pw_uid) < 0)
897         {
898             yaz_log(LOG_FATAL|LOG_ERRNO, "setuid");
899             exit(1);
900         }
901     }
902 #endif /* WIN32 */
903
904     if ((pListener == NULL) && *control_block.default_listen)
905         add_listener(control_block.default_listen,
906                      control_block.default_proto);
907         
908     if (pListener == NULL)
909         ret = 1;
910     else
911     {
912         yaz_log(LOG_LOG, "Entering event loop.");
913         ret = event_loop(&pListener);
914     }
915     nmem_exit ();
916     return ret;
917 }
918
919 int check_options(int argc, char **argv)
920 {
921     int ret = 0, r;
922     char *arg;
923
924     while ((ret = options("1a:iszSTl:v:u:c:w:t:k:d:", argv, argc, &arg)) != -2)
925     {
926         switch (ret)
927         {
928         case 0:
929             add_listener(arg, control_block.default_proto);
930             break;
931         case '1':        
932             control_block.one_shot = 1;
933             control_block.dynamic = 0;
934             break;
935         case 'z':
936             control_block.default_proto = PROTO_Z3950;
937             break;
938         case 's':
939             control_block.default_proto = PROTO_SR;
940             break;
941         case 'S':
942             control_block.dynamic = 0;
943             break;
944         case 'T':
945 #if HAVE_PTHREAD_H
946             control_block.dynamic = 0;
947             control_block.threads = 1;
948 #else
949             fprintf(stderr, "%s: Threaded mode not available.\n", me);
950             return 1;
951 #endif
952             break;
953         case 'l':
954             strcpy(control_block.logfile, arg ? arg : "");
955             log_init(control_block.loglevel, me, control_block.logfile);
956             break;
957         case 'v':
958             control_block.loglevel = log_mask_str(arg);
959             log_init(control_block.loglevel, me, control_block.logfile);
960             break;
961         case 'a':
962             strcpy(control_block.apdufile, arg ? arg : "");
963             break;
964         case 'u':
965             strcpy(control_block.setuid, arg ? arg : "");
966             break;
967         case 'c':
968             strcpy(control_block.configname, arg ? arg : "");
969             break;
970         case 'd':
971             strcpy(control_block.daemon_name, arg ? arg : "");
972             break;
973         case 't':
974             if (!arg || !(r = atoi(arg)))
975             {
976                 fprintf(stderr, "%s: Specify positive timeout for -t.\n", me);
977                 return(1);
978             }
979             control_block.idle_timeout = r;
980             break;
981         case  'k':
982             if (!arg || !(r = atoi(arg)))
983             {
984                 fprintf(stderr, "%s: Specify positive size for -k.\n", me);
985                 return(1);
986             }
987             control_block.maxrecordsize = r * 1024;
988             break;
989         case 'i':
990             control_block.inetd = 1;
991             break;
992         case 'w':
993             if (chdir(arg))
994             {
995                 perror(arg);            
996                 return 1;
997             }
998             break;
999         default:
1000             fprintf(stderr, "Usage: %s [ -a <pdufile> -v <loglevel>"
1001                     " -l <logfile> -u <user> -c <config> -t <minutes>"
1002                     " -k <kilobytes> -d <daemon>"
1003                         " -zsiST -w <directory> <listender-addr>... ]\n", me);
1004             return 1;
1005         }
1006     }
1007     return 0;
1008 }
1009
1010 #ifdef WIN32
1011 typedef struct _Args
1012 {
1013     char **argv;
1014     int argc;
1015 } Args; 
1016
1017 static Args ArgDetails;
1018
1019 /* name of the executable */
1020 #define SZAPPNAME            "server"
1021
1022 /* list of service dependencies - "dep1\0dep2\0\0" */
1023 #define SZDEPENDENCIES       ""
1024
1025 int statserv_main(int argc, char **argv)
1026 {
1027     statserv_options_block *cb = statserv_getcontrol();
1028     
1029     /* Lets setup the Arg structure */
1030     ArgDetails.argc = argc;
1031     ArgDetails.argv = argv;
1032     
1033     /* Now setup the service with the service controller */
1034     SetupService(argc, argv, &ArgDetails, SZAPPNAME,
1035                  cb->service_name, /* internal service name */
1036                  cb->service_name, /* displayed name of the service */
1037                  SZDEPENDENCIES);
1038     return 0;
1039 }
1040
1041 int StartAppService(void *pHandle, int argc, char **argv)
1042 {
1043     /* Initializes the App */
1044     return 1;
1045 }
1046
1047 void RunAppService(void *pHandle)
1048 {
1049     Args *pArgs = (Args *)pHandle;
1050     
1051     /* Starts the app running */
1052     statserv_start(pArgs->argc, pArgs->argv);
1053 }
1054
1055 void StopAppService(void *pHandle)
1056 {
1057     /* Stops the app */
1058     statserv_closedown();
1059 }
1060 #else
1061 int statserv_main(int argc, char **argv)
1062 {
1063     int ret = statserv_start (argc, argv);
1064     statserv_closedown ();
1065     return ret;
1066 }
1067 #endif