Bug fix: shell (wproto) sometimes closed server FIFO before cgi
[egate.git] / www / wproto.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  * $Log: wproto.c,v $
44  * Revision 1.11  1996/01/05 16:21:21  adam
45  * Bug fix: shell (wproto) sometimes closed server FIFO before cgi
46  * program opened it - solution: cgi sends OK when response has been read.
47  *
48  * Revision 1.10  1995/12/22  14:21:16  adam
49  * More work on scan. The search.egw script takes care of cached
50  * query page (doesn't always increment nextSetNo). To make new search set
51  * either 'New query' must be selected or the query page must be reloaded.
52  * The msearch script doesn't do this yet, however.
53  *
54  * Revision 1.9  1995/11/14  16:31:36  adam
55  * Temporary remove of ccl entry.
56  *
57  * Revision 1.8  1995/11/13  15:41:45  adam
58  * Arrow gifs.
59  * Gateway uses record element set names B(rief) and F(ull).
60  * Bug fix. Didn't save idAuthentication correctly.
61  *
62  * Revision 1.7  1995/11/10  14:47:32  adam
63  * Plus (+) characters automatically converted to space in forms.
64  * Work on search in multiple targets. Doesn't work well - yet.
65  * Presentation formats enhanced.
66  *
67  * Revision 1.6  1995/11/06  10:51:17  adam
68  * End of response marker in response from wsh/wproto to wcgi.
69  * Shells are respawned when necessary.
70  *
71  * Revision 1.5  1995/11/02  16:35:37  adam
72  * Bug fixes and select on FIFOs in wcgi - doesn't really work!
73  *
74  * Revision 1.4  1995/10/31  16:56:25  adam
75  * Record presentation.
76  *
77  * Revision 1.3  1995/10/27  15:12:10  adam
78  * IrTcl incorporated in the gateway.
79  * Better separation of script types.
80  * Z39.50 gateway scripts entered.
81  *
82  * Revision 1.2  1995/10/23  16:55:39  adam
83  * A lot of changes - really.
84  *
85  * Revision 1.1  1995/10/20  11:49:26  adam
86  * First version of www gateway.
87  *
88  */
89
90 #include <stdio.h>
91 #include <string.h>
92 #include <stdlib.h>
93 #include <sys/time.h>
94 #include <sys/types.h>
95 #include <sys/stat.h>
96 #include <fcntl.h>
97 #include <unistd.h>
98 #include <stdarg.h>
99 #include <ctype.h>
100 #include <errno.h>
101
102 #include "wproto.h"
103
104 static int wproto_dumpcache(WCLIENT wc, int level);
105 static int wproto_findcache(WCLIENT wc, char *name);
106 static void wproto_uncache(WCLIENT wc, int level);
107
108 static char *mod = "wproto";
109
110 void wo_write (WCLIENT wc, const char *s, size_t len)
111 {
112     if (wc->outbuffer_offset + len >= wc->outbuffer_size)
113         wc->outbuffer = realloc(wc->outbuffer, wc->outbuffer_size +=
114         OUTBUFFER_CHUNK);
115     memcpy(wc->outbuffer + wc->outbuffer_offset, s, len);
116     wc->outbuffer_offset += len;
117 }
118
119 void wo_puts (WCLIENT wc, const char *s)
120 {
121     wo_write (wc, s, strlen(s));
122 }
123
124 void wo_printf (WCLIENT wc, const char *fmt, ...)
125 {
126     va_list ap;
127     char tmpbuf[4048];
128
129     va_start(ap, fmt);
130     vsprintf(tmpbuf, fmt, ap);
131     wo_puts(wc, tmpbuf);
132     va_end(ap);
133 }
134
135 void wo_clear (WCLIENT wc, char *type)
136 {
137     if (!wc->outbuffer)
138         wc->outbuffer = malloc(wc->outbuffer_size = OUTBUFFER_CHUNK);
139     wc->outbuffer_offset = 0;
140 #if 0
141     wo_printf(wc, "Expires: 0\nContent-type: %s\n\n", type);
142 #else
143     wo_printf(wc, "Content-type: %s\n\n", type);
144 #endif
145 }
146
147 int wo_puthtml (WCLIENT wc, char *name)
148 {
149     FILE *f; 
150     char ch;
151
152     wo_clear(wc, "text/html");
153     if (!(f = fopen(name, "r")))
154     {
155         wo_printf(wc, "<BR>Failed to open file: %s<BR>", name);
156         return 0;
157     }
158     while (ch = getc(f), !feof(f))
159     {
160         if (wo_putc(wc, ch) < 0)
161         {
162             fclose(f);
163             return -1;
164         }
165     }
166     fclose(f);
167     return 0;
168 }
169
170 int wo_flush(WCLIENT wc)
171 {
172     int wrote, towrite;
173
174     if (!(wc->outbuffer_offset))
175         return 0;
176     towrite = wc->outbuffer_offset;
177     wc->outbuffer_offset = 0;
178     for (;;)
179     {
180         int w_chunk;
181
182         w_chunk = towrite;
183         wrote = write(wc->lineout, wc->outbuffer + wc->outbuffer_offset,
184             w_chunk);
185         if (wrote <= 0)
186         {
187             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write response");
188             return -1;
189         }
190         gw_log (GW_LOG_DEBUG, mod, "wrote %d bytes", wrote);
191         if (wc->cache_fd >= 0)
192             if (write(wc->cache_fd, wc->outbuffer + wc->outbuffer_offset,
193                 towrite) < 0)
194             {   
195                 gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write cache");
196                 return -1;
197             }
198         towrite -= wrote;
199         if (!towrite)
200             break;
201         wc->outbuffer_offset += wrote;
202     }
203     wc->outbuffer_offset = 0;
204     return 0;
205 }
206
207 int wo_overflow(WCLIENT wc, char ch)
208 {
209     gw_log (GW_LOG_DEBUG, mod, "wo_overflow");
210     if (wo_flush(wc) < 0)
211         return -1;
212     return wo_putc(wc, ch);
213 }
214
215 int wo_finish(WCLIENT wc)
216 {
217     char buf[4];
218     gw_log (GW_LOG_DEBUG, mod, "wo_finish");
219
220     wo_putc (wc, 0);
221     if (wo_flush(wc) < 0)
222         return -1;
223 #if 1
224     gw_log (GW_LOG_DEBUG, mod, "reading ack");
225     if (read(wc->linein, buf, 2) != 2)
226     {
227         gw_log (GW_LOG_DEBUG, mod, "read ack");
228     }
229 #endif
230     close(wc->lineout);
231     wc->lineout = -1;
232     if (wc->cache_fd >= 0)
233     {
234         close(wc->cache_fd);
235         wc->cache_fd = -1;
236     }
237     return 0;
238 }
239
240 static void descramble(char *t, const char *o)
241 {
242     unsigned int v;
243
244     while (*o)
245     {
246         if (*o == '%' && isxdigit(*(o + 1)) && isxdigit(*(o + 2)))
247         {
248             sscanf(o + 1, "%2x", &v);
249             o += 3;
250             if (v == '+')
251                 *t = ' ';
252             else
253                 *t = (char) v;
254             t++;
255         }
256         else
257         {
258             if (*o == '+')
259                 *t = ' ';
260             else
261                 *t = *o;
262             t++;
263             o++;
264         }
265     }
266     *t = '\0';
267 }
268
269 static void decode_form(wform_data *form, char *buf)
270 {
271     int i = 0;
272     char *p;
273     char tmp[512];
274
275     while (*buf)
276     {
277         for (p = form[i].name; *buf && *buf != '='; buf++)
278             *(p++) = *buf;
279         *p = '\0';
280         if (*buf)
281             buf++;
282         for (p = tmp; *buf && *buf != '&'; buf++)
283             *(p++) = *buf;
284         *p = '\0';
285         descramble(form[i].value, tmp);
286         if (*buf)
287             buf++;
288         i++;
289     }
290     *form[i].name = '\0';
291 }
292
293 char *wgetval(WCLIENT wc, char *name)
294 {
295     int i;
296
297     for (i = 0; *wc->wf_data[i].name; i++)
298         if (!strcmp(name, wc->wf_data[i].name))
299             return wc->wf_data[i].value;
300     return 0;
301 }
302
303 int wproto_process(WCLIENT wc, int timeout)
304 {
305     int toread, rs, level;
306     char combuf[COMBUF], *p,*t;
307     fd_set input;
308     struct timeval to, *top;
309
310     for (;;)
311     {
312         gw_log (GW_LOG_DEBUG, mod, "process waiting for input.");
313         if (timeout > 0)
314         {
315             to.tv_usec = 0;
316             to.tv_sec = timeout;
317             top = &to;
318         }
319         else
320             top = 0;
321         FD_ZERO(&input);
322         FD_SET(wc->linein, &input);
323         /* go through select handle list */
324         while ((rs = select(wc->linein + 1, &input, 0, 0, top)) < 0 &&
325             errno == EINTR)
326             ;
327         if (rs < 0)
328         {
329             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "select");
330             return -1;
331         }
332         if (rs == 0)
333         {
334             gw_log (GW_LOG_STAT, mod, 
335                     "wproto_process returning 0 after %d second timeout.",
336                     timeout);
337             unlink (wc->wf_serverp);
338             return 0;
339         }
340         /* determine handle (fifo or user) */
341         if (read(wc->linein, &toread, sizeof(toread)) < sizeof(toread))
342         {
343             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "wp_proc:len read failed");
344             exit(1);
345         }
346         toread -= sizeof(toread);
347         if (read(wc->linein, combuf, toread) < toread)
348         {
349             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "wp_proc: data read failed");
350             exit(1);
351         }
352         p = combuf;
353         for (t = wc->wf_serverp; (*t = *p); t++, p++);
354         p++;
355         for (t = wc->wf_parms; (*t = *p); t++, p++);
356         p++;
357         p++;         /* we don't deal with envvars yet */
358         decode_form(wc->wf_data, p);
359         if (wc->lineout < 0)
360         {
361             gw_log (GW_LOG_DEBUG, mod, "open %s", wc->wf_serverp);
362             if ((wc->lineout = open(wc->wf_serverp, O_WRONLY)) < 0)
363             {
364                 gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open %s", 
365                         wc->wf_serverp);
366                 exit(1);
367             }
368         }
369         /* look in cache only if request carries no forms data. */
370         if (!*wc->wf_data[0].name && (level = wproto_findcache(wc,
371             wc->wf_parms)) >= 0)
372         {
373             gw_log (GW_LOG_DEBUG, mod, "wproto_dumpcache");
374             wproto_dumpcache(wc, level);
375             wo_finish(wc);
376         }
377         else
378         {
379             return 1;
380         }
381     }
382 }
383
384 WCLIENT wproto_init(void)
385 {
386     char *val, path2[256];
387     wclient_data *new;
388
389     gw_log (GW_LOG_DEBUG, mod, "wproto_init");
390     close(1);    /* release us from the wserver */
391     if (!(new = malloc(sizeof(*new))))
392     {
393         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "malloc");
394         exit (1);
395     }
396     if (!(val = getenv ("GWID")))
397     {
398         gw_log (GW_LOG_FATAL, mod, "GWID not set");
399         exit (1);
400     }
401     new->id = atoi (val);
402     sprintf(new->path, "%s/%s/clt%d", FIFOROOT, FIFODIR, new->id);
403     if (mkfifo(new->path, 0666 | S_IFIFO) < 0)
404         gw_log (GW_LOG_WARN|GW_LOG_ERRNO, mod, "mkfifo(%s)", new->path);
405     gw_log (GW_LOG_DEBUG, mod, "Synchronizing with server.");
406     sprintf(path2, "%s/%s/srv%d", FIFOROOT, FIFODIR, getppid());
407     if ((new->lineout = open(path2, O_WRONLY)) < 0)
408     {
409         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open out %s", path2);
410         exit(1);
411     }
412     if (write(new->lineout, "OK", 2) < 2)
413     {
414         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write");
415         exit(1);
416     }
417     gw_log (GW_LOG_DEBUG, mod, "Synchronized.");
418     if ((new->linein = open(new->path, O_RDONLY)) < 0)
419     {
420         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open input %s", new->path);
421         exit(1);
422     }
423     gw_log (GW_LOG_DEBUG, mod, "init. linein=%d lineout=%d",
424             new->linein, new->lineout);
425     /* we put a handle on this so we get a blocking read when no peer */
426     if (open(new->path, O_WRONLY | O_NDELAY) < 0)
427     {
428         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open dummy %s", new->path);
429         exit(1);
430     }
431     new->outbuffer = 0;
432     new->cache_level = -1;
433     new->cache_fd = -1;
434     return new;
435 }
436
437 static void wproto_uncache(WCLIENT wc, int level)
438 {
439     for (;wc->cache_level >= level; wc->cache_level--)
440         unlink(wc->cache[wc->cache_level].path);
441 }
442
443 void wproto_terminate(WCLIENT wc)
444 {
445     close(wc->linein);
446     unlink(wc->path);
447     wproto_uncache(wc, 0);
448     free(wc);
449 }
450
451 int wproto_cache(WCLIENT wc, int level)
452 {
453     cache_data *p;
454
455     if (level > wc->cache_level + 1)
456     {
457         gw_log (GW_LOG_FATAL, mod, "Illegal cache level increment.");
458         exit(1);
459     }
460     wproto_uncache(wc, level);
461     p = &wc->cache[++wc->cache_level];
462     sprintf(p->path, "%s/%s/csh%d.%d", FIFOROOT, FIFODIR, wc->id, level);
463     if ((wc->cache_fd = open(p->path, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0)
464     {
465         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open %s", p->path);
466         return -1;
467     }
468     strcpy(p->name, wc->wf_parms);
469     return 0;
470 }
471
472 static int wproto_findcache(WCLIENT wc, char *name)
473 {
474     int i;
475
476     for (i = 0; i <= wc->cache_level; i++)
477         if (!strcmp(wc->cache[i].name, name))
478             return i;
479     return -1;
480 }
481
482 static int wproto_dumpcache(WCLIENT wc, int level)
483 {
484     int fd, rd;
485
486     gw_log (GW_LOG_STAT, mod, "Using Cache: %s", wc->cache[level].name);
487     if ((fd = open(wc->cache[level].path, O_RDONLY)) < 0)
488     {
489         gw_log (GW_LOG_FATAL, mod, "open (R) %s", wc->cache[level].path);
490         return -1;
491     }
492     while ((rd = read(fd, wc->outbuffer, OUTBUFFER_CHUNK)) > 0)
493         if (write(wc->lineout, wc->outbuffer, rd) < rd)
494         {
495             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write toline");
496             return -1;
497         }
498     if (rd < 0)
499     {
500         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "read");
501         return -1;
502     }
503     wproto_uncache(wc, level + 1);
504     return 0;
505 }