Updated for YAZ 1.7. HTML output tidy up. Added LOC target.
[egate.git] / kernel / main.c
1 /*
2  * Copyright (c) 1995, the EUROPAGATE consortium (see below).
3  *
4  * The EUROPAGATE consortium members are:
5  *
6  *    University College Dublin
7  *    Danmarks Teknologiske Videnscenter
8  *    An Chomhairle Leabharlanna
9  *    Consejo Superior de Investigaciones Cientificas
10  *
11  * Permission to use, copy, modify, distribute, and sell this software and
12  * its documentation, in whole or in part, for any purpose, is hereby granted,
13  * provided that:
14  *
15  * 1. This copyright and permission notice appear in all copies of the
16  * software and its documentation. Notices of copyright or attribution
17  * which appear at the beginning of any file must remain unchanged.
18  *
19  * 2. The names of EUROPAGATE or the project partners may not be used to
20  * endorse or promote products derived from this software without specific
21  * prior written permission.
22  *
23  * 3. Users of this software (implementors and gateway operators) agree to
24  * inform the EUROPAGATE consortium of their use of the software. This
25  * information will be used to evaluate the EUROPAGATE project and the
26  * software, and to plan further developments. The consortium may use
27  * the information in later publications.
28  * 
29  * 4. Users of this software agree to make their best efforts, when
30  * documenting their use of the software, to acknowledge the EUROPAGATE
31  * consortium, and the role played by the software in their work.
32  *
33  * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND,
34  * EXPRESS, IMPLIED, OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
35  * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
36  * IN NO EVENT SHALL THE EUROPAGATE CONSORTIUM OR ITS MEMBERS BE LIABLE
37  * FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF
38  * ANY KIND, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
39  * OR PROFITS, WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND
40  * ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
41  * USE OR PERFORMANCE OF THIS SOFTWARE.
42  *
43  */
44 /* Gateway kernel - Main
45  * Europagate, 1995
46  *
47  * $Log: main.c,v $
48  * Revision 1.31  2001/02/26 14:32:36  adam
49  * Updated for YAZ 1.7. HTML output tidy up. Added LOC target.
50  *
51  * Revision 1.30  1996/02/12 10:04:54  adam
52  * The gateway doesn't try to reconnect if it is already known that
53  * it will fail (connect_fail flag introduced).
54  *
55  * Revision 1.29  1995/07/28  10:51:08  adam
56  * Bug fix: account from previous session had effect when new target command
57  * was executed.
58  *
59  * Revision 1.28  1995/07/03  08:20:15  adam
60  * More help information and better diagnostics.
61  *
62  * Revision 1.27  1995/05/19  13:25:59  adam
63  * Bug fixes. Better command line options.
64  *
65  * Revision 1.26  1995/05/18  12:03:08  adam
66  * Bug fixes and minor improvements.
67  *
68  * Revision 1.25  1995/05/16  09:40:42  adam
69  * LICENSE. Setting of CCL token names (and/or/not/set) in read_kernel_res.
70  *
71  * Revision 1.24  1995/05/04  10:40:07  adam
72  * More work on Def-settings.
73  *
74  * Revision 1.23  1995/05/03  16:34:18  adam
75  * CCL def command, i.e. user definitions - saved as resource files.
76  *
77  * Revision 1.22  1995/05/03  07:37:39  adam
78  * CCL commands stop/continue implemented. New functions gw_res_{int,bool}
79  * are used when possible.
80  *
81  * Revision 1.21  1995/05/01  16:26:56  adam
82  * More work on resource monitor.
83  *
84  * Revision 1.20  1995/05/01  12:43:32  adam
85  * First work on resource monitor program.
86  *
87  * Revision 1.19  1995/04/19  16:01:58  adam
88  * Some hacks to get the FIFO communication work!! Isn't reliable.
89  * Resource gw.account added - default account info.
90  *
91  * Revision 1.18  1995/04/19  13:19:09  adam
92  * New command: account - for authentication.
93  *
94  * Revision 1.17  1995/04/19  10:46:18  adam
95  * Persistency works much better now. New command: status - history-like
96  *
97  * Revision 1.16  1995/04/19  07:31:07  adam
98  * First work on Z39.50 persistence.
99  *
100  * Revision 1.15  1995/04/17  09:34:30  adam
101  * Timeout (idletime) adjustable. Minor changes in kernel.
102  *
103  * Revision 1.14  1995/03/28  11:42:34  adam
104  * First use of string-queue utility.
105  *
106  * Revision 1.13  1995/03/28  08:01:25  adam
107  * FIFO existence is used to test for a running kernel.
108  *
109  * Revision 1.12  1995/03/27  12:51:05  adam
110  * New log level in use: GW_LOG_ERRNO.
111  *
112  * Revision 1.11  1995/03/27  08:24:02  adam
113  * First use of gip interface and gw-db.
114  * First work on eti program.
115  *
116  * Revision 1.10  1995/03/01  14:32:25  adam
117  * Better diagnostics. Default is, that only one database selected when
118  * several are known.
119  *
120  * Revision 1.9  1995/02/23  08:32:17  adam
121  * Changed header.
122  *
123  * Revision 1.7  1995/02/22  15:22:33  adam
124  * Much more checking of run-time state. Show command never retrieves
125  * more records than indicated by the previous search request. Help
126  * command available. The maximum number of records retrieved can be
127  * controlled now.
128  *
129  * Revision 1.6  1995/02/22  08:51:34  adam
130  * Output function can be customized in fml, which is used to print
131  * the reply to reply_fd.
132  *
133  * Revision 1.5  1995/02/20  21:16:20  adam
134  * FML support. Bug fixes. Profile for drewdb.
135  *
136  * Revision 1.4  1995/02/17  17:06:16  adam
137  * Minor changes.
138  *
139  * Revision 1.3  1995/02/16  18:35:09  adam
140  * First use of Zdist library. Search requests are supported.
141  * Present requests are not supported yet.
142  *
143  * Revision 1.2  1995/02/16  13:21:00  adam
144  * Organization of resource files for targets and conversion
145  * language implemented.
146  *
147  * Revision 1.1  1995/02/15  17:45:29  adam
148  * First version of email gateway kernel. Email requests are read
149  * from stdin. The output is transferred to an MTA if 'From' is
150  * found in the header - or stdout if absent. No Z39.50 client is used.
151  *
152  */
153
154 #include <stdio.h>
155 #include <stdlib.h>
156 #include <string.h>
157 #include <assert.h>
158 #include <unistd.h>
159 #include <sys/types.h>
160 #include <sys/time.h>
161 #include <fcntl.h>
162
163 #include <gip.h>
164 #include <strqueue.h>
165 #include "kernel.h"
166
167 FILE *reply_fd;
168
169 struct gw_kernel_info info;
170
171 static void kernel_events (struct str_queue *queue)
172 {
173     char fifo_client_name[1024];
174     char fifo_server_name[1024];
175     char line_buf[1024];
176     GIP gip;
177     fd_set set_r;
178     int r, gip_fd;
179     struct timeval tv;
180     int timeout;
181     int continuation = 0;
182     int extra_fd;
183     int persist_flag;
184     int stop_flag = 0;
185
186     reply_fd = stdout;
187
188     persist_flag = gw_res_bool (info.kernel_res, "gw.persist", 0);
189     timeout = gw_res_int (info.kernel_res, "gw.timeout", 600);
190     gw_log (GW_LOG_DEBUG, KERNEL_LOG, "event loop");
191
192     sprintf (fifo_client_name, "fifo.c.%d", info.userid);
193     sprintf (fifo_server_name, "fifo.s.%d", info.userid);
194
195     gip = gips_initialize (fifo_server_name);
196     gips_open (gip, fifo_client_name, 1);
197     gip_fd = gip_infileno (gip);
198     extra_fd = open (fifo_server_name, O_WRONLY);
199
200     while (1)
201     {
202         FD_ZERO (&set_r);
203         FD_SET (gip_fd, &set_r);
204         if (stop_flag)
205             tv.tv_sec = 1;
206         else
207             tv.tv_sec = timeout;
208         tv.tv_usec = 0;
209
210         gw_log (GW_LOG_DEBUG, KERNEL_LOG, "IPC select");
211         r = select (gip_fd+1, &set_r, NULL, NULL, &tv);
212         if (r == -1)
213         {
214             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, KERNEL_LOG, "select");
215             exit (1);
216         }
217         if (r == 0)
218         {
219             if (stop_flag)
220                 gw_log (GW_LOG_STAT, KERNEL_LOG, "Stopping");
221             else
222                 gw_log (GW_LOG_STAT, KERNEL_LOG, "Timeout after %d seconds", 
223                         timeout);
224             if (info.zass && persist_flag)
225                 save_p_state (info.userid);
226             break;
227         }
228         if (FD_ISSET (gip_fd, &set_r))
229         {
230             char command[128], *cp;
231
232             if (!(lgets (command, 127, gip_fd)))
233             {
234                 gw_log (GW_LOG_WARN, KERNEL_LOG, "Unexpected close");
235                 break;
236             }
237             if ((cp = strchr (command, '\n')))
238                 *cp = '\0';
239             gw_log (GW_LOG_STAT, KERNEL_LOG, "IPC: %s", command);
240             if (!strcmp (command, "mail"))
241             {
242                 gw_log (GW_LOG_DEBUG, KERNEL_LOG, "Incoming mail");
243                 while (lgets (line_buf, sizeof(line_buf)-1, gip_fd))
244                     str_queue_enq (queue, line_buf);
245                 urp_start (continuation, queue);
246                 if (persist_flag && !continuation)
247                     load_p_state (info.userid); 
248                 r = urp_command (queue);
249                 if (r == 1)                         /* stop? */
250                 {
251                     info.zass = NULL;               /* delete association */
252                     *info.target = 0;               /* indicate no target */
253                     read_kernel_res();              /* reread resources */
254                     if (persist_flag)
255                         del_p_state (info.userid);  /* remove persist file */
256                 }
257                 urp_end ();
258                 while (str_queue_deq (queue, 0, 0))
259                     ;
260             }
261             else if (!strcmp (command, "stop"))
262             {
263                 gw_log (GW_LOG_DEBUG, KERNEL_LOG, "stop");
264                 while (lgets (line_buf, sizeof(line_buf)-1, gip_fd))
265                     ;
266                 break;
267             }
268             else 
269                 gw_log (GW_LOG_WARN, KERNEL_LOG, "Unknown IPC: %s", command);
270             continuation = 1;
271         }
272     }
273     close (extra_fd);
274     gips_close (gip);
275     gips_destroy (gip);
276 }
277
278 int main (int argc, char **argv)
279 {
280     struct str_queue *queue;
281
282     info.kernel_res = NULL;
283     info.default_res = "default.res";
284     info.override_res = NULL;
285     *info.target = 0;
286     *info.account = 0;
287     info.account_in_session = 1;
288     info.lang = NULL;
289     info.bibset = NULL;
290     info.zass = NULL;
291     info.override_portno = NULL;
292     info.override_hostname = NULL;
293     info.databases = NULL;
294     info.database = NULL;
295     info.setno = -1;
296     info.userid = -1;
297     info.connect_failed = 0;
298 #if USE_FML
299     info.fml = NULL;
300 #endif
301     info.sets = NULL;
302
303     gw_log_init (*argv);
304     info.kernel_res = gw_res_init ();
305     while (--argc > 0)
306     {
307         if (**++argv == '-')
308         {
309             switch (argv[0][1])
310             {
311             case 'H':
312                 fprintf (stderr, "kernel [options] [resourceFile]\n");
313                 fprintf (stderr, "If no resource file is specified");
314                 fprintf (stderr, " default.res is used\n");
315                 fprintf (stderr, "Options:\n");
316                 fprintf (stderr, " -d           Enable debugging log\n");
317                 fprintf (stderr, " -t target    Open target immediately\n");
318                 fprintf (stderr, " -g lang      Set language\n");
319                 fprintf (stderr, " -o resource  Override with resource\n");
320                 fprintf (stderr, " -h host      Override host\n");
321                 fprintf (stderr, " -p port      Override port\n");
322                 fprintf (stderr, " -l log       Set Log file\n");
323                 fprintf (stderr, " -i id        Set IPC userid\n");
324                 exit (1);
325             case 'd':
326                 gw_log_level (GW_LOG_ALL & ~RES_DEBUG);
327                 break;
328             case 'D':
329                 gw_log_level (GW_LOG_ALL);
330                 break;
331             case 't':
332                 if (argv[0][2])
333                     strcpy (info.target, argv[0]+2);
334                 else if (argc > 0)
335                 {
336                     --argc;
337                     strcpy (info.target, *++argv);
338                 }
339                 else
340                 {
341                     gw_log (GW_LOG_FATAL, KERNEL_LOG, "missing target name");
342                     exit (1);
343                 }
344                 break;
345             case 'g':
346                 if (argv[0][2])
347                     info.lang = argv[0]+2;
348                 else if (argc > 0)
349                 {
350                     --argc;
351                     info.lang = *++argv;
352                 }
353                 else
354                 {
355                     gw_log (GW_LOG_FATAL, KERNEL_LOG, "missing language name");
356                     exit (1);
357                 }
358                 break;
359             case 'o':
360                 if (argv[0][2])
361                     info.override_res = argv[0]+2;
362                 else if (argc > 0)
363                 {
364                     --argc;
365                     info.override_res = *++argv;
366                 }
367                 else
368                 {
369                     gw_log (GW_LOG_FATAL, KERNEL_LOG, "missing override name");
370                     exit (1);
371                 }
372                 break;
373             case 'p':
374                 if (argv[0][2])
375                     info.override_portno = argv[0]+2;
376                 else if (argc > 0)
377                 {
378                     --argc;
379                     info.override_portno = *++argv;
380                 }
381                 else
382                 {
383                     gw_log (GW_LOG_FATAL, KERNEL_LOG, "missing portno");
384                     exit (1);
385                 }
386                 break;
387             case 'h':
388                 if (argv[0][2])
389                     info.override_hostname = argv[0]+2;
390                 else if (argc > 0)
391                 {
392                     --argc;
393                     info.override_hostname = *++argv;
394                 }
395                 else
396                 {
397                     gw_log (GW_LOG_FATAL, KERNEL_LOG, "missing hostname");
398                     exit (1);
399                 }
400                 break;
401             case 'l':
402                 if (argv[0][2])
403                     gw_log_file (GW_LOG_ALL, argv[0]+2);
404                 else if (argc > 0)
405                 {
406                     --argc;
407                     gw_log_file (GW_LOG_ALL, *++argv);
408                 }
409                 else
410                 {
411                     gw_log (GW_LOG_FATAL, KERNEL_LOG, "missing log filename");
412                     exit (1);
413                 }
414                 break;
415             case 'i':
416                 if (argv[0][2])
417                     info.userid = atoi (argv[0]+2);
418                 else if (argc > 0)
419                 {
420                     --argc;
421                     info.userid = atoi (*++argv);
422                 }
423                 else
424                 {
425                     gw_log (GW_LOG_FATAL, KERNEL_LOG, "missing user id");
426                     exit (1);
427                 }
428                 gw_log_session (info.userid);
429                 break;
430             default:
431                 gw_log (GW_LOG_FATAL, KERNEL_LOG, "unknown option %s;"
432                         " use -H for help", *argv);
433                 exit (1);
434             }
435         }
436         else
437             info.default_res = *argv;
438     }
439     if (!(queue = str_queue_mk ()))
440     {
441         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, KERNEL_LOG, "str_queue_mk");
442         exit (1);
443     }
444     if (info.userid != -1)
445     {
446         read_kernel_res ();
447         kernel_events (queue);
448     }
449     else
450     {
451         char line_buf[512];
452         read_kernel_res ();
453         while (lgets (line_buf, sizeof(line_buf)-1, 0))
454             str_queue_enq (queue, line_buf);
455         urp_start (0, queue);
456         urp_command (queue);
457         urp_end ();
458     }
459     return 0;
460 }
461
462 struct gw_user_set *user_set_add (const char *name, int hits, 
463                                   const char *database, 
464                                   struct ccl_rpn_node *rpn,
465                                   int present_flag,
466                                   const char *search_str)
467 {
468     struct gw_user_set *s;
469
470     s = malloc (sizeof (*s));
471     assert (s);
472
473     s->name = gw_strdup (name);
474     s->hits = hits;
475     s->database = gw_strdup (database);
476     s->rpn = rpn;
477     s->present_flag = present_flag;
478     s->search_str = gw_strdup (search_str);
479     s->prev = info.sets;
480     info.sets = s;
481     return s;
482 }
483
484 void user_set_init (void)
485 {
486     struct gw_user_set *s, *s1;
487
488     for (s = info.sets; s; s = s1)
489     {
490         free (s->name);
491         free (s->database);
492         ccl_rpn_delete (s->rpn);
493         s1 = s->prev;
494         free (s);
495     }
496     info.sets = NULL;
497 }
498
499 struct gw_user_set *user_set_search (const char *name)
500 {
501     struct gw_user_set *s;
502
503     if (!name)
504         return info.sets;
505     for (s = info.sets; s; s = s->prev)
506         if (!strcmp (s->name, name))
507             return s;
508     return NULL;
509 }
510
511 #if USE_FML
512 static void fml_inf_write (int ch)
513 {
514     putc (ch, reply_fd);
515 }
516 static FILE *fml_inf;
517
518 static int fml_inf_read (void)
519 {
520     return getc (fml_inf);
521 }
522 #endif
523
524 void read_kernel_res (void)
525 {
526     const char *v;
527     char *cp;
528     char resource_name[256];
529
530     user_set_init ();
531
532     if (info.bibset)
533         ccl_qual_rm (&info.bibset);
534     info.bibset = ccl_qual_mk ();
535
536     if (info.kernel_res)
537         gw_res_close (info.kernel_res);
538     info.kernel_res = gw_res_init ();
539
540     gw_log (GW_LOG_DEBUG, KERNEL_LOG, "reading kernel resource, default %s",
541             info.default_res);
542     if (*info.target)
543         gw_log (GW_LOG_DEBUG, KERNEL_LOG, "reading kernel resource, target %s",
544                 info.target);
545     if (info.lang)
546         gw_log (GW_LOG_DEBUG, KERNEL_LOG, "reading kernel resource, lang %s",
547                 info.lang);
548
549     /* read default resources. These should exist */
550     if (gw_res_merge (info.kernel_res, info.default_res))
551     {
552         gw_log (GW_LOG_WARN, KERNEL_LOG, "Couldn't read resource file %s",
553                 info.default_res);
554         return;
555     }
556     /* fetch target definitions (if defined at all) */
557     if (*info.target)
558     {
559         sprintf (resource_name, "gw.target.%s", info.target);
560         v = gw_res_get (info.kernel_res, resource_name, NULL);
561         if (v)
562             gw_res_merge (info.kernel_res, v);
563     }
564     /* fetch user definitions (if user-id is specified) */
565     if (info.userid >= 0)
566     {
567         char fname[250];
568         sprintf (fname, "user.%d.r", info.userid);
569         gw_res_merge (info.kernel_res, fname);
570     }
571     /* fetch language definitions (if specified at all) */
572     v = gw_res_get (info.kernel_res, "gw.language", info.lang);
573     if (v)
574     {
575         sprintf (resource_name, "gw.lang.%s", v);
576         v = gw_res_get (info.kernel_res, resource_name, NULL);
577         if (v)
578         {
579             gw_log (GW_LOG_DEBUG, KERNEL_LOG, "Reading language resources %s",
580                     v);
581             gw_res_merge (info.kernel_res, v);
582         }
583     }
584     /* fetch overriding resources from file (if specified) */
585     if (info.override_res)
586         gw_res_merge (info.kernel_res, info.override_res);        
587
588     /* read bibset definition for ccl */
589     v = gw_res_get (info.kernel_res, "gw.bibset", NULL);
590     if (v)
591     {
592         FILE *bib_inf;
593
594         bib_inf = fopen (v, "r");
595         if (!bib_inf)
596             gw_log (GW_LOG_WARN, KERNEL_LOG, "cannot open %s", v);
597         else
598         {
599             gw_log (GW_LOG_DEBUG, KERNEL_LOG, "reading bib file %s", v);
600             ccl_qual_file (info.bibset, bib_inf);
601             fclose (bib_inf);
602         }
603     }
604
605     /* determine host name and port no */
606     sprintf (resource_name, "gw.target.%s", info.target);
607     if (*info.target && ! gw_res_get (info.kernel_res, resource_name, NULL))
608     {
609         /* target is specified, and there is no sub-resource for it... */
610         const char *split;
611
612         if ((split = strchr (info.target, ':')))
613         {
614             memcpy (info.hostname, info.target, split-info.target);
615             info.hostname[split-info.target] = '\0';
616             info.port = atoi (split+1);
617         }
618         else
619         {
620             strcpy (info.hostname, info.target);
621             info.port = gw_res_int (info.kernel_res, "gw.portno", 210);
622         }
623     }
624     else
625     {   /* resources gw.hostname and gw.portno will be used */
626         strncpy (info.hostname, gw_res_get (info.kernel_res,
627                                             "gw.hostname", "localhost"),
628                  sizeof(info.hostname)-1);
629         info.port = gw_res_int (info.kernel_res, "gw.portno", 210);
630         strcpy (info.account, gw_res_get (info.kernel_res, "gw.account", ""));
631         info.account_in_session = 0;
632     }
633     /* set info.databases (all available databases for target) */
634     /* set info.database (first database for target) */
635     if (info.databases)
636         free (info.databases);
637     if (info.database)
638         free (info.database);
639     v = gw_res_get (info.kernel_res, "gw.databases", "");
640     info.databases = gw_strdup (v);
641     for (cp = info.databases; (cp = strchr (cp, ' ')); cp++)
642         *cp = ',';
643     v = gw_res_get (info.kernel_res, "gw.database", "");
644     if (*v == '\0' && *info.databases)
645     {
646         int len;
647         cp = strchr (info.databases, ',');
648         
649         len = cp ? (cp-info.databases) : strlen (info.databases);
650         info.database = malloc (len+1);
651         assert (info.database);
652         memcpy (info.database, info.databases, len);
653         info.database[len] = '\0';
654     }
655     else
656     {
657         info.database = gw_strdup (v);
658         for (cp = info.database; (cp = strchr (cp, ' ')); cp++)
659             *cp = ',';
660     }
661
662     /* the port no can be explicitly overridden by a command line option */
663     if (info.override_portno)
664         info.port = atoi (info.override_portno);
665
666     /* the hostname can be explicitly overridden by a command line option */
667     if (info.override_hostname)
668         strncpy (info.hostname, info.override_hostname,
669                  sizeof(info.hostname)-1);
670
671     ccl_token_and = gw_res_get (info.kernel_res, "ccl.token.and", "and");
672     ccl_token_or = gw_res_get (info.kernel_res, "ccl.token.or", "or");
673     ccl_token_not = gw_res_get (info.kernel_res, "ccl.token.not", "not");
674     ccl_token_set = gw_res_get (info.kernel_res, "ccl.token.set", "set");
675
676     /* determine if more than one result-set names is supported */
677     if (gw_res_bool (info.kernel_res, "gw.result.set", 1))
678         info.setno = 0;
679     else
680         info.setno = -1;
681 #if USE_FML
682     if (!info.fml)
683     {
684         v = gw_res_get (info.kernel_res, "gw.fml", "default.fml");    
685         fml_inf = fopen (v, "r");
686         if (!fml_inf)
687             gw_log (GW_LOG_WARN, KERNEL_LOG, "cannot open fml script %s", v);
688         else
689         {
690             info.fml = fml_open ();
691             info.fml->read_func = fml_inf_read;
692             info.fml->write_func = fml_inf_write;
693             fml_preprocess (info.fml);
694             fml_exec (info.fml);
695             fclose (fml_inf);
696         }
697     }
698 #endif
699 }