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.
12 #include "webserver.h"
14 /* Only one thread may manipulate SessionList at a time... */
15 pthread_mutex_t SessionListMutex
;
17 wcsession
*SessionList
= NULL
; /**< our sessions ????*/
19 pthread_key_t MyConKey
; /**< TSD key for MySession() */
23 void DestroySession(wcsession
**sessions_to_kill
)
25 close((*sessions_to_kill
)->serv_sock
);
26 close((*sessions_to_kill
)->chat_sock
);
28 // if ((*sessions_to_kill)->preferences != NULL) {
29 // free((*sessions_to_kill)->preferences);
32 if ((*sessions_to_kill
)->cache_fold
!= NULL
) {
33 free((*sessions_to_kill
)->cache_fold
);
35 DeleteServInfo(&((*sessions_to_kill
)->serv_info
));
36 DeleteHash(&((*sessions_to_kill
)->attachments
));
37 free_march_list((*sessions_to_kill
));
38 DeleteHash(&((*sessions_to_kill
)->hash_prefs
));
39 DeleteHash(&((*sessions_to_kill
)->IconBarSettings
));
40 DeleteHash(&((*sessions_to_kill
)->ServCfg
));
41 FreeStrBuf(&((*sessions_to_kill
)->ReadBuf
));
42 FreeStrBuf(&((*sessions_to_kill
)->UrlFragment1
));
43 FreeStrBuf(&((*sessions_to_kill
)->UrlFragment2
));
44 FreeStrBuf(&((*sessions_to_kill
)->UrlFragment3
));
45 FreeStrBuf(&((*sessions_to_kill
)->UrlFragment4
));
46 FreeStrBuf(&((*sessions_to_kill
)->WBuf
));
47 FreeStrBuf(&((*sessions_to_kill
)->HBuf
));
48 FreeStrBuf(&((*sessions_to_kill
)->CLineBuf
));
49 FreeStrBuf(&((*sessions_to_kill
)->wc_username
));
50 FreeStrBuf(&((*sessions_to_kill
)->wc_fullname
));
51 FreeStrBuf(&((*sessions_to_kill
)->wc_password
));
52 FreeStrBuf(&((*sessions_to_kill
)->wc_roomname
));
53 FreeStrBuf(&((*sessions_to_kill
)->httpauth_user
));
54 FreeStrBuf(&((*sessions_to_kill
)->httpauth_pass
));
55 free((*sessions_to_kill
));
56 (*sessions_to_kill
) = NULL
;
59 void shutdown_sessions(void)
63 for (sptr
= SessionList
; sptr
!= NULL
; sptr
= sptr
->next
) {
68 void do_housekeeping(void)
71 wcsession
*sessions_to_kill
= NULL
;
73 static int num_threads
= MIN_WORKER_THREADS
;
76 * Lock the session list, moving any candidates for euthanasia into
79 pthread_mutex_lock(&SessionListMutex
);
81 for (sptr
= SessionList
; sptr
!= NULL
; sptr
= sptr
->next
) {
84 /** Kill idle sessions */
85 if ((time(NULL
) - (sptr
->lastreq
)) >
86 (time_t) WEBCIT_TIMEOUT
) {
90 /** Remove sessions flagged for kill */
93 /** remove session from linked list */
94 if (sptr
== SessionList
) {
95 SessionList
= SessionList
->next
;
97 else for (ss
=SessionList
;ss
!=NULL
;ss
=ss
->next
) {
98 if (ss
->next
== sptr
) {
99 ss
->next
= ss
->next
->next
;
103 sptr
->next
= sessions_to_kill
;
104 sessions_to_kill
= sptr
;
107 pthread_mutex_unlock(&SessionListMutex
);
110 * Now free up and destroy the culled sessions.
112 while (sessions_to_kill
!= NULL
) {
113 lprintf(3, "Destroying session %d\n", sessions_to_kill
->wc_session
);
114 pthread_mutex_lock(&sessions_to_kill
->SessionMutex
);
115 pthread_mutex_unlock(&sessions_to_kill
->SessionMutex
);
116 sptr
= sessions_to_kill
->next
;
118 DestroySession(&sessions_to_kill
);
119 sessions_to_kill
= sptr
;
124 * If there are more sessions than threads, then we should spawn
125 * more threads ... up to a predefined maximum.
127 while ( (num_sessions
> num_threads
)
128 && (num_threads
<= MAX_WORKER_THREADS
) ) {
129 spawn_another_worker_thread();
131 lprintf(3, "There are %d sessions and %d threads active.\n",
132 num_sessions
, num_threads
);
138 * Wake up occasionally and clean house
140 void housekeeping_loop(void)
143 sleeeeeeeeeep(HOUSEKEEPING
);
150 * Create a Session id
151 * Generate a unique WebCit session ID (which is not the same thing as the
152 * Citadel session ID).
154 int GenerateSessionID(void)
156 static int seq
= (-1);
159 seq
= (int) time(NULL
);
166 * Collapse multiple cookies on one line
168 int ReqGetStrBuf(int *sock
, StrBuf
*Target
, StrBuf
*buf
)
171 return ClientGetLine(sock
, Target
, buf
);
177 * lingering_close() a`la Apache. see
178 * http://www.apache.org/docs/misc/fin_wait_2.html for rationale
180 int lingering_close(int fd
)
185 struct timeval tv
, start
;
187 gettimeofday(&start
, NULL
);
191 gettimeofday(&tv
, NULL
);
192 tv
.tv_sec
= SLEEPING
- (tv
.tv_sec
- start
.tv_sec
);
193 tv
.tv_usec
= start
.tv_usec
- tv
.tv_usec
;
194 if (tv
.tv_usec
< 0) {
196 tv
.tv_usec
+= 1000000;
200 i
= select(fd
+ 1, &set
, NULL
, NULL
, &tv
);
201 } while (i
== -1 && errno
== EINTR
);
206 i
= read(fd
, buf
, sizeof buf
);
207 } while (i
!= 0 && (i
!= -1 || errno
== EINTR
));
215 * Look for commonly-found probes of malware such as worms, viruses, trojans, and Microsoft Office.
216 * Short-circuit these requests so we don't have to send them through the full processing loop.
218 int is_bogus(StrBuf
*http_cmd
) {
221 const char *bogus_prefixes
[] = {
222 "/scripts/root.exe", /* Worms and trojans and viruses, oh my! */
225 "/_vti", /* Broken Microsoft DAV implementation */
226 "/MSOffice", /* Stoopid MSOffice thinks everyone is IIS */
227 "/nonexistenshit" /* Exploit found in the wild January 2009 */
230 url
= ChrPtr(http_cmd
);
231 if (IsEmptyStr(url
)) return(1);
234 max
= sizeof(bogus_prefixes
) / sizeof(char *);
236 for (i
=0; i
<max
; ++i
) {
237 if (!strncasecmp(url
, bogus_prefixes
[i
], strlen(bogus_prefixes
[i
]))) {
242 return(0); /* probably ok */
246 const char *nix(void *vptr
) {return ChrPtr( (StrBuf
*)vptr
);}
251 * This loop gets called once for every HTTP connection made to WebCit. At
252 * this entry point we have an HTTP socket with a browser allegedly on the
253 * other end, but we have not yet bound to a WebCit session.
255 * The job of this function is to locate the correct session and bind to it,
256 * or create a session if necessary and bind to it, then run the WebCit
257 * transaction loop. Afterwards, we unbind from the session. When this
258 * function returns, the worker thread is then free to handle another
261 void context_loop(int *sock
)
264 int desired_session
= 0;
267 wcsession
*TheSession
, *sptr
;
268 char httpauth_string
[1024];
269 char httpauth_user
[1024];
270 char httpauth_pass
[1024];
271 int session_is_new
= 0;
275 StrBuf
*Buf
, *Line
, *LastLine
, *HeaderName
, *ReqLine
, *ReqType
, *HTTPVersion
;
276 StrBuf
*accept_language
= NULL
;
277 const char *pch
, *pchs
, *pche
;
278 HashList
*HTTPHeaders
;
280 strcpy(httpauth_string
, "");
281 strcpy(httpauth_user
, DEFAULT_HTTPAUTH_USER
);
282 strcpy(httpauth_pass
, DEFAULT_HTTPAUTH_PASS
);
285 * Find out what it is that the web browser is asking for
287 HeaderName
= NewStrBuf();
290 HTTPHeaders
= NewHash(1, NULL
);
293 * Read in the request
298 if (ReqGetStrBuf(sock
, Line
, Buf
) < 0) return;
300 LineLen
= StrLength(Line
);
311 /* Do we need to Unfold? */
312 if ((LastLine
!= NULL
) &&
313 (isspace(*ChrPtr(Line
)))) {
314 pch
= pchs
= ChrPtr(Line
);
315 pche
= pchs
+ StrLength(Line
);
316 while (isspace(*pch
) && (pch
< pche
))
318 StrBufCutLeft(Line
, pch
- pchs
);
319 StrBufAppendBuf(LastLine
, Line
, 0);
324 StrBufSanitizeAscii(Line
, '§');
325 StrBufExtract_token(HeaderName
, Line
, 0, ':');
328 pch
= pchs
+ StrLength(HeaderName
) + 1;
329 pche
= pchs
+ StrLength(Line
);
330 while (isspace(*pch
) && (pch
< pche
))
332 StrBufCutLeft(Line
, pch
- pchs
);
334 StrBufUpCase(HeaderName
);
335 Put(HTTPHeaders
, SKEY(HeaderName
), Line
, HFreeStrBuf
);
337 } while (LineLen
> 0);
338 FreeStrBuf(&HeaderName
);
340 /*/// dbg_PrintHash(HTTPHeaders, nix, NULL); */
346 if (GetHash(HTTPHeaders
, HKEY("ACCEPT-ENCODING"), &vLine
) &&
348 buf
= ChrPtr((StrBuf
*)vLine
);
349 if (strstr(&buf
[16], "gzip")) {
355 * Browser-based sessions use cookies for session authentication
357 if (GetHash(HTTPHeaders
, HKEY("COOKIE"), &vLine
) &&
359 cookie_to_stuff(vLine
, &desired_session
,
365 * GroupDAV-based sessions use HTTP authentication
367 if (GetHash(HTTPHeaders
, HKEY("AUTHORIZATION"), &vLine
) &&
369 Line
= (StrBuf
*)vLine
;
370 if (strncasecmp(ChrPtr(Line
), "Basic", 5) == 0) {
371 StrBufCutLeft(Line
, 6);
372 CtdlDecodeBase64(httpauth_string
, ChrPtr(Line
), StrLength(Line
));
373 extract_token(httpauth_user
, httpauth_string
, 0, ':', sizeof httpauth_user
);
374 extract_token(httpauth_pass
, httpauth_string
, 1, ':', sizeof httpauth_pass
);
377 lprintf(1, "Authentication scheme not supported! [%s]\n", ChrPtr(Line
));
380 if (GetHash(HTTPHeaders
, HKEY("IF-MODIFIED-SINCE"), &vLine
) &&
382 if_modified_since
= httpdate_to_timestamp((StrBuf
*)vLine
);
385 if (GetHash(HTTPHeaders
, HKEY("ACCEPT-LANGUAGE"), &vLine
) &&
387 accept_language
= (StrBuf
*) vLine
;
391 ReqType
= NewStrBuf();
392 HTTPVersion
= NewStrBuf();
393 StrBufExtract_token(HTTPVersion
, ReqLine
, 2, ' ');
394 StrBufExtract_token(ReqType
, ReqLine
, 0, ' ');
395 StrBufCutLeft(ReqLine
, StrLength(ReqType
) + 1);
396 StrBufCutRight(ReqLine
, StrLength(HTTPVersion
) + 1);
399 * If the request is prefixed by "/webcit" then chop that off. This
400 * allows a front end web server to forward all /webcit requests to us
401 * while still using the same web server port for other things.
403 if ( (StrLength(ReqLine
) >= 8) && (strstr(ChrPtr(ReqLine
), "/webcit/")) ) {
404 StrBufCutLeft(ReqLine
, 7);
407 /* Begin parsing the request. */
409 if ((strncmp(ChrPtr(ReqLine
), "/sslg", 5) != 0) &&
410 (strncmp(ChrPtr(ReqLine
), "/static/", 8) != 0) &&
411 (strncmp(ChrPtr(ReqLine
), "/tiny_mce/", 10) != 0) &&
412 (strncmp(ChrPtr(ReqLine
), "/wholist_section", 16) != 0) &&
413 (strstr(ChrPtr(ReqLine
), "wholist_section") == NULL
)) {
415 lprintf(5, "HTTP: %s %s %s\n", ChrPtr(ReqType
), ChrPtr(ReqLine
), ChrPtr(HTTPVersion
));
420 /** Check for bogus requests */
421 if ((StrLength(HTTPVersion
) == 0) ||
422 (StrLength(ReqType
) == 0) ||
424 StrBufPlain(ReqLine
, HKEY("/404 HTTP/1.1"));
425 StrBufPlain(ReqType
, HKEY("GET"));
427 FreeStrBuf(&HTTPVersion
);
430 * While we're at it, gracefully handle requests for the
431 * robots.txt and favicon.ico files.
433 if (!strncasecmp(ChrPtr(ReqLine
), "/robots.txt", 11)) {
435 HKEY("/static/robots.txt"
436 "?force_close_session=yes HTTP/1.1"));
437 StrBufPlain(ReqType
, HKEY("GET"));
439 else if (!strncasecmp(ChrPtr(ReqLine
), "/favicon.ico", 12)) {
440 StrBufPlain(ReqLine
, HKEY("/static/favicon.ico"));
441 StrBufPlain(ReqType
, HKEY("GET"));
445 * These are the URL's which may be executed without a
446 * session cookie already set. If it's not one of these,
447 * force the session to close because cookies are
448 * probably disabled on the client browser.
450 else if ( (StrLength(ReqLine
) > 1 )
451 && (strncasecmp(ChrPtr(ReqLine
), "/listsub", 8))
452 && (strncasecmp(ChrPtr(ReqLine
), "/freebusy", 9))
453 && (strncasecmp(ChrPtr(ReqLine
), "/do_logout", 10))
454 && (strncasecmp(ChrPtr(ReqLine
), "/groupdav", 9))
455 && (strncasecmp(ChrPtr(ReqLine
), "/static", 7))
456 && (strncasecmp(ChrPtr(ReqLine
), "/rss", 4))
457 && (strncasecmp(ChrPtr(ReqLine
), "/404", 4))
458 && (got_cookie
== 0)) {
460 HKEY("/static/nocookies.html"
461 "?force_close_session=yes"));
465 * See if there's an existing session open with the desired ID or user/pass
469 if (TheSession
== NULL
) {
470 pthread_mutex_lock(&SessionListMutex
);
471 for (sptr
= SessionList
; sptr
!= NULL
; sptr
= sptr
->next
) {
473 /** If HTTP-AUTH, look for a session with matching credentials */
474 if ( (!IsEmptyStr(httpauth_user
))
475 &&(!strcasecmp(ChrPtr(sptr
->httpauth_user
), httpauth_user
))
476 &&(!strcasecmp(ChrPtr(sptr
->httpauth_pass
), httpauth_pass
)) ) {
480 /** If cookie-session, look for a session with matching session ID */
481 if ( (desired_session
!= 0) && (sptr
->wc_session
== desired_session
)) {
486 pthread_mutex_unlock(&SessionListMutex
);
490 * Create a new session if we have to
492 if (TheSession
== NULL
) {
493 lprintf(3, "Creating a new session\n");
494 TheSession
= (wcsession
*)
495 malloc(sizeof(wcsession
));
496 memset(TheSession
, 0, sizeof(wcsession
));
497 TheSession
->serv_sock
= (-1);
498 TheSession
->chat_sock
= (-1);
500 /* If we're recreating a session that expired, it's best to give it the same
501 * session number that it had before. The client browser ought to pick up
502 * the new session number and start using it, but in some rare situations it
503 * doesn't, and that's a Bad Thing because it causes lots of spurious sessions
506 if (desired_session
== 0) {
507 TheSession
->wc_session
= GenerateSessionID();
510 TheSession
->wc_session
= desired_session
;
513 if (TheSession
->httpauth_user
!= NULL
){
514 FlushStrBuf(TheSession
->httpauth_user
);
515 StrBufAppendBufPlain(TheSession
->httpauth_user
, httpauth_user
, -1, 0);
517 else TheSession
->httpauth_user
= NewStrBufPlain(httpauth_user
, -1);
518 if (TheSession
->httpauth_user
!= NULL
){
519 FlushStrBuf(TheSession
->httpauth_pass
);
520 StrBufAppendBufPlain(TheSession
->httpauth_pass
, httpauth_user
, -1, 0);
522 else TheSession
->httpauth_pass
= NewStrBufPlain(httpauth_user
, -1);
524 TheSession
->CLineBuf
= NewStrBuf();
525 TheSession
->hash_prefs
= NewHash(1,NULL
); /* Get a hash table for the user preferences */
526 pthread_mutex_init(&TheSession
->SessionMutex
, NULL
);
527 pthread_mutex_lock(&SessionListMutex
);
528 TheSession
->nonce
= rand();
529 TheSession
->next
= SessionList
;
530 TheSession
->is_mobile
= -1;
531 SessionList
= TheSession
;
532 pthread_mutex_unlock(&SessionListMutex
);
537 * A future improvement might be to check the session integrity
538 * at this point before continuing.
542 * Bind to the session and perform the transaction
544 pthread_mutex_lock(&TheSession
->SessionMutex
); /* bind */
545 pthread_setspecific(MyConKey
, (void *)TheSession
);
547 TheSession
->urlstrings
= NewHash(1,NULL
);
548 TheSession
->vars
= NewHash(1,NULL
);
549 TheSession
->http_sock
= *sock
;
550 TheSession
->lastreq
= time(NULL
); /* log */
551 TheSession
->gzip_ok
= gzip_ok
;
553 if (session_is_new
) {
554 httplang_to_locale(accept_language
);
556 go_selected_language(); /* set locale */
558 session_loop(HTTPHeaders
, ReqLine
, ReqType
, Buf
); /* do transaction */
560 stop_selected_language(); /* unset locale */
562 DeleteHash(&TheSession
->summ
);
563 DeleteHash(&TheSession
->urlstrings
);
564 DeleteHash(&TheSession
->vars
);
565 FreeStrBuf(&TheSession
->WBuf
);
566 FreeStrBuf(&TheSession
->HBuf
);
569 pthread_mutex_unlock(&TheSession
->SessionMutex
); /* unbind */
571 /* Free the request buffer */
572 DeleteHash(&HTTPHeaders
);
573 FreeStrBuf(&ReqLine
);
574 FreeStrBuf(&ReqType
);
577 * Free up any session-local substitution variables which
578 * were set during this transaction
584 void tmplput_nonce(StrBuf
*Target
, WCTemplputParams
*TP
)
587 StrBufAppendPrintf(Target
, "%ld",
588 (WCC
!= NULL
)? WCC
->nonce
:0);
591 void tmplput_current_user(StrBuf
*Target
, WCTemplputParams
*TP
)
593 StrBufAppendTemplate(Target
, TP
, WC
->wc_fullname
, 0);
596 void tmplput_current_room(StrBuf
*Target
, WCTemplputParams
*TP
)
598 StrBufAppendTemplate(Target
, TP
, WC
->wc_roomname
, 0);
607 RegisterNamespace("CURRENT_USER", 0, 1, tmplput_current_user
, CTX_NONE
);
608 RegisterNamespace("CURRENT_ROOM", 0, 1, tmplput_current_room
, CTX_NONE
);
609 RegisterNamespace("NONCE", 0, 0, tmplput_nonce
, 0);