Enhanced the egw_source command. An optional parameter specifies the
[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.24  1997/01/31 11:16:00  adam
45  * Enhanced the egw_source command. An optional parameter specifies the
46  * name of a variable in which the HTML output is stored.
47  *
48  * Revision 1.23  1997/01/27 11:27:14  adam
49  * Implemented a new command, egw_clear, to clear http output cache.
50  * Changed prototype for function wo_clear.
51  *
52  * Revision 1.22  1997/01/24 13:13:11  adam
53  * Implemnted egw_source and added a "raw" option to the URL.
54  * Fixed a bug in the buffering system of wproto; the macro wo_putc could
55  * override memory if it was the first HTML generating function called.
56  *
57  * Revision 1.21  1996/05/23 15:53:11  adam
58  * Bug fix: egw_enc failed on 8-bit chars.
59  * New command: egw_parms.
60  *
61  * Revision 1.20  1996/05/21  14:53:04  adam
62  * Tcl command wform extented; options -raw and -exists added.
63  *
64  * Revision 1.19  1996/03/14  11:48:39  adam
65  * New function egw_prog that returns name of shell.
66  *
67  * Revision 1.18  1996/02/12  10:09:23  adam
68  * New parameter to wproto_init: directory root for the FIFOs (instead
69  * of using preprocessor defines FIFODIR/FIFOROOT).
70  *
71  * Revision 1.17  1996/01/26  09:02:22  adam
72  * Open of client FIFO called with O_NDELAY when reconnecting to shell
73  * in order to prevent serious lock if previous shell died without
74  * unlinking client FIFO.
75  *
76  * Revision 1.16  1996/01/24  10:13:56  adam
77  * Bug fix: in function wo_write realloc is used only when memory is already
78  * allocated with malloc.
79  *
80  * Revision 1.15  1996/01/24  08:25:32  adam
81  * Buf fix: Uninitialized outbuffer_offset member.
82  *
83  * Revision 1.14  1996/01/12  13:08:07  adam
84  * CGI script passes name of lock file to the shell. The server will not close
85  * the response FIFO until this file becomes unlocked. This method handles
86  * cancel operations much better.
87  *
88  * Revision 1.13  1996/01/12  10:05:20  adam
89  * If script name ends with ';' HTTP/GET/Expires will be defined.
90  * The cgi interface only reads final handshake if response from
91  * server (shell) was zero-terminated [If it isn't it probably died].
92  *
93  * Revision 1.12  1996/01/05  16:35:02  adam
94  * Minor changes.
95  *
96  * Revision 1.11  1996/01/05  16:21:21  adam
97  * Bug fix: shell (wproto) sometimes closed server FIFO before cgi
98  * program opened it - solution: cgi sends OK when response has been read.
99  *
100  * Revision 1.10  1995/12/22  14:21:16  adam
101  * More work on scan. The search.egw script takes care of cached
102  * query page (doesn't always increment nextSetNo). To make new search set
103  * either 'New query' must be selected or the query page must be reloaded.
104  * The msearch script doesn't do this yet, however.
105  *
106  * Revision 1.9  1995/11/14  16:31:36  adam
107  * Temporary remove of ccl entry.
108  *
109  * Revision 1.8  1995/11/13  15:41:45  adam
110  * Arrow gifs.
111  * Gateway uses record element set names B(rief) and F(ull).
112  * Bug fix. Didn't save idAuthentication correctly.
113  *
114  * Revision 1.7  1995/11/10  14:47:32  adam
115  * Plus (+) characters automatically converted to space in forms.
116  * Work on search in multiple targets. Doesn't work well - yet.
117  * Presentation formats enhanced.
118  *
119  * Revision 1.6  1995/11/06  10:51:17  adam
120  * End of response marker in response from wsh/wproto to wcgi.
121  * Shells are respawned when necessary.
122  *
123  * Revision 1.5  1995/11/02  16:35:37  adam
124  * Bug fixes and select on FIFOs in wcgi - doesn't really work!
125  *
126  * Revision 1.4  1995/10/31  16:56:25  adam
127  * Record presentation.
128  *
129  * Revision 1.3  1995/10/27  15:12:10  adam
130  * IrTcl incorporated in the gateway.
131  * Better separation of script types.
132  * Z39.50 gateway scripts entered.
133  *
134  * Revision 1.2  1995/10/23  16:55:39  adam
135  * A lot of changes - really.
136  *
137  * Revision 1.1  1995/10/20  11:49:26  adam
138  * First version of www gateway.
139  *
140  */
141
142 #include <stdio.h>
143 #include <string.h>
144 #include <stdlib.h>
145 #include <sys/time.h>
146 #include <sys/types.h>
147 #include <sys/stat.h>
148 #include <fcntl.h>
149 #include <unistd.h>
150 #include <stdarg.h>
151 #include <ctype.h>
152 #include <errno.h>
153 #include <assert.h>
154
155 #include "wproto.h"
156
157 static int wproto_dumpcache(WCLIENT wc, int level);
158 static int wproto_findcache(WCLIENT wc, char *name);
159 static void wproto_uncache(WCLIENT wc, int level);
160
161 static char *mod = "wproto";
162
163 void wo_expand (WCLIENT wc, size_t len)
164 {
165     assert (wc->outbuffer);
166     wc->outbuffer = realloc(wc->outbuffer, wc->outbuffer_size +=
167           ((len >= OUTBUFFER_CHUNK) ? len*2 : OUTBUFFER_CHUNK));
168 }
169
170 void wo_write (WCLIENT wc, const char *s, size_t len)
171 {
172     if (wc->outbuffer_offset + len >= wc->outbuffer_size)
173         wo_expand (wc, len);
174     memcpy(wc->outbuffer + wc->outbuffer_offset, s, len);
175     wc->outbuffer_offset += len;
176 }
177
178 void wo_puts (WCLIENT wc, const char *s)
179 {
180     wo_write (wc, s, strlen(s));
181 }
182
183 void wo_printf (WCLIENT wc, const char *fmt, ...)
184 {
185     va_list ap;
186     char tmpbuf[4048];
187
188     va_start(ap, fmt);
189     vsprintf(tmpbuf, fmt, ap);
190     wo_puts(wc, tmpbuf);
191     va_end(ap);
192 }
193
194 void wo_clear (WCLIENT wc)
195 {
196     if (!wc->outbuffer)
197         wc->outbuffer = malloc (wc->outbuffer_size = OUTBUFFER_CHUNK);
198     wc->outbuffer_offset = 0;
199 }
200
201 int wo_flush(WCLIENT wc)
202 {
203     int wrote, towrite;
204
205     if (!(wc->outbuffer_offset))
206         return 0;
207     towrite = wc->outbuffer_offset;
208     wc->outbuffer_offset = 0;
209     for (;;)
210     {
211         int w_chunk;
212
213         w_chunk = towrite;
214         wrote = write(wc->lineout, wc->outbuffer + wc->outbuffer_offset,
215             w_chunk);
216         if (wrote <= 0)
217         {
218             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write response");
219             return -1;
220         }
221         gw_log (GW_LOG_DEBUG, mod, "wrote %d bytes", wrote);
222         if (wc->cache_fd >= 0)
223             if (write(wc->cache_fd, wc->outbuffer + wc->outbuffer_offset,
224                 towrite) < 0)
225             {   
226                 gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write cache");
227                 return -1;
228             }
229         towrite -= wrote;
230         if (!towrite)
231             break;
232         wc->outbuffer_offset += wrote;
233     }
234     wc->outbuffer_offset = 0;
235     return 0;
236 }
237
238 int wo_overflow(WCLIENT wc, char ch)
239 {
240     gw_log (GW_LOG_DEBUG, mod, "wo_overflow");
241     if (wc->save_level)
242         wo_expand (wc, 0);
243     else if (wo_flush(wc) < 0)
244         return -1;
245     return wo_putc(wc, ch);
246 }
247
248 int wo_finish(WCLIENT wc)
249 {
250     int fd;
251     gw_log (GW_LOG_DEBUG, mod, "wo_finish");
252
253     wo_putc (wc, 0);
254     if (wo_flush(wc) < 0)
255         return -1;
256
257     fd = open (wc->wf_serverf, O_RDONLY);
258     if (fd != -1)
259     {
260         struct flock area;
261         area.l_type = F_RDLCK;
262         area.l_whence = SEEK_SET;
263         area.l_start = 0L;
264         area.l_len = 0L;
265         fcntl (fd, F_SETLKW, &area);
266         close (fd);
267     }
268     close(wc->lineout);
269     wc->lineout = -1;
270     if (wc->cache_fd >= 0)
271     {
272         close(wc->cache_fd);
273         wc->cache_fd = -1;
274     }
275     return 0;
276 }
277
278 static void descramble(char *t, const char *o)
279 {
280     unsigned int v;
281
282     while (*o)
283     {
284         if (*o == '%' && isxdigit(*(o + 1)) && isxdigit(*(o + 2)))
285         {
286             sscanf(o + 1, "%2x", &v);
287             o += 3;
288             if (v == '+')
289                 *t = ' ';
290             else
291                 *t = (char) v;
292             t++;
293         }
294         else
295         {
296             if (*o == '+')
297                 *t = ' ';
298             else
299                 *t = *o;
300             t++;
301             o++;
302         }
303     }
304     *t = '\0';
305 }
306
307 static void decode_form(wform_data *form, char *buf)
308 {
309     int i = 0;
310     char *p;
311     char tmp[512];
312
313     while (*buf)
314     {
315         for (p = form[i].name; *buf && *buf != '='; buf++)
316             *(p++) = *buf;
317         *p = '\0';
318         if (*buf)
319             buf++;
320         for (p = tmp; *buf && *buf != '&'; buf++)
321             *(p++) = *buf;
322         *p = '\0';
323         descramble(form[i].value, tmp);
324         if (*buf)
325             buf++;
326         i++;
327     }
328     *form[i].name = '\0';
329 }
330
331 char *wgetval(WCLIENT wc, char *name)
332 {
333     int i;
334
335     for (i = 0; *wc->wf_data[i].name; i++)
336         if (!strcmp(name, wc->wf_data[i].name))
337             return wc->wf_data[i].value;
338     return 0;
339 }
340
341 int wproto_process(WCLIENT wc, int timeout)
342 {
343     int toread, rs, level;
344     char combuf[COMBUF], *p,*t;
345     fd_set input;
346     struct timeval to, *top;
347
348     for (;;)
349     {
350         gw_log (GW_LOG_DEBUG, mod, "process waiting for input.");
351         if (timeout > 0)
352         {
353             to.tv_usec = 0;
354             to.tv_sec = timeout;
355             top = &to;
356         }
357         else
358             top = 0;
359         FD_ZERO(&input);
360         FD_SET(wc->linein, &input);
361         /* go through select handle list */
362         while ((rs = select(wc->linein + 1, &input, 0, 0, top)) < 0 &&
363             errno == EINTR)
364             ;
365         if (rs < 0)
366         {
367             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "select");
368             return -1;
369         }
370         if (rs == 0)
371         {
372             gw_log (GW_LOG_STAT, mod, 
373                     "select %d second timeout.",
374                     timeout);
375             unlink (wc->wf_serverp);
376             return 0;
377         }
378         if (read(wc->linein, &toread, sizeof(toread)) < sizeof(toread))
379         {
380             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "wp_proc:len read failed");
381             exit(1);
382         }
383         toread -= sizeof(toread);
384         if (read(wc->linein, combuf, toread) < toread)
385         {
386             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "wp_proc: data read failed");
387             exit(1);
388         }
389         p = combuf;
390         for (t = wc->wf_serverp; (*t = *p); t++, p++);
391         p++;
392         for (t = wc->wf_serverf; (*t = *p); t++, p++);
393         p++;
394         for (t = wc->wf_script; *p && *p != '/'; t++, p++)
395             *t = *p;
396         *t = '\0';
397         if (*p)
398             p++;
399         decode_form (wc->wf_parms_var, p);
400         for (t = wc->wf_parms; (*t = *p); t++, p++);
401         p++;
402         p++;         /* we don't deal with envvars yet */
403         wc->raw_data = p;
404         decode_form(wc->wf_data, p);
405         if (wc->lineout < 0)
406         {
407             gw_log (GW_LOG_DEBUG, mod, "open %s", wc->wf_serverp);
408             if ((wc->lineout = open(wc->wf_serverp, O_WRONLY)) < 0)
409             {
410                 gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open %s", 
411                         wc->wf_serverp);
412                 exit(1);
413             }
414         }
415         /* look in cache only if request carries no forms data. */
416         if (!*wc->wf_data[0].name && (level = wproto_findcache(wc,
417             wc->wf_parms)) >= 0)
418         {
419             gw_log (GW_LOG_DEBUG, mod, "wproto_dumpcache");
420             wproto_dumpcache(wc, level);
421             wo_finish(wc);
422             
423         }
424         else
425         {
426             return 1;
427         }
428     }
429 }
430
431 WCLIENT wproto_init (const char *fifoDir, const char *prog)
432 {
433     char *val, path2[256];
434     wclient_data *newp;
435
436     gw_log (GW_LOG_DEBUG, mod, "wproto_init");
437     close(1);    /* release us from the wserver */
438     if (!(newp = malloc(sizeof(*newp))))
439     {
440         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "malloc");
441         exit (1);
442     }
443     if (!(val = getenv ("GWID")))
444     {
445         gw_log (GW_LOG_FATAL, mod, "GWID not set");
446         exit (1);
447     }
448     if (!(newp->prog = malloc (strlen(prog)+1)))
449     {
450         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "malloc");
451         exit (1);
452     }
453     strcpy (newp->prog, prog);
454     newp->fifoDir = fifoDir;
455     newp->id = atoi (val);
456     sprintf(newp->path, "%s/clt%d", newp->fifoDir, newp->id);
457     if (mkfifo(newp->path, 0666 | S_IFIFO) < 0)
458         gw_log (GW_LOG_WARN|GW_LOG_ERRNO, mod, "mkfifo(%s)", newp->path);
459     gw_log (GW_LOG_DEBUG, mod, "Synchronizing with server.");
460     sprintf(path2, "%s/srv%d", newp->fifoDir, getppid());
461     if ((newp->lineout = open(path2, O_WRONLY)) < 0)
462     {
463         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open out %s", path2);
464         exit(1);
465     }
466     if (write(newp->lineout, "OK", 2) < 2)
467     {
468         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write");
469         exit(1);
470     }
471     gw_log (GW_LOG_DEBUG, mod, "Synchronized.");
472     if ((newp->linein = open(newp->path, O_RDONLY)) < 0)
473     {
474         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open input %s", newp->path);
475         exit(1);
476     }
477     gw_log (GW_LOG_DEBUG, mod, "init. linein=%d lineout=%d",
478             newp->linein, newp->lineout);
479     /* we put a handle on this so we get a blocking read when no peer */
480     if (open(newp->path, O_WRONLY | O_NDELAY) < 0)
481     {
482         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open dummy %s", newp->path);
483         exit(1);
484     }
485     newp->outbuffer = 0;
486     newp->outbuffer_size = 0;
487     newp->outbuffer_offset = 0;
488     newp->cache_level = -1;
489     newp->cache_fd = -1;
490     newp->save_level = 0;
491     return newp;
492 }
493
494 static void wproto_uncache(WCLIENT wc, int level)
495 {
496     for (;wc->cache_level >= level; wc->cache_level--)
497         unlink(wc->cache[wc->cache_level].path);
498 }
499
500 void wproto_terminate(WCLIENT wc)
501 {
502     free (wc->prog);
503     close(wc->linein);
504     unlink(wc->path);
505     wproto_uncache(wc, 0);
506     free(wc);
507 }
508
509 int wproto_cache(WCLIENT wc, int level)
510 {
511     cache_data *p;
512
513     if (level > wc->cache_level + 1)
514     {
515         gw_log (GW_LOG_FATAL, mod, "Illegal cache level increment.");
516         exit(1);
517     }
518     wproto_uncache(wc, level);
519     p = &wc->cache[++wc->cache_level];
520     sprintf(p->path, "%s/csh%d.%d", wc->fifoDir, wc->id, level);
521     if ((wc->cache_fd = open(p->path, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0)
522     {
523         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "open %s", p->path);
524         return -1;
525     }
526     strcpy(p->name, wc->wf_parms);
527     return 0;
528 }
529
530 static int wproto_findcache(WCLIENT wc, char *name)
531 {
532     int i;
533
534     for (i = 0; i <= wc->cache_level; i++)
535         if (!strcmp(wc->cache[i].name, name))
536             return i;
537     return -1;
538 }
539
540 int wproto_save_push (WCLIENT wc)
541 {
542     return wc->outbuffer_offset;
543     wc->save_level++;
544 }
545
546 char *wproto_save_pop (WCLIENT wc, int offset)
547 {
548     char *cp;
549     if (!wc->save_level)
550         return NULL;
551     --(wc->save_level);
552     assert (offset <= wc->outbuffer_offset);
553     cp = wc->outbuffer + offset;
554     wc->outbuffer[wc->outbuffer_offset] = '\0';
555     wc->outbuffer_offset = offset;
556     return cp;
557 }
558
559 static int wproto_dumpcache(WCLIENT wc, int level)
560 {
561     int fd, rd;
562
563     gw_log (GW_LOG_STAT, mod, "Using Cache: %s", wc->cache[level].name);
564     if ((fd = open(wc->cache[level].path, O_RDONLY)) < 0)
565     {
566         gw_log (GW_LOG_FATAL, mod, "open (R) %s", wc->cache[level].path);
567         return -1;
568     }
569     while ((rd = read(fd, wc->outbuffer, OUTBUFFER_CHUNK)) > 0)
570         if (write(wc->lineout, wc->outbuffer, rd) < rd)
571         {
572             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "write toline");
573             return -1;
574         }
575     if (rd < 0)
576     {
577         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, mod, "read");
578         return -1;
579     }
580     wproto_uncache(wc, level + 1);
581     return 0;
582 }