743c4f3839e683278f04493090e14445b17fd528
[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 sessions 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 started.
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 /* Only one thread may manipulate SessionList at a time... */
42 pthread_mutex_t SessionListMutex;
43
44 struct wcsession *SessionList = NULL;
45
46 pthread_key_t MyConKey;                         /* TSD key for MySession() */
47
48 void do_housekeeping(void)
49 {
50         struct wcsession *sptr, *ss, *session_to_kill;
51
52         do {
53                 session_to_kill = NULL;
54                 pthread_mutex_lock(&SessionListMutex);
55                 for (sptr = SessionList; sptr != NULL; sptr = sptr->next) {
56
57                         /* Kill idle sessions */
58                         if ((time(NULL) - (sptr->lastreq)) >
59                            (time_t) WEBCIT_TIMEOUT) {
60                                 sptr->killthis = 1;
61                         }
62
63                         /* Remove sessions flagged for kill */
64                         if (sptr->killthis) {
65
66                                 lprintf(3, "Destroying session %d\n",
67                                         sptr->wc_session);
68
69                                 /* remove session from linked list */
70                                 if (sptr == SessionList) {
71                                         SessionList = SessionList->next;
72                                 }
73                                 else for (ss=SessionList;ss!=NULL;ss=ss->next) {
74                                         if (ss->next == sptr) {
75                                                 ss->next = ss->next->next;
76                                         }
77                                 }
78
79                                 session_to_kill = sptr;
80                                 goto BREAKOUT;
81                         }
82                 }
83 BREAKOUT:       pthread_mutex_unlock(&SessionListMutex);
84
85                 if (session_to_kill != NULL) {
86                         pthread_mutex_lock(&session_to_kill->SessionMutex);
87                         close(session_to_kill->serv_sock);
88                         if (session_to_kill->preferences != NULL) {
89                                 free(session_to_kill->preferences);
90                         }
91                         pthread_mutex_unlock(&session_to_kill->SessionMutex);
92                         free(session_to_kill);
93                 }
94
95         } while (session_to_kill != NULL);
96 }
97
98
99 /* 
100  * Wake up occasionally and clean house
101  */
102 void housekeeping_loop(void)
103 {
104         while (1) {
105                 sleeeeeeeeeep(HOUSEKEEPING);
106                 do_housekeeping();
107         }
108 }
109
110
111 /*
112  * Generate a unique WebCit session ID (which is not the same thing as the
113  * Citadel session ID).
114  *
115  * FIXME ... ensure that session number is truly unique
116  *
117  */
118 int GenerateSessionID(void)
119 {
120         static int seq = (-1);
121
122         if (seq < 0) {
123                 seq = (int) time(NULL);
124         }
125                 
126         return ++seq;
127 }
128
129
130 /*
131  * Collapse multiple cookies on one line
132  */
133 int req_gets(int sock, char *buf, char *hold)
134 {
135         int a;
136
137         if (strlen(hold) == 0) {
138                 strcpy(buf, "");
139                 a = client_gets(sock, buf);
140                 if (a<1) return(-1);
141         } else {
142                 strcpy(buf, hold);
143         }
144         strcpy(hold, "");
145
146         if (!strncasecmp(buf, "Cookie: ", 8)) {
147                 for (a = 0; a < strlen(buf); ++a)
148                         if (buf[a] == ';') {
149                                 sprintf(hold, "Cookie: %s", &buf[a + 1]);
150                                 buf[a] = 0;
151                                 while (isspace(hold[8]))
152                                         strcpy(&hold[8], &hold[9]);
153                                 return(0);
154                         }
155         }
156
157         return(0);
158 }
159
160 /*
161  * lingering_close() a`la Apache. see
162  * http://www.apache.org/docs/misc/fin_wait_2.html for rationale
163  */
164
165 int lingering_close(int fd)
166 {
167         char buf[SIZ];
168         int i;
169         fd_set set;
170         struct timeval tv, start;
171
172         gettimeofday(&start, NULL);
173         shutdown(fd, 1);
174         do {
175                 do {
176                         gettimeofday(&tv, NULL);
177                         tv.tv_sec = SLEEPING - (tv.tv_sec - start.tv_sec);
178                         tv.tv_usec = start.tv_usec - tv.tv_usec;
179                         if (tv.tv_usec < 0) {
180                                 tv.tv_sec--;
181                                 tv.tv_usec += 1000000;
182                         }
183                         FD_ZERO(&set);
184                         FD_SET(fd, &set);
185                         i = select(fd + 1, &set, NULL, NULL, &tv);
186                 } while (i == -1 && errno == EINTR);
187
188                 if (i <= 0)
189                         break;
190
191                 i = read(fd, buf, sizeof buf);
192         } while (i != 0 && (i != -1 || errno == EINTR));
193
194         return close(fd);
195 }
196
197
198
199 /*
200  * Check for bogus requests coming from (for example) brain-dead
201  * Windoze boxes that are infected with the latest worm-of-the-week.
202  * If we detect one of these, bail out without bothering our Citadel
203  * server.
204  */
205 int is_bogus(char *http_cmd) {
206
207         if (!strncasecmp(http_cmd, "GET /scripts/root.exe", 21)) return(1);
208         if (!strncasecmp(http_cmd, "GET /c/winnt", 12)) return(2);
209         if (!strncasecmp(http_cmd, "GET /MSADC/", 11)) return(3);
210
211         return(0);      /* probably ok */
212 }
213
214
215
216 /*
217  * This loop gets called once for every HTTP connection made to WebCit.  At
218  * this entry point we have an HTTP socket with a browser allegedly on the
219  * other end, but we have not yet bound to a WebCit session.
220  *
221  * The job of this function is to locate the correct session and bind to it,
222  * or create a session if necessary and bind to it, then run the WebCit
223  * transaction loop.  Afterwards, we unbind from the session.  When this
224  * function returns, the worker thread is then free to handle another
225  * transaction.
226  */
227 void context_loop(int sock)
228 {
229         struct httprequest *req = NULL;
230         struct httprequest *last = NULL;
231         struct httprequest *hptr;
232         char buf[SIZ], hold[SIZ];
233         int desired_session = 0;
234         int got_cookie = 0;
235         struct wcsession *TheSession, *sptr;
236
237         /*
238          * Find out what it is that the web browser is asking for
239          */
240         memset(hold, 0, sizeof(hold));
241         do {
242                 if (req_gets(sock, buf, hold) < 0) return;
243
244                 if (!strncasecmp(buf, "Cookie: webcit=", 15)) {
245                         cookie_to_stuff(&buf[15], &desired_session,
246                                 NULL, NULL, NULL);
247                         got_cookie = 1;
248                 }
249
250                 hptr = (struct httprequest *)
251                         malloc(sizeof(struct httprequest));
252                 if (req == NULL)
253                         req = hptr;
254                 else
255                         last->next = hptr;
256                 hptr->next = NULL;
257                 last = hptr;
258
259                 strcpy(hptr->line, buf);
260
261         } while (strlen(buf) > 0);
262
263         strcpy(buf, req->line);
264         lprintf(5, "HTTP: %s\n", buf);
265
266         /* Check for bogus requests */
267         if (is_bogus(buf)) goto bail;
268
269         /*
270          * If requesting a non-root page, there should already be a cookie
271          * set.  If there isn't, the client browser has cookies turned off
272          * (or doesn't support them) and we have to barf & bail.
273          */
274         if (!strncasecmp(buf, "GET ", 4)) strcpy(buf, &buf[4]);
275         else if (!strncasecmp(buf, "HEAD ", 5)) strcpy(buf, &buf[5]);
276         else if (!strncasecmp(buf, "POST ", 5)) strcpy(buf, &buf[5]);
277         if (buf[1]==' ') buf[1]=0;
278
279         /*
280          * While we're at it, gracefully handle requests for the
281          * robots.txt file...
282          */
283         if (!strncasecmp(buf, "/robots.txt", 11)) {
284                 strcpy(req->line, "GET /static/robots.txt"
285                                 "?force_close_session=yes HTTP/1.0");
286         }
287
288         /* Do the non-root-cookie check now. */
289         else if ( (strcmp(buf, "/"))
290                 && (strncasecmp(buf, "/listsub", 8))
291                 && (got_cookie == 0)) {
292                 strcpy(req->line, "GET /static/nocookies.html"
293                                 "?force_close_session=yes HTTP/1.0");
294         }
295
296
297         /*
298          * See if there's an existing session open with the desired ID
299          */
300         TheSession = NULL;
301         if (desired_session != 0) {
302                 pthread_mutex_lock(&SessionListMutex);
303                 for (sptr = SessionList; sptr != NULL; sptr = sptr->next) {
304                         if (sptr->wc_session == desired_session) {
305                                 TheSession = sptr;
306                         }
307                 }
308                 pthread_mutex_unlock(&SessionListMutex);
309         }
310
311         /*
312          * Create a new session if we have to
313          */
314         if (TheSession == NULL) {
315                 lprintf(3, "Creating a new session\n");
316                 TheSession = (struct wcsession *)
317                         malloc(sizeof(struct wcsession));
318                 memset(TheSession, 0, sizeof(struct wcsession));
319                 TheSession->wc_session = GenerateSessionID();
320                 pthread_mutex_init(&TheSession->SessionMutex, NULL);
321
322                 pthread_mutex_lock(&SessionListMutex);
323                 TheSession->next = SessionList;
324                 SessionList = TheSession;
325                 pthread_mutex_unlock(&SessionListMutex);
326         }
327
328         /*
329          * A future improvement might be to check the session integrity
330          * at this point before continuing.
331          */
332
333         /*
334          * Bind to the session and perform the transaction
335          */
336         pthread_mutex_lock(&TheSession->SessionMutex);          /* bind */
337         pthread_setspecific(MyConKey, (void *)TheSession);
338         TheSession->http_sock = sock;
339         TheSession->lastreq = time(NULL);                       /* log */
340         session_loop(req);              /* perform the requested transaction */
341         pthread_mutex_unlock(&TheSession->SessionMutex);        /* unbind */
342
343         /* Free the request buffer */
344 bail:   while (req != NULL) {
345                 hptr = req->next;
346                 free(req);
347                 req = hptr;
348         }
349 }