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