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