11a997c1bbb2527a28225ba06bd2fe7cc13877cc
[citadel.git] / webcit / context_loop.c
1 /*
2  * $Id$
3  */
4 /**
5  * \defgroup WebServerII some of the webserver stuff.
6  * This is the other half of the webserver.  It handles the task of hooking
7  * up HTTP requests with the sessions they belong to, using HTTP cookies to
8  * keep track of things.  If the HTTP request doesn't belong to any currently
9  * active session, a new session is started.
10  *
11  */
12 /*@{*/
13 #include "webcit.h"
14 #include "webserver.h"
15
16 /** Only one thread may manipulate SessionList at a time... */
17 pthread_mutex_t SessionListMutex;
18
19 struct wcsession *SessionList = NULL; /**< our sessions ????*/
20
21 pthread_key_t MyConKey;         /**< TSD key for MySession() */
22
23
24 /**
25  * \brief free the memory used for viewing atachments
26  * \param sess the session object to destroy
27  */
28 void free_attachments(struct wcsession *sess) {
29         struct wc_attachment *att;
30
31         while (sess->first_attachment != NULL) {
32                 att = sess->first_attachment;
33                 sess->first_attachment = sess->first_attachment->next;
34                 free(att->data);
35                 free(att);
36         }
37 }
38
39 /**
40  * \brief what??????
41  */
42 void do_housekeeping(void)
43 {
44         struct wcsession *sptr, *ss;
45         struct wcsession *sessions_to_kill = NULL;
46         int num_sessions = 0;
47         static int num_threads = MIN_WORKER_THREADS;
48
49         /**
50          * Lock the session list, moving any candidates for euthanasia into
51          * a separate list.
52          */
53         pthread_mutex_lock(&SessionListMutex);
54         num_sessions = 0;
55         for (sptr = SessionList; sptr != NULL; sptr = sptr->next) {
56                 ++num_sessions;
57
58                 /** Kill idle sessions */
59                 if ((time(NULL) - (sptr->lastreq)) >
60                    (time_t) WEBCIT_TIMEOUT) {
61                         sptr->killthis = 1;
62                 }
63
64                 /** Remove sessions flagged for kill */
65                 if (sptr->killthis) {
66
67                         /** remove session from linked list */
68                         if (sptr == SessionList) {
69                                 SessionList = SessionList->next;
70                         }
71                         else for (ss=SessionList;ss!=NULL;ss=ss->next) {
72                                 if (ss->next == sptr) {
73                                         ss->next = ss->next->next;
74                                 }
75                         }
76
77                         sptr->next = sessions_to_kill;
78                         sessions_to_kill = sptr;
79                 }
80         }
81         pthread_mutex_unlock(&SessionListMutex);
82
83         /**
84          * Now free up and destroy the culled sessions.
85          */
86         while (sessions_to_kill != NULL) {
87                 lprintf(3, "Destroying session %d\n", sessions_to_kill->wc_session);
88                 pthread_mutex_lock(&sessions_to_kill->SessionMutex);
89                 close(sessions_to_kill->serv_sock);
90                 close(sessions_to_kill->chat_sock);
91                 if (sessions_to_kill->preferences != NULL) {
92                         free(sessions_to_kill->preferences);
93                 }
94                 if (sessions_to_kill->cache_fold != NULL) {
95                         free(sessions_to_kill->cache_fold);
96                 }
97                 free_attachments(sessions_to_kill);
98                 pthread_mutex_unlock(&sessions_to_kill->SessionMutex);
99                 sptr = sessions_to_kill->next;
100                 free(sessions_to_kill);
101                 sessions_to_kill = sptr;
102                 --num_sessions;
103         }
104
105         /**
106          * If there are more sessions than threads, then we should spawn
107          * more threads ... up to a predefined maximum.
108          */
109         while ( (num_sessions > num_threads)
110               && (num_threads <= MAX_WORKER_THREADS) ) {
111                 spawn_another_worker_thread();
112                 ++num_threads;
113                 lprintf(3, "There are %d sessions and %d threads active.\n",
114                         num_sessions, num_threads);
115         }
116 }
117
118
119 /**
120  * \brief Wake up occasionally and clean house
121  */
122 void housekeeping_loop(void)
123 {
124         while (1) {
125                 sleeeeeeeeeep(HOUSEKEEPING);
126                 do_housekeeping();
127         }
128 }
129
130
131 /**
132  * \brief Create a Session id
133  * Generate a unique WebCit session ID (which is not the same thing as the
134  * Citadel session ID).
135  *
136  * \todo FIXME ... ensure that session number is truly unique
137  *
138  */
139 int GenerateSessionID(void)
140 {
141         static int seq = (-1);
142
143         if (seq < 0) {
144                 seq = (int) time(NULL);
145         }
146                 
147         return ++seq;
148 }
149
150
151 /**
152  * \brief Collapse multiple cookies on one line
153  * \param sock a socket?
154  * \param buf some bunch of chars?
155  * \param hold hold what?
156  */
157 int req_gets(int sock, char *buf, char *hold)
158 {
159         int a;
160
161         if (strlen(hold) == 0) {
162                 strcpy(buf, "");
163                 a = client_getln(sock, buf, SIZ);
164                 if (a<1) return(-1);
165         } else {
166                 safestrncpy(buf, hold, SIZ);
167         }
168         strcpy(hold, "");
169
170         if (!strncasecmp(buf, "Cookie: ", 8)) {
171                 for (a = 0; a < strlen(buf); ++a)
172                         if (buf[a] == ';') {
173                                 sprintf(hold, "Cookie: %s", &buf[a + 1]);
174                                 buf[a] = 0;
175                                 while (isspace(hold[8]))
176                                         strcpy(&hold[8], &hold[9]);
177                                 return(0);
178                         }
179         }
180
181         return(0);
182 }
183
184 /**
185  * \brief close some fd for some reason???
186  * \param fd the fd to close??????
187  * lingering_close() a`la Apache. see
188  * http://www.apache.org/docs/misc/fin_wait_2.html for rationale
189  */
190
191 int lingering_close(int fd)
192 {
193         char buf[SIZ];
194         int i;
195         fd_set set;
196         struct timeval tv, start;
197
198         gettimeofday(&start, NULL);
199         shutdown(fd, 1);
200         do {
201                 do {
202                         gettimeofday(&tv, NULL);
203                         tv.tv_sec = SLEEPING - (tv.tv_sec - start.tv_sec);
204                         tv.tv_usec = start.tv_usec - tv.tv_usec;
205                         if (tv.tv_usec < 0) {
206                                 tv.tv_sec--;
207                                 tv.tv_usec += 1000000;
208                         }
209                         FD_ZERO(&set);
210                         FD_SET(fd, &set);
211                         i = select(fd + 1, &set, NULL, NULL, &tv);
212                 } while (i == -1 && errno == EINTR);
213
214                 if (i <= 0)
215                         break;
216
217                 i = read(fd, buf, sizeof buf);
218         } while (i != 0 && (i != -1 || errno == EINTR));
219
220         return close(fd);
221 }
222
223
224
225 /**
226  * \brief sanity requests
227  * Check for bogus requests coming from (for example) brain-dead
228  * Windoze boxes that are infected with the latest worm-of-the-week.
229  * If we detect one of these, bail out without bothering our Citadel
230  * server.
231  * \param http_cmd the cmd to check
232  */
233 int is_bogus(char *http_cmd) {
234
235         if (!strncasecmp(http_cmd, "GET /scripts/root.exe", 21)) return(1);
236         if (!strncasecmp(http_cmd, "GET /c/winnt", 12)) return(2);
237         if (!strncasecmp(http_cmd, "GET /MSADC/", 11)) return(3);
238
239         return(0);      /* probably ok */
240 }
241
242
243
244 /**
245  * \brief handle one request
246  * This loop gets called once for every HTTP connection made to WebCit.  At
247  * this entry point we have an HTTP socket with a browser allegedly on the
248  * other end, but we have not yet bound to a WebCit session.
249  *
250  * The job of this function is to locate the correct session and bind to it,
251  * or create a session if necessary and bind to it, then run the WebCit
252  * transaction loop.  Afterwards, we unbind from the session.  When this
253  * function returns, the worker thread is then free to handle another
254  * transaction.
255  * \param sock the socket we will put our answer to
256  */
257 void context_loop(int sock)
258 {
259         struct httprequest *req = NULL;
260         struct httprequest *last = NULL;
261         struct httprequest *hptr;
262         char buf[SIZ], hold[SIZ];
263         int desired_session = 0;
264         int got_cookie = 0;
265         int gzip_ok = 0;
266         struct wcsession *TheSession, *sptr;
267         char httpauth_string[1024];
268         char httpauth_user[1024];
269         char httpauth_pass[1024];
270         char accept_language[256];
271         char *ptr = NULL;
272         int session_is_new = 0;
273
274         strcpy(httpauth_string, "");
275         strcpy(httpauth_user, DEFAULT_HTTPAUTH_USER);
276         strcpy(httpauth_pass, DEFAULT_HTTPAUTH_PASS);
277
278         /**
279          * Find out what it is that the web browser is asking for
280          */
281         memset(hold, 0, sizeof(hold));
282         do {
283                 if (req_gets(sock, buf, hold) < 0) return;
284
285                 /**
286                  * Can we compress?
287                  */
288                 if (!strncasecmp(buf, "Accept-encoding:", 16)) {
289                         if (strstr(&buf[16], "gzip")) {
290                                 gzip_ok = 1;
291                         }
292                 }
293
294                 /**
295                  * Browser-based sessions use cookies for session authentication
296                  */
297                 if (!strncasecmp(buf, "Cookie: webcit=", 15)) {
298                         cookie_to_stuff(&buf[15], &desired_session,
299                                 NULL, 0, NULL, 0, NULL, 0);
300                         got_cookie = 1;
301                 }
302
303                 /**
304                  * GroupDAV-based sessions use HTTP authentication
305                  */
306                 if (!strncasecmp(buf, "Authorization: Basic ", 21)) {
307                         CtdlDecodeBase64(httpauth_string, &buf[21], strlen(&buf[21]));
308                         extract_token(httpauth_user, httpauth_string, 0, ':', sizeof httpauth_user);
309                         extract_token(httpauth_pass, httpauth_string, 1, ':', sizeof httpauth_pass);
310                 }
311
312                 if (!strncasecmp(buf, "If-Modified-Since: ", 19)) {
313                         if_modified_since = httpdate_to_timestamp(&buf[19]);
314                 }
315
316                 if (!strncasecmp(buf, "Accept-Language: ", 17)) {
317                         safestrncpy(accept_language, &buf[17], sizeof accept_language);
318                 }
319
320                 /**
321                  * Read in the request
322                  */
323                 hptr = (struct httprequest *)
324                         malloc(sizeof(struct httprequest));
325                 if (req == NULL)
326                         req = hptr;
327                 else
328                         last->next = hptr;
329                 hptr->next = NULL;
330                 last = hptr;
331
332                 safestrncpy(hptr->line, buf, sizeof hptr->line);
333
334         } while (strlen(buf) > 0);
335
336         /**
337          * If the request is prefixed by "/webcit" then chop that off.  This
338          * allows a front end web server to forward all /webcit requests to us
339          * while still using the same web server port for other things.
340          */
341         
342         ptr = strstr(req->line, " /webcit ");   /*< Handle "/webcit" */
343         if (ptr != NULL) {
344                 strcpy(ptr+2, ptr+8);
345         }
346
347         ptr = strstr(req->line, " /webcit");    /*< Handle "/webcit/" */
348         if (ptr != NULL) {
349                 strcpy(ptr+1, ptr+8);
350         }
351
352         /** Begin parsing the request. */
353
354         safestrncpy(buf, req->line, sizeof buf);
355         lprintf(5, "HTTP: %s\n", buf);
356
357         /** Check for bogus requests */
358         if (is_bogus(buf)) goto bail;
359
360         /**
361          * Strip out the method, leaving the URL up front...
362          */
363         remove_token(buf, 0, ' ');
364         if (buf[1]==' ') buf[1]=0;
365
366         /**
367          * While we're at it, gracefully handle requests for the
368          * robots.txt and favicon.ico files.
369          */
370         if (!strncasecmp(buf, "/robots.txt", 11)) {
371                 strcpy(req->line, "GET /static/robots.txt"
372                                 "?force_close_session=yes HTTP/1.1");
373         }
374         else if (!strncasecmp(buf, "/favicon.ico", 12)) {
375                 strcpy(req->line, "GET /static/favicon.ico");
376         }
377
378         /**
379          * These are the URL's which may be executed without a
380          * session cookie already set.  If it's not one of these,
381          * force the session to close because cookies are
382          * probably disabled on the client browser.
383          */
384         else if ( (strcmp(buf, "/"))
385                 && (strncasecmp(buf, "/listsub", 8))
386                 && (strncasecmp(buf, "/freebusy", 9))
387                 && (strncasecmp(buf, "/do_logout", 10))
388                 && (strncasecmp(buf, "/groupdav", 9))
389                 && (strncasecmp(buf, "/static", 7))
390                 && (strncasecmp(buf, "/rss", 4))
391                 && (got_cookie == 0)) {
392                 strcpy(req->line, "GET /static/nocookies.html"
393                                 "?force_close_session=yes HTTP/1.1");
394         }
395
396         /**
397          * See if there's an existing session open with the desired ID or user/pass
398          */
399         TheSession = NULL;
400
401         if (TheSession == NULL) {
402                 pthread_mutex_lock(&SessionListMutex);
403                 for (sptr = SessionList; sptr != NULL; sptr = sptr->next) {
404
405                         /** If HTTP-AUTH, look for a session with matching credentials */
406                         if ( (strlen(httpauth_user) > 0)
407                            &&(!strcasecmp(sptr->httpauth_user, httpauth_user))
408                            &&(!strcasecmp(sptr->httpauth_pass, httpauth_pass)) ) {
409                                 TheSession = sptr;
410                         }
411
412                         /** If cookie-session, look for a session with matching session ID */
413                         if ( (desired_session != 0) && (sptr->wc_session == desired_session)) {
414                                 TheSession = sptr;
415                         }
416
417                 }
418                 pthread_mutex_unlock(&SessionListMutex);
419         }
420
421         /**
422          * Create a new session if we have to
423          */
424         if (TheSession == NULL) {
425                 lprintf(3, "Creating a new session\n");
426                 TheSession = (struct wcsession *)
427                         malloc(sizeof(struct wcsession));
428                 memset(TheSession, 0, sizeof(struct wcsession));
429                 TheSession->serv_sock = (-1);
430                 TheSession->chat_sock = (-1);
431                 TheSession->wc_session = GenerateSessionID();
432                 strcpy(TheSession->httpauth_user, httpauth_user);
433                 strcpy(TheSession->httpauth_pass, httpauth_pass);
434                 pthread_mutex_init(&TheSession->SessionMutex, NULL);
435                 pthread_mutex_lock(&SessionListMutex);
436                 TheSession->next = SessionList;
437                 SessionList = TheSession;
438                 pthread_mutex_unlock(&SessionListMutex);
439                 session_is_new = 1;
440         }
441
442         /**
443          * A future improvement might be to check the session integrity
444          * at this point before continuing.
445          */
446
447         /**
448          * Bind to the session and perform the transaction
449          */
450         pthread_mutex_lock(&TheSession->SessionMutex);          /*< bind */
451         pthread_setspecific(MyConKey, (void *)TheSession);
452         TheSession->http_sock = sock;
453         TheSession->lastreq = time(NULL);                       /*< log */
454         TheSession->gzip_ok = gzip_ok;
455 #ifdef ENABLE_NLS
456         if (session_is_new) {
457                 httplang_to_locale(accept_language);
458         }
459         go_selected_language();                         /*< set locale */
460 #endif
461         session_loop(req);                              /*< do transaction */
462 #ifdef ENABLE_NLS
463         stop_selected_language();                       /*< unset locale */
464 #endif
465         pthread_mutex_unlock(&TheSession->SessionMutex);        /*< unbind */
466
467         /** Free the request buffer */
468 bail:   while (req != NULL) {
469                 hptr = req->next;
470                 free(req);
471                 req = hptr;
472         }
473
474         /**
475          * Free up any session-local substitution variables which
476          * were set during this transaction
477          */
478         clear_local_substs();
479 }
480 /*@}*/