Added new option '-c' to specify configuration name in control block.
[yaz-moved-to-github.git] / server / statserv.c
1 /*
2  * Copyright (c) 1995, Index Data
3  * See the file LICENSE for details.
4  * Sebastian Hammer, Adam Dickmeiss
5  *
6  * $Log: statserv.c,v $
7  * Revision 1.31  1995-11-17 11:09:39  adam
8  * Added new option '-c' to specify configuration name in control block.
9  *
10  * Revision 1.30  1995/11/01  13:54:59  quinn
11  * Minor adjustments
12  *
13  * Revision 1.29  1995/10/30  12:41:29  quinn
14  * Added hostname lookup for server.
15  *
16  * Revision 1.28  1995/09/29  17:12:30  quinn
17  * Smallish
18  *
19  * Revision 1.27  1995/09/27  15:03:02  quinn
20  * Modified function heads & prototypes.
21  *
22  * Revision 1.26  1995/08/29  14:44:51  quinn
23  * Reset timeouts.
24  *
25  * Revision 1.25  1995/08/29  11:18:02  quinn
26  * Added code to receive close
27  *
28  * Revision 1.24  1995/06/16  10:31:39  quinn
29  * Added session timeout.
30  *
31  * Revision 1.23  1995/06/15  12:30:48  quinn
32  * Setuid-facility.
33  *
34  * Revision 1.22  1995/06/15  07:45:17  quinn
35  * Moving to v3.
36  *
37  * Revision 1.21  1995/06/06  08:15:40  quinn
38  * Cosmetic.
39  *
40  * Revision 1.20  1995/05/29  08:12:09  quinn
41  * Moved oid to util
42  *
43  * Revision 1.19  1995/05/16  09:37:27  quinn
44  * Fixed bug
45  *
46  * Revision 1.18  1995/05/16  08:51:09  quinn
47  * License, documentation, and memory fixes
48  *
49  * Revision 1.17  1995/05/15  11:56:42  quinn
50  * Asynchronous facilities. Restructuring of seshigh code.
51  *
52  * Revision 1.16  1995/04/10  10:23:40  quinn
53  * Some work to add scan and other things.
54  *
55  * Revision 1.15  1995/03/31  10:16:51  quinn
56  * Fixed logging.
57  *
58  * Revision 1.14  1995/03/31  09:18:58  quinn
59  * Added logging.
60  *
61  * Revision 1.13  1995/03/30  16:08:39  quinn
62  * Little mods.
63  *
64  * Revision 1.12  1995/03/30  13:29:02  quinn
65  * Smallish
66  *
67  * Revision 1.11  1995/03/30  12:18:17  quinn
68  * Fixed bug.
69  *
70  * Revision 1.10  1995/03/29  15:40:16  quinn
71  * Ongoing work. Statserv is now dynamic by default
72  *
73  * Revision 1.9  1995/03/27  08:34:30  quinn
74  * Added dynamic server functionality.
75  * Released bindings to session.c (is now redundant)
76  *
77  * Revision 1.8  1995/03/20  09:46:26  quinn
78  * Added osi support.
79  *
80  * Revision 1.7  1995/03/16  13:29:04  quinn
81  * Partitioned server.
82  *
83  * Revision 1.6  1995/03/15  15:18:52  quinn
84  * Little changes to better support nonblocking I/O
85  * Added backend.h
86  *
87  * Revision 1.5  1995/03/15  08:37:45  quinn
88  * Now we're pretty much set for nonblocking I/O.
89  *
90  * Revision 1.4  1995/03/14  16:59:48  quinn
91  * Bug-fixes
92  *
93  * Revision 1.3  1995/03/14  11:30:15  quinn
94  * Works better now.
95  *
96  * Revision 1.2  1995/03/14  10:28:03  quinn
97  * More work on demo server.
98  *
99  * Revision 1.1  1995/03/10  18:22:45  quinn
100  * The rudiments of an asynchronous server.
101  *
102  */
103
104 /*
105  * Simple, static server. I wouldn't advise a static server unless you
106  * really have to, but it's great for debugging memory management.  :)
107  */
108
109 #include <stdio.h>
110 #include <unistd.h>
111 #include <fcntl.h>
112 #include <sys/wait.h>
113 #include <signal.h>
114 #include <errno.h>
115 #include <sys/types.h>
116 #include <pwd.h>
117 #include <sys/time.h>
118
119 #include <options.h>
120 #include <eventl.h>
121 #include <session.h>
122 #include <eventl.h>
123 #include <comstack.h>
124 #include <tcpip.h>
125 #ifdef USE_XTIMOSI
126 #include <xmosi.h>
127 #endif
128 #include <log.h>
129 #include <statserv.h>
130
131 static char *me = "statserver";
132 /*
133  * default behavior.
134  */
135 static statserv_options_block control_block = {
136     1,                          /* dynamic mode */
137     LOG_DEFAULT_LEVEL,          /* log level */
138     "",                         /* no PDUs */
139     "",                         /* diagnostic output to stderr */
140     "tcp:@:9999",               /* default listener port */
141     PROTO_Z3950,                /* default application protocol */
142     60,                         /* idle timeout (minutes) */
143     1024*1024,                  /* maximum PDU size (approx.) to allow */
144     "default-config",           /* configuration name to pass to backend */
145     ""                          /* set user id */
146 };
147
148 /*
149  * handle incoming connect requests.
150  * The dynamic mode is a bit tricky mostly because we want to avoid
151  * doing all of the listening and accepting in the parent - it's
152  * safer that way.
153  */
154 static void listener(IOCHAN h, int event)
155 {
156     COMSTACK line = (COMSTACK) iochan_getdata(h);
157     association *newas;
158     static int hand[2];
159     static int child = 0;
160     int res;
161
162     if (event == EVENT_INPUT)
163     {
164         if (control_block.dynamic && !child) 
165         {
166             int res;
167
168             if (pipe(hand) < 0)
169             {
170                 logf(LOG_FATAL|LOG_ERRNO, "pipe");
171                 exit(1);
172             }
173             if ((res = fork()) < 0)
174             {
175                 logf(LOG_FATAL|LOG_ERRNO, "fork");
176                 exit(1);
177             }
178             else if (res == 0) /* child */
179             {
180                 char nbuf[100];
181
182                 close(hand[0]);
183                 child = 1;
184                 sprintf(nbuf, "%s(%d)", me, getpid());
185                 log_init(control_block.loglevel, nbuf, 0);
186             }
187             else /* parent */
188             {
189                 close(hand[1]);
190                 /* wait for child to take the call */
191                 for (;;)
192                 {
193                     char dummy[1];
194                     int res;
195                     
196                     if ((res = read(hand[0], dummy, 1)) < 0 && errno != EINTR)
197                     {
198                         logf(LOG_FATAL|LOG_ERRNO, "handshake read");
199                         exit(1);
200                     }
201                     else if (res >= 0)
202                         break;
203                 }
204                 logf(LOG_DEBUG, "P: Child has taken the call");
205                 close(hand[0]);
206                 return;
207             }
208         }
209         if ((res = cs_listen(line, 0, 0)) < 0)
210         {
211             logf(LOG_FATAL, "cs_listen failed.");
212             return;
213         }
214         else if (res == 1)
215             return;
216         logf(LOG_DEBUG, "listen ok");
217         iochan_setevent(h, EVENT_OUTPUT);
218         iochan_setflags(h, EVENT_OUTPUT | EVENT_EXCEPT); /* set up for acpt */
219     }
220     /* in dynamic mode, only the child ever comes down here */
221     else if (event == EVENT_OUTPUT)
222     {
223         COMSTACK new_line;
224         IOCHAN new_chan;
225         char *a;
226
227         if (!(new_line = cs_accept(line)))
228         {
229             logf(LOG_FATAL, "Accept failed.");
230             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
231             return;
232         }
233         logf(LOG_DEBUG, "accept ok");
234         if (control_block.dynamic)
235         {
236             IOCHAN pp;
237             /* close our half of the listener sockets */
238             for (pp = iochan_getchan(); pp; pp = iochan_getnext(pp))
239             {
240                 COMSTACK l = iochan_getdata(pp);
241                 cs_close(l);
242                 iochan_destroy(pp);
243             }
244             /* release dad */
245             logf(LOG_DEBUG, "Releasing parent");
246             close(hand[1]);
247         }
248         else
249             iochan_setflags(h, EVENT_INPUT | EVENT_EXCEPT); /* reset listener */
250
251         if (!(new_chan = iochan_create(cs_fileno(new_line), ir_session,
252             EVENT_INPUT)))
253         {
254             logf(LOG_FATAL, "Failed to create iochan");
255             exit(1);
256         }
257         if (!(newas = create_association(new_chan, new_line)))
258         {
259             logf(LOG_FATAL, "Failed to create new assoc.");
260             exit(1);
261         }
262         iochan_setdata(new_chan, newas);
263         iochan_settimeout(new_chan, control_block.idle_timeout * 60);
264         a = cs_addrstr(new_line);
265         logf(LOG_LOG, "Accepted connection from %s", a ? a : "[Unknown]");
266     }
267     else
268     {
269         logf(LOG_FATAL, "Bad event on listener.");
270         exit(1);
271     }
272 }
273
274 /*
275  * Set up a listening endpoint, and give it to the event-handler.
276  */
277 static void add_listener(char *where, int what)
278 {
279     COMSTACK l;
280     CS_TYPE type;
281     char mode[100], addr[100];
282     void *ap;
283     IOCHAN lst;
284
285     if (!where || sscanf(where, "%[^:]:%s", mode, addr) != 2)
286     {
287         fprintf(stderr, "%s: Address format: ('tcp'|'osi')':'<address>.\n",
288             me);
289         exit(1);
290     }
291     if (!strcmp(mode, "tcp"))
292     {
293         if (!(ap = tcpip_strtoaddr(addr)))
294         {
295             fprintf(stderr, "Address resolution failed for TCP.\n");
296             exit(1);
297         }
298         type = tcpip_type;
299     }
300     else if (!strcmp(mode, "osi"))
301     {
302 #ifdef USE_XTIMOSI
303         if (!(ap = mosi_strtoaddr(addr)))
304         {
305             fprintf(stderr, "Address resolution failed for TCP.\n");
306             exit(1);
307         }
308         type = mosi_type;
309 #else
310         fprintf(stderr, "OSI Transport not allowed by configuration.\n");
311         exit(1);
312 #endif
313     }
314     else
315     {
316         fprintf(stderr, "You must specify either 'osi:' or 'tcp:'.\n");
317         exit(1);
318     }
319     logf(LOG_LOG, "Adding %s %s listener on %s",
320         control_block.dynamic ? "dynamic" : "static",
321         what == PROTO_SR ? "SR" : "Z3950", where);
322     if (!(l = cs_create(type, 0, what)))
323     {
324         logf(LOG_FATAL|LOG_ERRNO, "Failed to create listener");
325         exit(1);
326     }
327     if (cs_bind(l, ap, CS_SERVER) < 0)
328     {
329         logf(LOG_FATAL|LOG_ERRNO, "Failed to bind to %s", where);
330         exit(1);
331     }
332     if (!(lst = iochan_create(cs_fileno(l), listener, EVENT_INPUT |
333          EVENT_EXCEPT)))
334     {
335         logf(LOG_FATAL|LOG_ERRNO, "Failed to create IOCHAN-type");
336         exit(1);
337     }
338     iochan_setdata(lst, l);
339 }
340
341 static void catchchld(int num)
342 {
343     while (waitpid(-1, 0, WNOHANG) > 0)
344         ;
345     signal(SIGCHLD, catchchld);
346 }
347
348 statserv_options_block *statserv_getcontrol(void)
349 {
350     static statserv_options_block cb;
351
352     memcpy(&cb, &control_block, sizeof(cb));
353     return &cb;
354 }
355
356 void statserv_setcontrol(statserv_options_block *block)
357 {
358     memcpy(&control_block, block, sizeof(*block));
359 }
360
361 int statserv_main(int argc, char **argv)
362 {
363     int ret, listeners = 0;
364     char *arg;
365     int protocol = control_block.default_proto;
366
367     me = argv[0];
368     while ((ret = options("a:szSl:v:u:c:", argv, argc, &arg)) != -2)
369     {
370         switch (ret)
371         {
372             case 0:
373                 add_listener(arg, protocol);
374                 listeners++;
375                 break;
376             case 'z': protocol = PROTO_Z3950; break;
377             case 's': protocol = PROTO_SR; break;
378             case 'S': control_block.dynamic = 0; break;
379             case 'l':
380                 strcpy(control_block.logfile, arg ? arg : "");
381                 log_init(control_block.loglevel, me, control_block.logfile);
382                 break;
383             case 'v':
384                 control_block.loglevel = log_mask_str(arg);
385                 log_init(control_block.loglevel, me, control_block.logfile);
386                 break;
387             case 'a':
388                 strcpy(control_block.apdufile, arg ? arg : ""); break;
389             case 'u':
390                 strcpy(control_block.setuid, arg ? arg : ""); break;
391             case 'c':
392                 strcpy(control_block.configname, arg ? arg : ""); break;
393             default:
394                 fprintf(stderr, "Usage: %s [ -a <pdufile> -v <loglevel>"
395                         " -l <logfile> -u <user> -c <config>"
396                         " -zsS <listener-addr> ... ]\n", me);
397                 exit(1);
398             }
399     }
400     if (control_block.dynamic)
401         signal(SIGCHLD, catchchld);
402     if (!listeners && *control_block.default_listen)
403         add_listener(control_block.default_listen, protocol);
404     if (*control_block.setuid)
405     {
406         struct passwd *pw;
407         
408         if (!(pw = getpwnam(control_block.setuid)))
409         {
410             logf(LOG_FATAL, "%s: Unknown user", control_block.setuid);
411             exit(1);
412         }
413         if (setuid(pw->pw_uid) < 0)
414         {
415             logf(LOG_FATAL|LOG_ERRNO, "setuid");
416             exit(1);
417         }
418     }
419     logf(LOG_LOG, "Entering event loop.");
420             
421     return event_loop();
422 }