Updated for YAZ 1.7. HTML output tidy up. Added LOC target.
[egate.git] / www / wcgi.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: wcgi.c,v $
44  * Revision 1.20  2001/02/26 14:32:36  adam
45  * Updated for YAZ 1.7. HTML output tidy up. Added LOC target.
46  *
47  * Revision 1.19  1997/01/24 13:13:10  adam
48  * Implemnted egw_source and added a "raw" option to the URL.
49  * Fixed a bug in the buffering system of wproto; the macro wo_putc could
50  * override memory if it was the first HTML generating function called.
51  *
52  * Revision 1.18  1996/03/14 11:48:37  adam
53  * New function egw_prog that returns name of shell.
54  *
55  * Revision 1.17  1996/02/26  10:36:15  adam
56  * Better error handling when (re)spawn of the shell fails.
57  *
58  * Revision 1.16  1996/02/12  10:10:29  adam
59  * Resource/config system used by the gateway.
60  *
61  * Revision 1.15  1996/01/26  09:02:20  adam
62  * Open of client FIFO called with O_NDELAY when reconnecting to shell
63  * in order to prevent serious lock if previous shell died without
64  * unlinking client FIFO.
65  *
66  * Revision 1.14  1996/01/12  13:08:06  adam
67  * CGI script passes name of lock file to the shell. The server will not close
68  * the response FIFO until this file becomes unlocked. This method handles
69  * cancel operations much better.
70  *
71  * Revision 1.13  1996/01/12  10:05:17  adam
72  * If script name ends with ';' HTTP/GET/Expires will be defined.
73  * The cgi interface only reads final handshake if response from
74  * server (shell) was zero-terminated [If it isn't it probably died].
75  *
76  * Revision 1.12  1996/01/09  10:46:49  adam
77  * New defines: LOGDIR/EGWDIR/CGIDIR set in Makefile.
78  *
79  * Revision 1.11  1996/01/08  08:42:19  adam
80  * Handles method GET.
81  *
82  * Revision 1.10  1996/01/05  16:21:20  adam
83  * Bug fix: shell (wproto) sometimes closed server FIFO before cgi
84  * program opened it - solution: cgi sends OK when response has been read.
85  *
86  * Revision 1.9  1995/12/20  16:31:33  adam
87  * Bug fix: shell might terminate even though new request was initiated
88  * by the cgi interface program.
89  * Work on more simple user interface and Europagate buttons.
90  *
91  * Revision 1.8  1995/11/08  16:14:35  adam
92  * Many improvements and bug fixes.
93  * First version that ran on dtbsun.
94  *
95  * Revision 1.7  1995/11/08  12:42:18  adam
96  * Added descriptive text field in target info.
97  * Added authentication field in target info.
98  *
99  * Revision 1.6  1995/11/06  17:44:22  adam
100  * State reestablised when shell restarts. History of previous
101  * result sets.
102  *
103  * Revision 1.5  1995/11/06  10:51:15  adam
104  * End of response marker in response from wsh/wproto to wcgi.
105  * Shells are respawned when necessary.
106  *
107  * Revision 1.4  1995/11/02  16:35:37  adam
108  * Bug fixes and select on FIFOs in wcgi - doesn't really work!
109  *
110  * Revision 1.3  1995/10/31  16:56:24  adam
111  * Record presentation.
112  *
113  * Revision 1.2  1995/10/23  16:55:36  adam
114  * A lot of changes - really.
115  *
116  * Revision 1.1  1995/10/20  11:49:25  adam
117  * First version of www gateway.
118  *
119  */
120
121 #include <stdio.h>
122 #include <stdlib.h>
123 #include <string.h>
124 #include <unistd.h>
125 #include <fcntl.h>
126 #include <sys/stat.h>
127 #include <sys/time.h>
128 #include <sys/types.h>
129 #ifdef AIX
130 #include <sys/select.h>
131 #endif
132
133 #include <gw-db.h>
134 #include <gw-res.h>
135 #include "wproto.h"
136
137 static char *prog = "cgi";
138
139 static char serverp[256] = {'\0'};
140 static char serverf[256] = {'\0'};
141 static GW_DB gw_db = NULL;
142
143 static void fatal(char *p)
144 {
145     printf("Content-type: text/html\n\n<HTML><HEAD><TITLE>Server Failure</TITLE></HEAD>\n");
146     printf("<BODY>%s</BODY>\n", p);
147     if (gw_db)
148         gw_db_close (gw_db);
149     if (*serverp)
150         unlink (serverp);
151     if (*serverf)
152         unlink (serverf);
153     exit(0);
154 }
155
156 static int spawn (char *sprog, int id)
157 {
158     int r, fd;
159     char path[256];
160     char envstr[80];
161
162     sprintf (envstr, "GWID=%d", id);
163     putenv (envstr);
164     sprintf(path, "%s/%s", EGWDIR, sprog);
165     switch(r = fork())
166     {
167         case -1: 
168             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "fork"); 
169             fatal ("Internal error in server");
170         case 0: 
171             close (0);
172             close (1);
173             close (2);
174             gw_log (GW_LOG_DEBUG, prog, "execl %s", path);
175             execl (path, sprog, 0); 
176             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "execl %s", path);
177             fd = open (serverp, O_WRONLY);
178             if (fd >= 0)
179             {
180                 write (fd, "FA", 2);
181                 close (fd);
182             }
183             exit(1);
184         default: 
185             return r;
186     }
187 }
188
189 #if 0
190 static void print_environ (void)
191 {
192     extern char **environ;
193     int i;
194
195     for (i = 0; environ[i]; i++)
196         gw_log (GW_LOG_DEBUG, prog, "e: %s", environ[i]);
197 }
198 #endif
199
200 /*
201  * NOTE: In the (perhaps odd) terminology used within this software,
202  * the 'server' is the present program, which is executed by the httpd
203  * server. The 'client' is the process running outside.
204  * Protocol is long(len)<serverfifo>\0<extrapath>\0<envvars>\0<INFO>\0
205  */
206 int main()
207 {
208     char clientp[256], *path_info, *p, *operation, *t;
209     char combuf[COMBUF];
210     const char *fifoDir;
211     GwRes cgiRes;
212     int serverf_fd = -1;
213     int linein = -1, lineout, data, gw_id;
214
215     if (chdir (EGWDIR))
216         fatal ("egwcgi: Couldn't change directory to " EGWDIR);
217     gw_log_init ("egw");
218     gw_log_file (GW_LOG_ALL, "egwcgi_log");
219 #if 0
220     gw_log_level (GW_LOG_ALL);
221 #endif
222     gw_log_session (getpid());
223     gw_log (GW_LOG_STAT, prog, "Europagate www cgi server");
224     cgiRes = gw_res_init ();
225     gw_res_merge (cgiRes, "egw.res");
226
227 #if 1
228     gw_log_level (gw_log_mask_str (
229                   gw_res_get (cgiRes, "log.level", "default")));
230 #endif
231     fifoDir = gw_res_get (cgiRes, "fifo.dir", "/tmp/egw");
232     
233     /* Create fifo directory if it doesn't exist already */
234     if (access(fifoDir, R_OK|W_OK) < 0 && mkdir(fifoDir, 0777) < 0)
235     {
236         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "Failed to create %s",
237                 fifoDir);
238         fatal("Internal error in server.");
239     }
240     /* Delete server FIFO if it does exist */
241     sprintf(serverp, "%s/srv%d", fifoDir, getpid());
242     if (access(serverp, R_OK|W_OK) == 0)
243     {
244         if (unlink(serverp) < 0)
245         {
246             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog,
247                     "Failed to delete stale fifo.");
248             fatal("Internal error in server.");
249         }
250         else
251             gw_log (GW_LOG_WARN, prog, "Removed stale server fifo.");
252     }
253     /* Make server FIFO */
254     if (mkfifo(serverp, 0666 | S_IFIFO) < 0)
255     {
256         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "mkfifo(%s)", serverp);
257         fatal("Internal error in server.");
258     }
259     /* The httpd server must pass PATH_INFO */
260     if (!(path_info = getenv("PATH_INFO")))
261     {
262         gw_log (GW_LOG_FATAL, prog, "Must set PATH_INFO.");
263         fatal("Internal error in server.");
264     }
265     /* Create lock file that ensures the server (shell) doesn't */
266     /* terminate before we have read the whole response */
267     sprintf (serverf, "%s/srf%d", fifoDir, getpid ());
268     gw_log (GW_LOG_DEBUG, prog, "open w %s", serverf);
269     serverf_fd = open (serverf, O_WRONLY|O_CREAT|O_TRUNC, 0666);
270     if (serverf_fd == -1)
271     {
272         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "open (%s)", serverf);
273         fatal("Internal error in server.");
274     }
275     else
276     {
277         struct flock area;
278         area.l_type = F_WRLCK;
279         area.l_whence = SEEK_SET;
280         area.l_start = 0L;
281         area.l_len = 0L;
282         fcntl (serverf_fd, F_SETLK, &area);
283     }
284     /* Read first part of path_info. Either it is a numeric session id */
285     /* or it is the name of a backend shell. */
286     operation = ++path_info;
287     while (*path_info && *path_info != '/')
288         path_info++;
289     if (*path_info)
290         *(path_info++) = '\0';
291     gw_log (GW_LOG_DEBUG, prog, "www.db open");
292     if (!(gw_db = gw_db_open (EGWDIR "/www.db", 1, 1)))
293     {
294         gw_log (GW_LOG_FATAL, prog, "gw_db_open");
295         exit (1);
296     }
297     gw_log (GW_LOG_DEBUG, prog, "www.db ok");
298     /* Is operation a backend shell (new session) ? */
299     if ((gw_id = atoi(operation)) <= 0)
300     {
301         int r;
302         char gw_id_str[16];
303
304         /* Get new unique id */
305         gw_id = gw_db_seq_no (gw_db);
306         sprintf (gw_id_str, "%d", gw_id);
307        
308         /* Spawn backend shell (server) */ 
309         spawn(operation, gw_id);
310         r = gw_db_insert (gw_db, gw_id_str, strlen(gw_id_str)+1,
311                           operation, strlen(operation)+1);
312         if (r)
313         {
314             gw_log (GW_LOG_FATAL, prog, "gw_db_insert: %d", r);
315             gw_db_close (gw_db);
316             exit (1);
317         }
318         gw_log (GW_LOG_DEBUG, prog, "Synchronizing with client");
319         if ((linein = open(serverp, O_RDONLY)) < 0)
320         {
321             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "open r %s", serverp);
322             fatal("Internal error in server.");
323         }
324         if (read(linein, combuf, 2) < 2 || strcmp(combuf, "OK"))
325         {
326             gw_log (GW_LOG_FATAL, prog, "Failed to synchronize with client");
327             fatal("Internal error in server");
328         }
329         gw_log (GW_LOG_DEBUG, prog, "Synchronized");
330         sprintf(clientp, "%s/clt%d", fifoDir, gw_id);
331         gw_log (GW_LOG_DEBUG, prog, "open w %s", clientp);
332         lineout = open (clientp, O_WRONLY);
333     }
334     else /* A session is continued */
335     {
336         sprintf(clientp, "%s/clt%d", fifoDir, gw_id);
337         gw_log (GW_LOG_DEBUG, prog, "open w|n %s", clientp);
338         /* Open the FIFO in O_NDELAY-mode: This prevents blocking */
339         /* even though the shell died without unlinking the FIFO */
340         /* On the other hand, if the shell is running, it will never */
341         /* close this FIFO */
342         lineout = open (clientp, O_WRONLY|O_NDELAY);
343     }
344     /* If open of clientp failed, the shell is not running, so we */
345     /* invoke it again */
346     if (lineout < 0)
347     {
348         char gw_id_str[16];
349         void *sprog;
350         size_t sprog_size;
351         int r;
352
353         sprintf (gw_id_str, "%d", gw_id);
354         r = gw_db_lookup (gw_db, gw_id_str, strlen(gw_id_str)+1,  
355                           &sprog, &sprog_size);
356         if (r != 1)
357         {
358             gw_log (GW_LOG_FATAL, prog, "gw_db_lookup %s", gw_id_str);
359             fatal("Internal error in server");
360         }
361         gw_log (GW_LOG_DEBUG|GW_LOG_ERRNO, prog, "open r %s restart", clientp);
362         spawn (sprog, gw_id);
363         gw_log (GW_LOG_DEBUG, prog, "Synchronizing with client");
364         if ((linein = open(serverp, O_RDONLY)) < 0)
365         {
366             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "open %s", serverp);
367             fatal("Internal error in server");
368         }
369         if (read(linein, combuf, 2) < 2 || strcmp(combuf, "OK"))
370         {
371             gw_log (GW_LOG_FATAL, prog, "Failed to synchronize with client.");
372             fatal("Internal error in server");
373         }
374         gw_log (GW_LOG_DEBUG, prog, "Synchronized.");
375         if ((lineout = open(clientp, O_WRONLY)) < 0)
376         {
377             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "%s", clientp);
378             fatal("Internal error in server");
379         }
380     }
381     gw_db_close (gw_db);
382     gw_log (GW_LOG_DEBUG, prog, "Decoding user data");
383     p = combuf + sizeof(data);
384     strcpy (p, serverp);
385     p += strlen (p) + 1;
386     strcpy (p, serverf);
387     p += strlen (p) + 1;
388     strcpy (p, path_info);
389     gw_log (GW_LOG_STAT, prog, "P:%s", p);
390     p += strlen(p) + 1;
391     *(p++) = '\0';               /* no envvars tranferred at present */
392     if ((t = getenv("CONTENT_LENGTH")) && (data = atoi(t)) > 0)
393     {
394         int j, i = 0;
395         while (i < data)
396         {
397             j = read(0, p + i, data - i);
398             if (j == -1)
399             {
400                 gw_log (GW_LOG_ERRNO|GW_LOG_FATAL, prog,
401                         "Failed to read input");
402                 fatal("Internal error in server");
403             }
404             else if (j == 0)
405             {
406                 gw_log (GW_LOG_ERRNO, prog, "Failed to read input");
407                 fatal("Internal error in server");
408             }
409             i += j;
410         }
411     }
412     else if ((t = getenv("QUERY_STRING")))
413     {
414         strcpy (p, t);
415         data = strlen(p);
416     }
417     p[data] = '\0';
418     gw_log (GW_LOG_DEBUG, prog, "C:%s", p);
419     p += data+1;
420     data = (p - combuf);
421     memcpy(combuf, &data, sizeof(data));
422     gw_log (GW_LOG_DEBUG, prog, "Writing data");
423     if (write(lineout, combuf, data) < data)
424     {
425         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "write");
426         fatal("Internal server error");
427     }
428     if (linein < 0)
429     {
430         gw_log (GW_LOG_DEBUG, prog, "open r %s", serverp);
431         if ((linein = open(serverp, O_RDONLY)) < 0)
432         {
433             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "open %s", serverp);
434             fatal("Internal error in server");
435         }
436     }
437     gw_log (GW_LOG_DEBUG, prog, "Reading response");
438
439     while ((data = read(linein, combuf, COMBUF)) > 0)
440     {
441         gw_log (GW_LOG_DEBUG, prog, "Got %d bytes", data);
442         if (combuf[data-1] == '\0')
443             break;
444         if (write(1, combuf, data) < data)
445         {
446             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "write");
447             exit (1);
448         }
449     }
450     if (data < 0)
451     {
452         gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "read");
453         exit (1);
454     }
455     if (data > 0)
456     {
457         if (close (serverf_fd))
458         {
459             gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "close %s", serverf);
460         }
461         if (--data > 0)
462         {
463             if (write(1, combuf, data) < data)
464             {
465                 gw_log (GW_LOG_FATAL|GW_LOG_ERRNO, prog, "write");
466                 exit (1);
467             }
468         }
469     }
470     gw_log (GW_LOG_DEBUG, prog, "Cleaning up.");
471     unlink (serverf);
472     close(linein);
473     unlink(serverp);
474     close(lineout);
475     return 0;
476 }