Check for dead webcit child process before each transaction.
[citadel.git] / webcit / context_loop.c
1 /*
2  * context_loop.c
3  *
4  * This is the other half of the webserver.  It handles the task of hooking
5  * up HTTP requests with the session they belong to, using HTTP cookies to
6  * keep track of things.  If the HTTP request doesn't belong to any currently
7  * active session, a new session is spawned.
8  *
9  * $Id$
10  */
11
12 #include <ctype.h>
13 #include <stdlib.h>
14 #ifdef HAVE_UNISTD_H
15 #include <unistd.h>
16 #endif
17 #include <stdio.h>
18 #ifdef HAVE_FCNTL_H
19 #include <fcntl.h>
20 #endif
21 #include <signal.h>
22 #include <sys/types.h>
23 #include <sys/wait.h>
24 #include <sys/socket.h>
25 #ifdef HAVE_SYS_TIME_H
26 #include <sys/time.h>
27 #endif
28 #ifdef HAVE_LIMITS_H
29 #include <limits.h>
30 #endif
31 #include <netinet/in.h>
32 #include <netdb.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <stdarg.h>
36 #include <pthread.h>
37 #include <signal.h>
38 #include "webcit.h"
39 #include "webserver.h"
40
41 /*
42  * We keep one of these around for each active session
43  */
44 struct wc_session {
45         struct wc_session *next;        /* Next session in list */
46         int session_id;                 /* Session ID */
47         pid_t webcit_pid;               /* PID of the webcit process */
48         int inpipe[2];                  /* Data from webserver to session */
49         int outpipe[2];                 /* Data from session to webserver */
50         pthread_mutex_t critter;        /* Critical section uses pipes */
51         };
52
53 struct wc_session *SessionList = NULL;
54 extern const char *defaulthost;
55 extern const char *defaultport;
56
57 /* Only one thread may manipulate SessionList at a time... */
58 pthread_mutex_t MasterCritter;
59
60 int GenerateSessionID(void) {
61         return getpid();
62         }
63
64
65 void gets0(int fd, char buf[]) {
66
67         buf[0] = 0;
68         do {
69                 buf[strlen(buf)+1] = 0;
70                 read(fd, &buf[strlen(buf)], 1);
71                 } while (buf[strlen(buf)-1] >= 32);
72         buf[strlen(buf)-1] = 0;
73         }
74
75 /*
76  * Collapse multiple cookies on one line
77  */
78 void req_gets(int sock, char *buf, char *hold) {
79         int a;
80
81         if (strlen(hold)==0) {
82                 client_gets(sock, buf);
83                 }
84         else {
85                 strcpy(buf, hold);
86                 }
87         strcpy(hold, "");
88
89         if (!strncasecmp(buf, "Cookie: ", 8)) {
90                 for (a=0; a<strlen(buf); ++a) if (buf[a]==';') {
91                         sprintf(hold, "Cookie: %s", &buf[a+1]);
92                         buf[a]=0;
93                         while (isspace(hold[8])) strcpy(&hold[8], &hold[9]);
94                         return;
95                         }
96                 }
97         }
98
99 /*
100  * Grab a lock on the session, so other threads don't try to access
101  * the pipes at the same time.
102  */
103 static void lock_session(struct wc_session *session) {
104         printf("Locking session %d...\n", session->session_id);
105         pthread_mutex_lock(&session->critter);
106         printf("   ...got lock\n");
107         }
108
109 /*
110  * Let go of the lock.
111  */
112 static void unlock_session(struct wc_session *session) {
113         printf("Unlocking.\n");
114         pthread_mutex_unlock(&session->critter);
115         }
116
117 /*
118  * lingering_close() a`la Apache. see
119  * http://www.apache.org/docs/misc/fin_wait_2.html for rationale
120  */
121
122 static int lingering_close(int fd) {
123         char buf[256];
124         int i;
125         fd_set set;
126         struct timeval tv, start;
127
128         gettimeofday(&start, NULL);
129         shutdown(fd, 1);
130         do {
131                 do {
132                         gettimeofday(&tv, NULL);
133                         tv.tv_sec = SLEEPING - (tv.tv_sec - start.tv_sec);
134                         tv.tv_usec = start.tv_usec - tv.tv_usec;
135                         if (tv.tv_usec < 0) {
136                                 tv.tv_sec--;
137                                 tv.tv_usec += 1000000;
138                                 }
139                                 
140                         FD_ZERO(&set);
141                         FD_SET(fd, &set);
142                         i = select(fd + 1, &set, NULL, NULL, &tv);
143                         } while (i == -1 && errno == EINTR);
144
145                 if (i <= 0)
146                         break;
147
148                 i = read(fd, buf, sizeof buf);
149                 } while (i != 0 && (i != -1 || errno == EINTR));
150
151         return close(fd);
152         }
153
154 /*
155  * Remove a session context from the list
156  */
157 void remove_session(struct wc_session *TheSession) {
158         struct wc_session *sptr;
159
160         printf("Removing session.\n");
161         pthread_mutex_lock(&MasterCritter);
162
163         if (SessionList==TheSession) {
164                 SessionList = SessionList->next;
165                 }
166         else {
167                 for (sptr=SessionList; sptr!=NULL; sptr=sptr->next) {
168                         if (sptr->next == TheSession) {
169                                 sptr->next = TheSession->next;
170                                 }
171                         }
172                 }
173
174         close(TheSession->inpipe[1]);
175         close(TheSession->outpipe[0]);
176         unlock_session(TheSession);
177         free(TheSession);
178
179         pthread_mutex_unlock(&MasterCritter);
180         }
181
182 /*
183  * This loop gets called once for every HTTP connection made to WebCit.
184  */
185 void *context_loop(int sock) {
186         char (*req)[256];
187         char buf[256], hold[256];
188         int num_lines = 0;
189         int a;
190         int f;
191         int desired_session = 0;
192         char str_session[256];
193         struct wc_session *sptr;
194         struct wc_session *TheSession;
195         int ContentLength;
196         int CloseSession = 0;
197
198         if ((req = malloc((long)sizeof(char[256][256]))) == NULL) {
199                 sprintf(buf, "Can't malloc buffers; dropping connection.\n");
200                 fprintf(stderr, "%s", buf);
201                 write(sock, buf, strlen(buf));
202                 close (sock);
203                 pthread_exit(NULL);
204                 }
205
206         printf("Reading request from socket %d\n", sock);
207
208         /*
209          * Find out what it is that the web browser is asking for
210          */
211         ContentLength = 0;
212         do {
213                 req_gets(sock, buf, hold);
214                 if (!strncasecmp(buf, "Cookie: wc_session=", 19)) {
215                         desired_session = atoi(&buf[19]);
216                         }
217                 if (!strncasecmp(buf, "Content-length: ", 16)) {
218                         ContentLength = atoi(&buf[16]);
219                         }
220                 strcpy(&req[num_lines++][0], buf);
221                 } while(strlen(buf)>0);
222
223         /*
224          * See if there's an existing session open with the desired ID
225          */
226         TheSession = NULL;
227         if (desired_session != 0) {
228                 pthread_mutex_lock(&MasterCritter);
229                 for (sptr=SessionList; sptr!=NULL; sptr=sptr->next) {
230                         if (sptr->session_id == desired_session) {
231                                 TheSession = sptr;
232                                 lock_session(TheSession);
233                                 }
234                         }
235                 pthread_mutex_unlock(&MasterCritter);
236                 }
237
238         /*
239          * Before we trumpet to the universe that the session we're looking
240          * for actually exists, check first to make sure it's still there.
241          */
242         if (TheSession != NULL) {
243                 if (kill(TheSession->webcit_pid, 0)) {
244                         printf("   Session is *DEAD* !!\n");
245                         remove_session(TheSession);
246                         TheSession = NULL;
247                         }
248                 }
249
250         /*
251          * Create a new session if we have to
252          */
253         if (TheSession == NULL) {
254                 printf("Creating a new session\n");
255                 pthread_mutex_lock(&MasterCritter);
256                 TheSession = (struct wc_session *)
257                         malloc(sizeof(struct wc_session));
258                 TheSession->session_id = GenerateSessionID();
259                 pipe(TheSession->inpipe);
260                 pipe(TheSession->outpipe);
261                 pthread_mutex_init(&TheSession->critter, NULL);
262                 lock_session(TheSession);
263                 TheSession->next = SessionList;
264                 SessionList = TheSession;
265                 pthread_mutex_unlock(&MasterCritter);
266                 sprintf(str_session, "%d", TheSession->session_id);
267                 f = fork();
268                 if (f > 0) TheSession->webcit_pid = f;
269                 
270                 fflush(stdout); fflush(stdin);
271                 if (f==0) {
272
273                         /* Hook stdio to the ends of the pipe we're using */
274                         dup2(TheSession->inpipe[0], 0);
275                         dup2(TheSession->outpipe[1], 1);
276
277                         /* Close the ends of the pipes that we're not using */
278                         close(TheSession->inpipe[1]);
279                         close(TheSession->outpipe[0]);
280         
281                         /* Close the HTTP socket in this pid; don't need it */
282                         close(sock);
283
284                         /* Run the actual WebCit session */
285                         execlp("./webcit", "webcit", str_session, defaulthost,
286                                defaultport, NULL);
287
288                         /* Simple page to display if exec fails */
289                         printf("HTTP/1.0 404 WebCit Failure\n\n");
290                         printf("Server: %s\n", SERVER);
291                         printf("X-WebCit-Session: close\n");
292                         printf("Content-type: text/html\n");
293                         printf("Content-length: 76\n");
294                         printf("\n");
295                         printf("<HTML><HEAD><TITLE>Error</TITLE></HEAD>\n");
296                         printf("<BODY BACKGROUND=\"/image&name=background\" TEXT=\"#000000\" LINK=\"#004400\">execlp() failed</BODY></HTML>\n");
297                         exit(0);
298                         }
299                 else {
300                         /* Close the ends of the pipes that we're not using */
301                         close(TheSession->inpipe[0]);
302                         close(TheSession->outpipe[1]);
303                         }
304                 }
305
306         /* 
307          * Send the request to the appropriate session...
308          */
309         printf("   Writing %d lines of command\n", num_lines);
310         printf("%s\n", &req[0][0]);
311         for (a=0; a<num_lines; ++a) {
312                 write(TheSession->inpipe[1], &req[a][0], strlen(&req[a][0]));
313                 write(TheSession->inpipe[1], "\n", 1);
314                 }
315         printf("   Writing %d bytes of content\n", ContentLength);
316         while (ContentLength > 0) {
317                 a = ContentLength;
318                 if (a > sizeof buf) a = sizeof buf;
319                 if (!client_read(sock, buf, a)) goto end;
320                 if (write(TheSession->inpipe[1], buf, a) != a) goto end;
321                 ContentLength -= a;
322                 }
323
324         /*
325          * ...and get the response.
326          */
327         printf("   Reading response\n");
328         ContentLength = 0;
329         do {
330                 gets0(TheSession->outpipe[0], buf);
331                 write(sock, buf, strlen(buf));
332                 write(sock, "\n", 1);
333                 if (!strncasecmp(buf, "Content-length: ", 16))
334                         ContentLength = atoi(&buf[16]);
335                 if (!strcasecmp(buf, "X-WebCit-Session: close")) {
336                         CloseSession = 1;
337                         }
338                 } while (strlen(buf) > 0);
339
340         printf("   Reading %d bytes of content\n", ContentLength);
341         while(ContentLength--) {
342                 read(TheSession->outpipe[0], buf, 1);
343                 write(sock, buf, 1);
344                 }
345
346         /*
347          * If the last response included a "close session" directive,
348          * remove the context now.
349          */
350         if (CloseSession) {
351                 remove_session(TheSession);
352                 }
353         else {
354 end:            unlock_session(TheSession);
355                 }
356         free(req);
357
358
359         /*
360          * Now our HTTP connection is done.  It would be relatively easy
361          * to support HTTP/1.1 "persistent" connections by looping back to
362          * the top of this function.  For now, we'll just close.
363          */
364         printf("   Remember, the pid is %d\n", TheSession->webcit_pid);
365         printf("   Closing socket %d ... ret=%d\n", sock,
366                lingering_close(sock));
367
368         /*
369          * The thread handling this HTTP connection is now finished.
370          */
371         pthread_exit(NULL);
372         }