Added function, oid_ent_to_oid, to replace the function
[yaz-moved-to-github.git] / server / statserv.c
1 /*
2  * Copyright (c) 1995-1997, 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 Datasystem.
8  *
9  * $Log: statserv.c,v $
10  * Revision 1.41  1997-09-29 07:19:32  adam
11  * Server library uses nmem_init/nmem_exit. The log prefix no longer
12  * includes leading path on NT.
13  *
14  * Revision 1.40  1997/09/17 12:10:41  adam
15  * YAZ version 1.4.
16  *
17  * Revision 1.39  1997/09/09 10:10:19  adam
18  * Another MSV5.0 port. Changed projects to include proper
19  * library/include paths.
20  * Server starts server in test-mode when no options are given.
21  *
22  * Revision 1.38  1997/09/04 14:19:14  adam
23  * Added credits.
24  *
25  * Revision 1.37  1997/09/01 08:53:01  adam
26  * New windows NT/95 port using MSV5.0. The test server 'ztest' was
27  * moved a separate directory. MSV5.0 project server.dsp created.
28  * As an option, the server can now operate as an NT service.
29  *
30  * Revision 1.36  1996/07/06 19:58:36  quinn
31  * System headerfiles gathered in yconfig
32  *
33  * Revision 1.35  1996/05/29  10:03:28  quinn
34  * Options work
35  *
36  * Revision 1.34  1996/02/21  13:12:07  quinn
37  * *** empty log message ***
38  *
39  * Revision 1.33  1996/02/10  12:23:49  quinn
40  * Enable inetd operations fro TCP/IP stack
41  *
42  * Revision 1.32  1996/01/19  15:41:52  quinn
43  * *** empty log message ***
44  *
45  * Revision 1.31  1995/11/17  11:09:39  adam
46  * Added new option '-c' to specify configuration name in control block.
47  *
48  * Revision 1.30  1995/11/01  13:54:59  quinn
49  * Minor adjustments
50  *
51  * Revision 1.29  1995/10/30  12:41:29  quinn
52  * Added hostname lookup for server.
53  *
54  * Revision 1.28  1995/09/29  17:12:30  quinn
55  * Smallish
56  *
57  * Revision 1.27  1995/09/27  15:03:02  quinn
58  * Modified function heads & prototypes.
59  *
60  * Revision 1.26  1995/08/29  14:44:51  quinn
61  * Reset timeouts.
62  *
63  * Revision 1.25  1995/08/29  11:18:02  quinn
64  * Added code to receive close
65  *
66  * Revision 1.24  1995/06/16  10:31:39  quinn
67  * Added session timeout.
68  *
69  * Revision 1.23  1995/06/15  12:30:48  quinn
70  * Setuid-facility.
71  *
72  * Revision 1.22  1995/06/15  07:45:17  quinn
73  * Moving to v3.
74  *
75  * Revision 1.21  1995/06/06  08:15:40  quinn
76  * Cosmetic.
77  *
78  * Revision 1.20  1995/05/29  08:12:09  quinn
79  * Moved oid to util
80  *
81  * Revision 1.19  1995/05/16  09:37:27  quinn
82  * Fixed bug
83  *
84  * Revision 1.18  1995/05/16  08:51:09  quinn
85  * License, documentation, and memory fixes
86  *
87  * Revision 1.17  1995/05/15  11:56:42  quinn
88  * Asynchronous facilities. Restructuring of seshigh code.
89  *
90  * Revision 1.16  1995/04/10  10:23:40  quinn
91  * Some work to add scan and other things.
92  *
93  * Revision 1.15  1995/03/31  10:16:51  quinn
94  * Fixed logging.
95  *
96  * Revision 1.14  1995/03/31  09:18:58  quinn
97  * Added logging.
98  *
99  * Revision 1.13  1995/03/30  16:08:39  quinn
100  * Little mods.
101  *
102  * Revision 1.12  1995/03/30  13:29:02  quinn
103  * Smallish
104  *
105  * Revision 1.11  1995/03/30  12:18:17  quinn
106  * Fixed bug.
107  *
108  * Revision 1.10  1995/03/29  15:40:16  quinn
109  * Ongoing work. Statserv is now dynamic by default
110  *
111  * Revision 1.9  1995/03/27  08:34:30  quinn
112  * Added dynamic server functionality.
113  * Released bindings to session.c (is now redundant)
114  *
115  * Revision 1.8  1995/03/20  09:46:26  quinn
116  * Added osi support.
117  *
118  * Revision 1.7  1995/03/16  13:29:04  quinn
119  * Partitioned server.
120  *
121  * Revision 1.6  1995/03/15  15:18:52  quinn
122  * Little changes to better support nonblocking I/O
123  * Added backend.h
124  *
125  * Revision 1.5  1995/03/15  08:37:45  quinn
126  * Now we're pretty much set for nonblocking I/O.
127  *
128  * Revision 1.4  1995/03/14  16:59:48  quinn
129  * Bug-fixes
130  *
131  * Revision 1.3  1995/03/14  11:30:15  quinn
132  * Works better now.
133  *
134  * Revision 1.2  1995/03/14  10:28:03  quinn
135  * More work on demo server.
136  *
137  * Revision 1.1  1995/03/10  18:22:45  quinn
138  * The rudiments of an asynchronous server.
139  *
140  */
141
142 #include <yconfig.h>
143 #include <stdio.h>
144 #include <string.h>
145 #ifdef WINDOWS
146 #include <process.h>
147 #include <winsock.h>
148 #include <direct.h>
149 #else
150 #include <unistd.h>
151 #include <pwd.h>
152 #endif
153 #include <fcntl.h>
154 #include <signal.h>
155 #include <errno.h>
156
157 #include <options.h>
158 #include "eventl.h"
159 #include "session.h"
160 #include <comstack.h>
161 #include <tcpip.h>
162 #ifdef USE_XTIMOSI
163 #include <xmosi.h>
164 #endif
165 #include <log.h>
166 #include <statserv.h>
167
168 static IOCHAN pListener;
169
170 static char *me = "statserver";
171 /*
172  * default behavior.
173  */
174 static statserv_options_block control_block = {
175     1,                          /* dynamic mode */
176     LOG_DEFAULT_LEVEL,          /* log level */
177     "",                         /* no PDUs */
178     "",                         /* diagnostic output to stderr */
179     "tcp:@:9999",               /* default listener port */
180     PROTO_Z3950,                /* default application protocol */
181     60,                         /* idle timeout (minutes) */
182     1024*1024,                  /* maximum PDU size (approx.) to allow */
183     "default-config",           /* configuration name to pass to backend */
184     ""                          /* set user id */
185 };
186
187 /*
188  * handle incoming connect requests.
189  * The dynamic mode is a bit tricky mostly because we want to avoid
190  * doing all of the listening and accepting in the parent - it's
191  * safer that way.
192  */
193 #ifdef WINDOWS
194
195 typedef struct _ThreadList ThreadList;
196
197 typedef struct _ThreadList
198 {
199     HANDLE hThread;
200     IOCHAN pIOChannel;
201     ThreadList *pNext;
202 } ThreadList;
203
204 static ThreadList *pFirstThread;
205 static CRITICAL_SECTION Thread_CritSect;
206 static BOOL bInitialized = FALSE;
207
208 static void ThreadList_Initialize()
209 {
210     /* Initialize the critical Sections */
211      InitializeCriticalSection(&Thread_CritSect);
212
213      /* Set the first thraed */
214      pFirstThread = NULL;
215
216      /* we have been initialized */
217      bInitialized = TRUE;
218 }
219
220 static void statserv_add(HANDLE hThread, IOCHAN pIOChannel)
221 {
222     /* Only one thread can go through this section at a time */
223     EnterCriticalSection(&Thread_CritSect);
224
225     {
226         /* Lets create our new object */
227         ThreadList *pNewThread = (ThreadList *)malloc(sizeof(ThreadList));
228         pNewThread->hThread = hThread;
229         pNewThread->pIOChannel = pIOChannel;
230         pNewThread->pNext = pFirstThread;
231         pFirstThread = pNewThread;
232
233         /* Lets let somebody else create a new object now */
234         LeaveCriticalSection(&Thread_CritSect);
235     }
236 }
237
238 void statserv_remove(IOCHAN pIOChannel)
239 {
240     /* Only one thread can go through this section at a time */
241     EnterCriticalSection(&Thread_CritSect);
242
243     {
244         ThreadList *pCurrentThread = pFirstThread;
245         ThreadList *pNextThread;
246         ThreadList *pPrevThread =NULL;
247
248         /* Step through alll the threads */
249         for (; pCurrentThread != NULL; pCurrentThread = pNextThread)
250         {
251             /* We only need to compare on the IO Channel */
252             if (pCurrentThread->pIOChannel == pIOChannel)
253             {
254                 /* We have found the thread we want to delete */
255                 /* First of all reset the next pointers */
256                 if (pPrevThread == NULL)
257                     pFirstThread = pCurrentThread->pNext;
258                 else
259                     pPrevThread->pNext = pCurrentThread->pNext;
260
261                 /* All we need todo now is delete the memory */
262                 free(pCurrentThread);
263
264                 /* No need to look at any more threads */
265                 pNextThread = NULL;
266             }
267             else
268             {
269                 /* We need to look at another thread */
270                 pNextThread = pCurrentThread->pNext;
271             }
272         }
273
274         /* Lets let somebody else remove an object now */
275         LeaveCriticalSection(&Thread_CritSect);
276     }
277 }
278
279 void statserv_closedown()
280 {
281     /* Shouldn't do anything if we are not initialized */
282     if (bInitialized)
283     {
284         int iHandles = 0;
285         HANDLE *pThreadHandles = NULL;
286
287         /* We need to stop threads adding and removing while we */
288         /* start the closedown process */
289         EnterCriticalSection(&Thread_CritSect);
290
291         {
292             /* We have exclusive access to the thread stuff now */
293             /* Y didn't i use a semaphore - Oh well never mind */
294             ThreadList *pCurrentThread = pFirstThread;
295
296             /* Before we do anything else, we need to shutdown the listener */
297             if (pListener != NULL)
298                 iochan_destroy(pListener);
299
300             for (; pCurrentThread != NULL; pCurrentThread = pCurrentThread->pNext)
301             {
302                 /* Just destroy the IOCHAN, that should do the trick */
303                 iochan_destroy(pCurrentThread->pIOChannel);
304
305                 /* Keep a running count of our handles */
306                 iHandles++;
307             }
308
309             if (iHandles > 0)
310             {
311                 HANDLE *pCurrentHandle ;
312
313                 /* Allocate the thread handle array */
314                 pThreadHandles = (HANDLE *)malloc(sizeof(HANDLE) * iHandles);
315                 pCurrentHandle = pThreadHandles; 
316
317                 for (pCurrentThread = pFirstThread;
318                      pCurrentThread != NULL;
319                      pCurrentThread = pCurrentThread->pNext, pCurrentHandle++)
320                 {
321                     /* Just the handle */
322                     *pCurrentHandle = pCurrentThread->hThread;
323                 }
324             }
325
326             /* We can now leave the critical section */
327             LeaveCriticalSection(&Thread_CritSect);
328         }
329
330         /* Now we can really do something */
331         if (iHandles > 0)
332         {
333             /* This will now wait, until all the threads close */
334             WaitForMultipleObjects(iHandles, pThreadHandles, TRUE, INFINITE);
335
336             /* Free the memory we allocated for the handle array */
337             free(pThreadHandles);
338         }
339
340         /* No longer require the critical section, since all threads are dead */
341         DeleteCriticalSection(&Thread_CritSect);
342     }
343 }
344
345 static void listener(IOCHAN h, int event)
346 {
347     COMSTACK line = (COMSTACK) iochan_getdata(h);
348     association *newas;
349     int res;
350     HANDLE NewHandle;
351
352     if (event == EVENT_INPUT)
353     {
354         if ((res = cs_listen(line, 0, 0)) < 0)
355         {
356             logf(LOG_FATAL, "cs_listen failed.");
357             return;
358         }
359         else if (res == 1)
360             return;
361         logf(LOG_DEBUG, "listen ok");
362         iochan_setevent(h, EVENT_OUTPUT);
363         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
364     }
365     else if (event == EVENT_OUTPUT)
366     {
367         COMSTACK new_line;
368         IOCHAN new_chan;
369         char *a = NULL;
370         DWORD ThreadId;
371
372         if (!(new_line = cs_accept(line)))
373         {
374             logf(LOG_FATAL, "Accept failed.");
375             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
376             return;
377         }
378         logf(LOG_DEBUG, "Accept ok");
379
380         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session, EVENT_INPUT)))
381         {
382             logf(LOG_FATAL, "Failed to create iochan");
383             iochan_destroy(h);
384             return;
385         }
386
387         logf(LOG_DEBUG, "Creating association");
388         if (!(newas = create_association(new_chan, new_line)))
389         {
390             logf(LOG_FATAL, "Failed to create new assoc.");
391             iochan_destroy(h);
392             return;
393         }
394         logf(LOG_DEBUG, "Setting timeout %d", control_block.idle_timeout);
395         iochan_setdata(new_chan, newas);
396         iochan_settimeout(new_chan, control_block.idle_timeout * 60);
397         logf(LOG_DEBUG, "Determining client address");
398         a = cs_addrstr(new_line);
399         logf(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
400     /* Now what we need todo is create a new thread with this iochan as
401        the parameter */
402     /* if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)event_loop, new_chan,
403            0, &ThreadId) == NULL) */
404     /* Somehow, somewhere we need to store this thread id, otherwise we won't be
405        able to close cleanly */
406         NewHandle = (HANDLE)_beginthreadex(NULL, 0, event_loop, new_chan, 0, &ThreadId);
407         if (NewHandle == (HANDLE)-1)
408         {
409
410             logf(LOG_FATAL|LOG_ERRNO, "Failed to create new thread.");
411             iochan_destroy(h);
412             return;
413         }
414         /* We successfully created the thread, so add it to the list */
415         statserv_add(NewHandle, new_chan);
416
417         logf(LOG_DEBUG, "Created new thread, iochan %p", new_chan);
418         iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
419     }
420     else
421     {
422         logf(LOG_FATAL, "Bad event on listener.");
423         iochan_destroy(h);
424         return;
425     }
426 }
427
428 #else /* WINDOWS */
429
430 /* To save having an #ifdef in event_loop we need to define this empty function */
431 void statserv_remove(IOCHAN pIOChannel)
432 {
433 }
434
435 void statserv_closedown()
436 {
437     /* We don't need todoanything here - or do we */
438     if (pListener != NULL)
439         iochan_destroy(pListener);
440 }
441
442 static void listener(IOCHAN h, int event)
443 {
444     COMSTACK line = (COMSTACK) iochan_getdata(h);
445     association *newas;
446     static int hand[2];
447     static int child = 0;
448     int res;
449
450     if (event == EVENT_INPUT)
451     {
452         if (control_block.dynamic && !child) 
453         {
454             int res;
455
456             if (pipe(hand) < 0)
457             {
458                 logf(LOG_FATAL|LOG_ERRNO, "pipe");
459                 iochan_destroy(h);
460                 return;
461             }
462             if ((res = fork()) < 0)
463             {
464                 logf(LOG_FATAL|LOG_ERRNO, "fork");
465                 iochan_destroy(h);
466                 return;
467             }
468             else if (res == 0) /* child */
469             {
470                 char nbuf[100];
471                 IOCHAN pp;
472
473                 close(hand[0]);
474                 child = 1;
475                 for (pp = iochan_getchan(); pp; pp = iochan_getnext(pp))
476                 {
477                     if (pp != h)
478                     {
479                         COMSTACK l = iochan_getdata(pp);
480                         cs_close(l);
481                         iochan_destroy(pp);
482                     }
483                 }
484                 sprintf(nbuf, "%s(%d)", me, getpid());
485                 log_init(control_block.loglevel, nbuf, 0);
486             }
487             else /* parent */
488             {
489                 close(hand[1]);
490                 /* wait for child to take the call */
491                 for (;;)
492                 {
493                     char dummy[1];
494                     int res;
495                     
496                     if ((res = read(hand[0], dummy, 1)) < 0 && errno != EINTR)
497                     {
498                         logf(LOG_FATAL|LOG_ERRNO, "handshake read");
499                         return;
500                     }
501                     else if (res >= 0)
502                         break;
503                 }
504                 logf(LOG_DEBUG, "P: Child has taken the call");
505                 close(hand[0]);
506                 return;
507             }
508         }
509         if ((res = cs_listen(line, 0, 0)) < 0)
510         {
511             logf(LOG_FATAL, "cs_listen failed.");
512             return;
513         }
514         else if (res == 1)
515             return;
516         logf(LOG_DEBUG, "listen ok");
517         iochan_setevent(h, EVENT_OUTPUT);
518         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
519     }
520     /* in dynamic mode, only the child ever comes down here */
521     else if (event == EVENT_OUTPUT)
522     {
523         COMSTACK new_line;
524         IOCHAN new_chan;
525         char *a;
526
527         if (!(new_line = cs_accept(line)))
528         {
529             logf(LOG_FATAL, "Accept failed.");
530             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
531             return;
532         }
533         logf(LOG_DEBUG, "accept ok");
534         if (control_block.dynamic)
535         {
536             IOCHAN pp;
537             /* close our half of the listener socket */
538             for (pp = iochan_getchan(); pp; pp = iochan_getnext(pp))
539             {
540                 COMSTACK l = iochan_getdata(pp);
541                 cs_close(l);
542                 iochan_destroy(pp);
543             }
544             /* release dad */
545             logf(LOG_DEBUG, "Releasing parent");
546             close(hand[1]);
547         }
548         else
549             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
550
551         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
552             EVENT_INPUT)))
553         {
554             logf(LOG_FATAL, "Failed to create iochan");
555             iochan_destroy(h);
556             return;
557         }
558         if (!(newas = create_association(new_chan, new_line)))
559         {
560             logf(LOG_FATAL, "Failed to create new assoc.");
561             iochan_destroy(h);
562             return;
563         }
564         iochan_setdata(new_chan, newas);
565         iochan_settimeout(new_chan, control_block.idle_timeout * 60);
566         a = cs_addrstr(new_line);
567         logf(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
568     }
569     else
570     {
571         logf(LOG_FATAL, "Bad event on listener.");
572         iochan_destroy(h);
573         return;
574     }
575 }
576
577 #endif /* WINDOWS */
578
579 static void inetd_connection(int what)
580 {
581     COMSTACK line;
582     IOCHAN chan;
583     association *assoc;
584     char *addr;
585
586     if ((line = cs_createbysocket(0, tcpip_type, 0, what)))
587     {
588         if ((chan = iochan_create(cs_fileno(line), ir_session, EVENT_INPUT)))
589         {
590             if ((assoc = create_association(chan, line)))
591             {
592                 iochan_setdata(chan, assoc);
593                 iochan_settimeout(chan, control_block.idle_timeout * 60);
594                 addr = cs_addrstr(line);
595                 logf(LOG_LOG, "Inetd association from %s", addr ? addr : "[UNKNOWN]");
596             }
597             else
598             {
599                 logf(LOG_FATAL, "Failed to create association structure");
600             }
601         }
602         else
603         {
604             logf(LOG_FATAL, "Failed to create iochan");
605         }
606     }
607     else
608     {
609         logf(LOG_ERRNO|LOG_FATAL, "Failed to create comstack on socket 0");
610     }
611 }
612
613 /*
614  * Set up a listening endpoint, and give it to the event-handler.
615  */
616 static void add_listener(char *where, int what)
617 {
618     COMSTACK l;
619     CS_TYPE type;
620     char mode[100], addr[100];
621     void *ap;
622     IOCHAN lst = NULL;
623
624     if (!where || sscanf(where, "%[^:]:%s", mode, addr) != 2)
625     {
626         logf (LOG_WARN, "%s: Address format: ('tcp'|'osi')':'<address>", me);
627         return;
628     }
629     if (!strcmp(mode, "tcp"))
630         type = tcpip_type;
631     else if (!strcmp(mode, "osi"))
632     {
633 #ifdef USE_XTIMOSI
634         type = mosi_type;
635 #else
636         logf (LOG_WARN, "OSI Transport not allowed by configuration.");
637         return;
638 #endif
639     }
640     else
641     {
642         logf (LOG_WARN, "You must specify either 'osi:' or 'tcp:'");
643         return;
644     }
645     logf(LOG_LOG, "Adding %s %s listener on %s",
646         control_block.dynamic ? "dynamic" : "static",
647         what == PROTO_SR ? "SR" : "Z3950", where);
648     if (!(l = cs_create(type, 0, what)))
649     {
650         logf(LOG_FATAL|LOG_ERRNO, "Failed to create listener");
651     }
652     ap = cs_straddr (l, addr);
653     if (!ap)
654     {
655         fprintf(stderr, "Address resolution failed.\n");
656         cs_close (l);
657         return;
658     }
659     if (cs_bind(l, ap, CS_SERVER) < 0)
660     {
661         logf(LOG_FATAL|LOG_ERRNO, "Failed to bind to %s", where);
662     }
663     if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
664          EVENT_EXCEPT)))
665     {
666         logf(LOG_FATAL|LOG_ERRNO, "Failed to create IOCHAN-type");
667     }
668     iochan_setdata(lst, l);
669
670     /* Ensure our listener chain is setup properly */
671     lst->next = pListener;
672     pListener = lst;
673 }
674
675 #ifndef WINDOWS
676 /* For windows we don't need to catch the signals */
677 static void catchchld(int num)
678 {
679     while (waitpid(-1, 0, WNOHANG) > 0)
680         ;
681     signal(SIGCHLD, catchchld);
682 }
683 #endif /* WINDOWS */
684
685 statserv_options_block *statserv_getcontrol(void)
686 {
687     static statserv_options_block cb;
688
689     memcpy(&cb, &control_block, sizeof(cb));
690     return &cb;
691 }
692
693 void statserv_setcontrol(statserv_options_block *block)
694 {
695     memcpy(&control_block, block, sizeof(*block));
696 }
697
698 int statserv_main(int argc, char **argv)
699 {
700     int ret, listeners = 0, inetd = 0, r;
701     char *arg;
702     int protocol = control_block.default_proto;
703
704     nmem_init ();
705 #ifdef WINDOWS
706     /* We need to initialize the thread list */
707     ThreadList_Initialize();
708 #endif /* WINDOWS */
709
710 #ifdef WINDOWS
711     if ((me = strrchr (argv[0], '\\')))
712         me++;
713     else
714         me = argv[0];
715 #else
716     me = argv[0];
717 #endif
718     while ((ret = options("a:iszSl:v:u:c:w:t:k:", argv, argc, &arg)) != -2)
719     {
720         switch (ret)
721         {
722             case 0:
723                     add_listener(arg, protocol);
724                     listeners++;
725                 break;
726             case 'z': protocol = PROTO_Z3950; break;
727             case 's': protocol = PROTO_SR; break;
728             case 'S': control_block.dynamic = 0; break;
729             case 'l':
730                 strcpy(control_block.logfile, arg ? arg : "");
731                 log_init(control_block.loglevel, me, control_block.logfile);
732                 break;
733             case 'v':
734                 control_block.loglevel = log_mask_str(arg);
735                 log_init(control_block.loglevel, me, control_block.logfile);
736                 break;
737             case 'a':
738                 strcpy(control_block.apdufile, arg ? arg : ""); break;
739             case 'u':
740                 strcpy(control_block.setuid, arg ? arg : ""); break;
741             case 'c':
742                 strcpy(control_block.configname, arg ? arg : ""); break;
743             case 't':
744                 if (!arg || !(r = atoi(arg)))
745                 {
746                     fprintf(stderr, "%s: Specify positive timeout for -t.\n",
747                         me);
748                     return(1);
749                 }
750                 control_block.idle_timeout = r;
751                 break;
752             case  'k':
753                 if (!arg || !(r = atoi(arg)))
754                 {
755                     fprintf(stderr, "%s: Specify positive timeout for -t.\n",
756                         me);
757                     return(1);
758                 }
759                 control_block.maxrecordsize = r * 1024;
760                 break;
761             case 'i':
762                 inetd = 1; break;
763             case 'w':
764                 if (chdir(arg))
765                 {
766                     perror(arg);
767
768                     return(1);
769                 }
770                 break;
771             default:
772                 fprintf(stderr, "Usage: %s [ -i -a <pdufile> -v <loglevel>"
773                         " -l <logfile> -u <user> -c <config> -t <minutes>"
774                         " -k <kilobytes>"
775                         " -zsS <listener-addr> -w <directory> ... ]\n", me);
776                 return(1);
777             }
778     }
779
780 #if 0
781     log_init(control_block.loglevel, NULL, control_block.logfile);
782 #endif /* WINDOWS */
783
784     if ((pListener == NULL) && *control_block.default_listen)
785             add_listener(control_block.default_listen, protocol);
786
787 #ifndef WINDOWS
788     if (inetd)
789         inetd_connection(protocol);
790     else
791     {
792         if (control_block.dynamic)
793             signal(SIGCHLD, catchchld);
794     }
795     if (*control_block.setuid)
796     {
797         struct passwd *pw;
798         
799         if (!(pw = getpwnam(control_block.setuid)))
800         {
801             logf(LOG_FATAL, "%s: Unknown user", control_block.setuid);
802             return(1);
803         }
804         if (setuid(pw->pw_uid) < 0)
805         {
806             logf(LOG_FATAL|LOG_ERRNO, "setuid");
807             exit(1);
808         }
809     }
810 #endif /* WINDOWS */
811
812     logf(LOG_LOG, "Entering event loop.");
813         
814     if (pListener == NULL)
815         ret = 1;
816     else
817         ret = event_loop(pListener);
818     nmem_exit ();
819     return ret;
820 }