Minor changes.
[egate.git] / kernel / urp.c
1 /* Gateway kernel
2  * Europagate, 1995
3  *
4  * $Log: urp.c,v $
5  * Revision 1.9  1995/02/21 17:46:21  adam
6  * Minor changes.
7  *
8  * Revision 1.8  1995/02/21  12:12:00  adam
9  * Diagnostic record with error info. observed.
10  *
11  * Revision 1.7  1995/02/20  21:16:20  adam
12  * FML support. Bug fixes. Profile for drewdb.
13  *
14  * Revision 1.6  1995/02/17  14:41:14  quinn
15  * Added simple display of records.
16  *
17  * Revision 1.5  1995/02/17  14:22:13  adam
18  * First steps of CCL show command. Not finished yet.
19  *
20  * Revision 1.4  1995/02/17  09:08:36  adam
21  * Reply with subject. CCL base command implemented.
22  *
23  * Revision 1.3  1995/02/16  18:35:09  adam
24  * First use of Zdist library. Search requests are supported.
25  * Present requests are not supported yet.
26  *
27  * Revision 1.2  1995/02/16  13:21:00  adam
28  * Organization of resource files for targets and conversion
29  * language implemented.
30  *
31  * Revision 1.1  1995/02/15  17:45:30  adam
32  * First version of email gateway kernel. Email requests are read
33  * from stdin. The output is transferred to an MTA if 'From' is
34  * found in the header - or stdout if absent. No Z39.50 client is used.
35  *
36  */
37
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <assert.h>
41 #include <ctype.h>
42 #include <string.h>
43 #include <unistd.h>
44
45 #include "kernel.h"
46
47 #define LINE_MAX 256
48
49 static int reopen_target (void)
50 {
51     const char *v;
52     if (info.zass)
53         gw_log (GW_LOG_WARN, "urp", "Zass free...");
54     info.zass = zass_open (info.hostname, info.port);
55     if (!info.zass)
56     {
57         fprintf (reply_fd, "%s %s:%d\n", 
58                  gw_res_get (info.kernel_res, "gw.err.connect",
59                              "Cannot connect to target"),
60                  info.hostname, info.port);
61         return -1;
62     }
63     v = gw_res_get (info.kernel_res, "gw.description", NULL);
64     if (v)
65         fprintf (reply_fd, "%s\n", v);
66     fprintf (reply_fd, "%s %s:%d\n",
67              gw_res_get (info.kernel_res, "gw.msg.connect",
68                          "Connection established to"),
69              info.hostname, info.port);
70     if (*info.databases)
71         fprintf (reply_fd, "%s:\n%s\n",
72                  gw_res_get (info.kernel_res, "gw.msg.databases",
73                              "Available databases"),
74                  info.databases);
75     return 0;
76 }
77
78 static char line_buf[LINE_MAX+1];
79
80 static struct command_word {
81     char *default_value;
82     char *resource_suffix;
83 } command_tab [] = 
84 {
85 {   "find", "find"},
86 {   "show", "show"},
87 {   "base", "base" },
88 {   "help", "help" },
89 {   "info", "info" },
90 {   "continue", "continue" },
91 {   "status", "status" },
92 {   "cancel", "cancel" },
93 {   "target", "target" },
94 {   NULL, NULL }
95 };
96
97 static int command_search (struct command_word *tab, struct ccl_token *cmd,
98 const char *resource_prefix)
99 {
100     int no = 1;
101
102     assert (resource_prefix);
103     assert (tab);
104     assert (cmd);
105     while (tab->default_value)
106     {
107         char *cp, command_names[60];
108         char resource_name[60];
109         const char *v;
110
111         sprintf (resource_name, "%s%s", resource_prefix,
112                  tab->resource_suffix);
113         v = gw_res_get (info.kernel_res, resource_name, tab->default_value);
114         assert (v);
115         strcpy (command_names, v);
116         cp = command_names;
117         while (1)
118         {
119             char *split;
120
121             if ((split = strchr (cp, ' ')))
122                 *split = '\0';
123             if (cmd->len == strlen(cp) &&
124                 !memcmp (cmd->name, cp, cmd->len))
125                 return no;
126             if (!split)
127                 break;
128             cp = split+1;
129         }        
130         no++;
131         tab++;
132     }
133     return 0;
134 }
135
136 static struct error_no_struct {
137     int no;
138     char *resource_name;
139 } error_ccl_tab[] = {
140 {  CCL_ERR_OK, "ok"},
141 {  CCL_ERR_TERM_EXPECTED, "term.expected" },
142 {  CCL_ERR_RP_EXPECTED, "rp.expected" },
143 {  CCL_ERR_SETNAME_EXPECTED, "setname.expected" },
144 {  CCL_ERR_OP_EXPECTED, "op.expected" },
145 {  CCL_ERR_BAD_RP, "bad.rp" },
146 {  CCL_ERR_UNKNOWN_QUAL, "unknown.qual" },
147 {  CCL_ERR_DOUBLE_QUAL, "double.qual" },
148 {  CCL_ERR_EQ_EXPECTED, "eq.expected" },
149 {  CCL_ERR_BAD_RELATION, "bad.relation" },
150 {  CCL_ERR_TRUNC_NOT_LEFT, "trunc.not.left" },
151 {  CCL_ERR_TRUNC_NOT_BOTH, "trunc.not.both" },
152 {  CCL_ERR_TRUNC_NOT_RIGHT, "trunc.not.right" },
153 {  0, NULL }
154 };
155
156 static char *error_no_search (struct error_no_struct *tab, int no)
157 {
158     struct error_no_struct *p = tab;
159     while (p->resource_name)
160     {
161         if (no == p->no)
162             return p->resource_name;
163         p++;
164     }
165     return NULL;
166 }
167
168 static int email_header (FILE *inf, char *from_str, char *subject_str)
169 {
170     *from_str = '\0';
171     *subject_str = '\0';
172     while (fgets (line_buf, LINE_MAX, inf))
173     {
174         if (line_buf[0] == '\n')
175             return 0;
176         if (strncmp (line_buf, "From ", 5) == 0)
177             sscanf (line_buf+4, "%s", from_str);
178         if (strncmp (line_buf, "Subject: ", 9) == 0 &&
179             sscanf (line_buf+9, "%s", subject_str+1) == 1)
180             strcpy (subject_str, line_buf+9);
181     }
182     return 1;
183 }
184
185 static int exec_find (struct ccl_token *list)
186 {
187     const struct zass_searchent *p;
188
189     struct ccl_rpn_node *rpn;
190     int error;
191     const char *pos;
192
193     rpn = ccl_find (info.bibset, list, &error, &pos);
194     if (!rpn)
195     {
196         const char *v = NULL, *n;
197         char name[128];
198
199         fprintf (reply_fd, "  %*s^ - ", pos - line_buf, " ");
200
201         n = error_no_search (error_ccl_tab, error);
202         if (n)
203         {
204             sprintf (name, "gw.err.%s", n);
205             v = gw_res_get (info.kernel_res, name, NULL);
206         }
207         if (!v)
208             v = ccl_err_msg (error);
209         fprintf (reply_fd, "%s\n", v);
210         return -1;
211     }
212     ccl_pr_tree (rpn, reply_fd);
213     fprintf (reply_fd, "\n");
214
215     if (!info.zass)
216         return -2;
217     if (!*info.databases)
218     {
219         fprintf (reply_fd, "%s\n",
220                  gw_res_get (info.kernel_res, "gw.err.no.database",
221                              "You must select database"));
222         return -3;
223     }
224     gw_log (GW_LOG_DEBUG, "urp", "Searching in database %s",
225             info.databases);
226     p = zass_search (info.zass, rpn, "Default", info.databases);
227     if (!p)
228         return -1;
229     if (p->errcode != -1)
230         fprintf (reply_fd, "%s %d: %s\n",
231                  gw_res_get (info.kernel_res, "gw.msg.z39errcode",
232                              "Z39.50 error code"),
233                  p->errcode, p->errstring);
234     else
235         fprintf (reply_fd, "%d %s\n", p->num,
236                  gw_res_get (info.kernel_res, "gw.msg.hits", "hit(s)"));
237     return 0;
238 }
239
240 static int exec_target (struct ccl_token *list)
241 {
242     int len;
243     if (list->kind == CCL_TOK_EOL)
244         return -1;
245     len = list->len;
246     memcpy (info.target, list->name, len);
247     info.target [len] = '\0';
248
249     read_kernel_res ();
250     return reopen_target ();
251 }
252
253 static int exec_base (struct ccl_token *list)
254 {
255     struct ccl_token *li = list;
256     int len = 0;
257
258     if (list->kind == CCL_TOK_EOL)
259         return -1;
260     free (info.databases);
261     while (li->kind != CCL_TOK_EOL)
262     {
263         len += li->len + 1;
264         li = li->next;
265         if (li->kind == CCL_TOK_COMMA)
266             li = li->next;
267     }
268     info.databases = malloc (len);
269     assert (info.databases);
270     len = 0;
271     li = list;
272     while (li->kind != CCL_TOK_EOL)
273     {
274         memcpy (info.databases+len, li->name, li->len);
275         len += li->len;
276         info.databases[len++] = ',';
277         li = li->next;
278         if (li->kind == CCL_TOK_COMMA)
279             li = li->next;
280     }
281     info.databases[len-1] = '\0';
282     return 0;
283 }
284
285 struct command_word show_tab [] = 
286 {
287 {   "f", "format"},
288 {   "p", "position"},
289 {   NULL, NULL }
290 };
291
292 static int exec_show (struct ccl_token *list)
293 {
294     const struct zass_presentent *zp;
295     char num_str[20];
296     struct ccl_token *set_token = NULL;
297     struct ccl_token *format_token = NULL;
298     struct ccl_token *li = list;
299
300     if (list->kind == CCL_TOK_EOL)
301         return -1;
302     if (!info.zass)
303         return -2;
304     while (li->kind != CCL_TOK_EOL)
305     {
306         int modifier_no = 0;
307         if (li->next->kind == CCL_TOK_EQ)
308         {
309             if (li->kind == CCL_TOK_SET)    /* set = <name> ? */
310             {
311                 li = li->next->next;
312                 set_token = li;
313             }
314             else 
315             {
316                 modifier_no = command_search (show_tab, li, "ccl.token.");
317                 if (!modifier_no)
318                 {
319                     fprintf (reply_fd, "Unknown modifier in show\n");
320                     return -1;
321                 }
322                 li = li->next->next;
323                 if (modifier_no == 1)       /* f = <name> ? */
324                     format_token = li;
325                 else if (modifier_no == 2)  /* p = <name> ? */
326                 {
327                     if (li->kind != CCL_TOK_EOL   /* p = <name> - <name> ? */
328                         && li->next->kind == CCL_TOK_MINUS
329                         && li->next->next != CCL_TOK_EOL)
330                         li = li->next->next;
331                 }
332             }
333             li = li->next;
334         }
335         else
336             li = li->next;
337     }
338     if (set_token)
339         gw_log (GW_LOG_DEBUG, "urp", "Got set=%.*s", set_token->len,
340                 set_token->name);
341     if (format_token)
342         gw_log (GW_LOG_DEBUG, "urp", "Got format=%.*s", format_token->len,
343                 format_token->name);
344
345     li = list;
346     while (li->kind != CCL_TOK_EOL)
347     {
348         int modifier_no = 0;
349         int offset = 0;
350         int number = 0;
351         int len;
352         if (li->next->kind == CCL_TOK_EQ && li->kind != CCL_TOK_SET)
353         {
354             modifier_no = command_search (show_tab, li, "ccl.token.");
355             li = li->next->next;
356             if (modifier_no == 2)  /* p = <name> ? */
357             {
358                 if (li->kind != CCL_TOK_EOL   /* p = <name> - <name> ? */
359                     && li->next->kind == CCL_TOK_MINUS
360                     && li->next->next != CCL_TOK_EOL)
361                 {
362                     len = li->len;
363                     memcpy (num_str, li->name, len);
364                     num_str [len] = '\0';
365                     offset = atoi (num_str);
366                     li = li->next->next;
367
368                     len = li->len;
369                     memcpy (num_str, li->name, len);
370                     num_str [len] = '\0';
371                     number = atoi (num_str) - offset + 1;
372                 }
373                 else
374                 {
375                     len = li->len;
376                     memcpy (num_str, li->name, len);
377                     num_str [len] = '\0';
378                     offset = atoi (num_str);
379                     number = 1;
380                 }
381             }
382             li = li->next;
383         }
384         else
385         {
386             len = li->len;
387             memcpy (num_str, li->name, len);
388             num_str[len] = '\0';
389             number = atoi (num_str);
390             offset = 1;
391             li = li->next;
392         }
393         if (offset > 0 && number > 0)
394         {
395             if (set_token)
396             {
397                 len = set_token->len;
398                 memcpy (num_str, set_token->name, len);
399                 num_str[len] = '\0';
400             }
401             else
402                 strcpy (num_str, "Default");
403             gw_log (GW_LOG_DEBUG, "urp", "zass_present of %d records from"
404                     " offset %d in set %s", number, offset, num_str);
405             zp = zass_present(info.zass, num_str, offset, number);
406             if (zp)
407             {
408                 int i;
409                 zass_record *pp;
410                 
411                 fprintf (reply_fd, gw_res_get (info.kernel_res,
412                                                "gw.msg.records",
413                                                "Got %d records"),
414                          zp->num);
415                 fprintf (reply_fd, "\n");
416                 for (i = 0, pp = zp->records; pp; pp = pp->next, i++)
417                 {
418                     Iso2709Rec rec;
419 #if USE_FML
420                     const char *arg_ar[5];
421 #endif
422                     fprintf (reply_fd, "--- %d/%d ---\n",
423                              i+offset, offset+zp->num-1);
424                     if (pp->which == ZASS_REC_DIAG)
425                     {
426                         fprintf (reply_fd, "Record error %d: %s\n",
427                                  pp->errcode, pp->errstring);
428                         continue;
429                     }
430                     else if (pp->which != ZASS_REC_USMARC)
431                     {
432                         fprintf (reply_fd, "Unknown record kind %d\n",
433                                  pp->which);
434                         continue;
435                     }
436                     rec = iso2709_cvt (pp->record);
437 #if USE_FML
438                     if (format_token)
439                     {
440                         len = format_token->len;
441                         memcpy (num_str, format_token->name, len);
442                         num_str[len] = '\0';
443                     }
444                     if (format_token && 
445                         (!strcmp (num_str, "0") || !strcmp (num_str, "1")))
446                     {
447                         arg_ar[0] = "\\f";
448                         arg_ar[1] = num_str;
449                         arg_ar[2] = " \\list";
450                         arg_ar[3] = marc_to_str (info.fml, rec);
451                         arg_ar[4] = NULL;
452                         fml_exec_call_argv (info.fml, arg_ar);
453                     }
454                     else
455                         iso2709_display (rec, reply_fd);
456 #else
457                     iso2709_display (rec, reply_fd);
458 #endif
459                     iso2709_rm (rec);
460                 }
461             }
462         }
463     }
464 #if 0
465     len = list->len;
466     memcpy (num_str, list->name, len);
467     num_str[len] = '\0';
468
469     num = atoi (num_str);
470     if (!num)
471         return -3;
472     gw_log (GW_LOG_DEBUG, "urp", "zass_present of %d records", num);
473     zp = zass_present(info.zass, "Default", 1, num);
474     if (zp)
475     {
476         int i;
477         zass_record *pp;
478
479         fprintf (reply_fd, gw_res_get (info.kernel_res,
480                                        "gw.msg.records", "Got %d records"),
481                  zp->num);
482         fprintf (reply_fd, "\n");
483         for (i = 1, pp = zp->records; pp; pp = pp->next, i++)
484         {
485 #if USE_FML
486             const char *arg_ar[3];
487 #endif
488             Iso2709Rec rec = iso2709_cvt (pp->record);
489
490             fprintf (reply_fd, "--- %d/%d ---\n", i, zp->num);
491 #if USE_FML
492             arg_ar[0] = "\\f0 \\list";
493             arg_ar[1] = marc_to_str (info.fml, rec);
494             arg_ar[2] = NULL;
495             fml_exec_call_argv (info.fml, arg_ar);
496 #else
497             iso2709_display (rec, reply_fd);
498 #endif
499             iso2709_rm (rec);
500         }
501     }
502 #endif
503     return 0;
504 }
505
506 static int exec_command (const char *str)
507 {
508     struct ccl_token *cmd = ccl_tokenize (str);
509     int no;
510
511     if (cmd->kind != CCL_TOK_EOL &&
512         (no = command_search (command_tab, cmd, "ccl.command.")))
513     {
514         if (!info.zass && no != 9)
515             reopen_target ();
516         fprintf (reply_fd, "\n> %s", str);
517         switch (no)
518         {
519         case 1:
520             return exec_find (cmd->next);
521         case 2:
522             return exec_show (cmd->next);
523         case 3:
524             return exec_base (cmd->next);
525         case 9:
526             return exec_target (cmd->next);
527         default:
528             fprintf (reply_fd, "%s\n",
529                      gw_res_get (info.kernel_res, "gw.err.unimplemented",
530                                  "Not implemented yet"));
531         }
532     }
533     else
534     {
535         fprintf (reply_fd, "\n> %s", str);
536         fprintf (reply_fd, "  ^ %s\n", 
537                  gw_res_get (info.kernel_res, "gw.err.unknown.command",
538                              "unknown command"));
539     }
540     return 0;
541 }
542
543 int urp (FILE *inf)
544 {
545     char from_str[128];
546     char subject_str[128];
547     int command_no = 0;
548     char *reply_fname = NULL;
549
550     if (email_header (inf, from_str, subject_str))
551     {
552         gw_log (GW_LOG_WARN, "urp", "No message body");
553         return -1;
554     }
555     if (*from_str)
556     {
557         reply_fname = tempnam (gw_res_get (info.kernel_res,
558                                            "gw.reply.tmp.dir", NULL),
559                                gw_res_get (info.kernel_res,
560                                            "gw.reply.tmp.prefix", "gwr"));
561                                                  
562         reply_fd = fopen (reply_fname, "w");
563         if (!reply_fd)
564         {
565             gw_log (GW_LOG_FATAL, "urp", "Cannot create %s",
566                     reply_fname);
567             return -1;
568         }
569         fprintf (reply_fd, "Subject: ");
570         if (*subject_str)
571             fprintf (reply_fd, "Z39.50 Re: %s", subject_str);
572         else
573             fprintf (reply_fd, "%s\n", gw_res_get (info.kernel_res,
574                                                    "gw.msg.subject",
575                                                    "Your Query"));
576         fprintf (reply_fd, "\n");
577     }
578     else
579         gw_log (GW_LOG_WARN, "urp", "No From in email header");
580     fprintf (reply_fd, "%s\n", gw_res_get (info.kernel_res, "gw.msg.greeting",
581                                            "Email->Z39.50 gateway"));
582     while (fgets (line_buf, LINE_MAX, inf))
583     {
584         if (line_buf[0] == '\n')
585             break;
586         ccl_token_and = gw_res_get (info.kernel_res, "ccl.token.and", "and");
587         ccl_token_or = gw_res_get (info.kernel_res, "ccl.token.or", "or");
588         ccl_token_not = gw_res_get (info.kernel_res, "ccl.token.not", "not");
589         ccl_token_set = gw_res_get (info.kernel_res, "ccl.token.set", "set");
590         if (isalpha (line_buf[0]))
591             exec_command (line_buf);
592         command_no++;
593     }
594     if (!command_no)
595         fprintf (reply_fd, "%s\n", gw_res_get (info.kernel_res,
596                                                "gw.err.nullbody",
597                                                "No body"));
598     if (*from_str)
599     {
600         const char *mta;
601         char cmd[256];
602         int mta_code;
603
604         assert (reply_fname);
605         fclose (reply_fd);
606         reply_fd = stdout;
607
608         mta = gw_res_get (info.kernel_res, "gw.reply.mta",
609                           "/usr/lib/sendmail");
610         sprintf (cmd, "%s %s < %s", mta, from_str, reply_fname);
611         
612         mta_code = system (cmd);
613         if (mta_code)
614             gw_log (GW_LOG_FATAL, "urp", "Reply '%s' got exit code %d",
615                     cmd, mta_code);
616         unlink (reply_fname);        
617     }
618     return 0;
619 }