X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=webcit%2Fcontext_loop.c;h=4cf0b5bfee7ee265d8444d283bec75d3e1ec5b79;hb=48b40c677af024cbf96a4e63a8deb68be484d785;hp=83448ffbe42363293e67ed85343ab3c5765621f2;hpb=843c36acbf39d16b0d74d5480aec7e8ef49f4085;p=citadel.git diff --git a/webcit/context_loop.c b/webcit/context_loop.c index 83448ffbe..4cf0b5bfe 100644 --- a/webcit/context_loop.c +++ b/webcit/context_loop.c @@ -1,112 +1,159 @@ /* - * context_loop.c + * $Id$ * * This is the other half of the webserver. It handles the task of hooking * up HTTP requests with the sessions they belong to, using HTTP cookies to * keep track of things. If the HTTP request doesn't belong to any currently * active session, a new session is started. * - * $Id$ */ -#include -#include -#ifdef HAVE_UNISTD_H -#include -#endif -#include -#ifdef HAVE_FCNTL_H -#include -#endif -#include -#include -#include -#include -#ifdef HAVE_SYS_TIME_H -#include -#endif -#ifdef HAVE_LIMITS_H -#include -#endif -#include -#include -#include -#include -#include -#include -#include #include "webcit.h" #include "webserver.h" /* Only one thread may manipulate SessionList at a time... */ pthread_mutex_t SessionListMutex; -struct wcsession *SessionList = NULL; +struct wcsession *SessionList = NULL; /**< our sessions ????*/ -pthread_key_t MyConKey; /* TSD key for MySession() */ +pthread_key_t MyConKey; /**< TSD key for MySession() */ -void do_housekeeping(void) + +/* + * free the memory used for viewing atachments + */ +void free_attachments(struct wcsession *sess) { + struct wc_attachment *att; + + while (sess->first_attachment != NULL) { + att = sess->first_attachment; + sess->first_attachment = sess->first_attachment->next; + free(att->data); + free(att); + } +} + + +void DestroySession(struct wcsession **sessions_to_kill) { - struct wcsession *sptr, *ss, *session_to_kill; + close((*sessions_to_kill)->serv_sock); + close((*sessions_to_kill)->chat_sock); +// if ((*sessions_to_kill)->preferences != NULL) { +// free((*sessions_to_kill)->preferences); +// } + if ((*sessions_to_kill)->cache_fold != NULL) { + free((*sessions_to_kill)->cache_fold); + } + free_attachments((*sessions_to_kill)); + free_march_list((*sessions_to_kill)); + DeleteHash(&((*sessions_to_kill)->hash_prefs)); + DeleteHash(&((*sessions_to_kill)->IconBarSetttings)); + DeleteHash(&((*sessions_to_kill)->ServCfg)); + FreeStrBuf(&((*sessions_to_kill)->UrlFragment1)); + FreeStrBuf(&((*sessions_to_kill)->UrlFragment2)); + FreeStrBuf(&((*sessions_to_kill)->WBuf)); + FreeStrBuf(&((*sessions_to_kill)->HBuf)); + + free((*sessions_to_kill)); + (*sessions_to_kill) = NULL; +} - do { - session_to_kill = NULL; - pthread_mutex_lock(&SessionListMutex); - for (sptr = SessionList; sptr != NULL; sptr = sptr->next) { +void shutdown_sessions(void) +{ + struct wcsession *sptr; + + for (sptr = SessionList; sptr != NULL; sptr = sptr->next) { + sptr->killthis = 1; + } +} - /* Kill idle sessions */ - if ((time(NULL) - (sptr->lastreq)) > - (time_t) WEBCIT_TIMEOUT) { - sptr->killthis = 1; - } +void do_housekeeping(void) +{ + struct wcsession *sptr, *ss; + struct wcsession *sessions_to_kill = NULL; + int num_sessions = 0; + static int num_threads = MIN_WORKER_THREADS; + + /** + * Lock the session list, moving any candidates for euthanasia into + * a separate list. + */ + pthread_mutex_lock(&SessionListMutex); + num_sessions = 0; + for (sptr = SessionList; sptr != NULL; sptr = sptr->next) { + ++num_sessions; + + /** Kill idle sessions */ + if ((time(NULL) - (sptr->lastreq)) > + (time_t) WEBCIT_TIMEOUT) { + sptr->killthis = 1; + } - /* Remove sessions flagged for kill */ - if (sptr->killthis) { + /** Remove sessions flagged for kill */ + if (sptr->killthis) { - /* remove session from linked list */ - if (sptr == SessionList) { - SessionList = SessionList->next; - } - else for (ss=SessionList;ss!=NULL;ss=ss->next) { - if (ss->next == sptr) { - ss->next = ss->next->next; - } + /** remove session from linked list */ + if (sptr == SessionList) { + SessionList = SessionList->next; + } + else for (ss=SessionList;ss!=NULL;ss=ss->next) { + if (ss->next == sptr) { + ss->next = ss->next->next; } - - session_to_kill = sptr; - goto BREAKOUT; } - } -BREAKOUT: pthread_mutex_unlock(&SessionListMutex); - if (session_to_kill != NULL) { - pthread_mutex_lock(&session_to_kill->SessionMutex); - close(session_to_kill->serv_sock); - pthread_mutex_unlock(&session_to_kill->SessionMutex); - free(session_to_kill); + sptr->next = sessions_to_kill; + sessions_to_kill = sptr; } + } + pthread_mutex_unlock(&SessionListMutex); - } while (session_to_kill != NULL); + /** + * Now free up and destroy the culled sessions. + */ + while (sessions_to_kill != NULL) { + lprintf(3, "Destroying session %d\n", sessions_to_kill->wc_session); + pthread_mutex_lock(&sessions_to_kill->SessionMutex); + pthread_mutex_unlock(&sessions_to_kill->SessionMutex); + sptr = sessions_to_kill->next; + + DestroySession(&sessions_to_kill); + sessions_to_kill = sptr; + --num_sessions; + } + + /** + * If there are more sessions than threads, then we should spawn + * more threads ... up to a predefined maximum. + */ + while ( (num_sessions > num_threads) + && (num_threads <= MAX_WORKER_THREADS) ) { + spawn_another_worker_thread(); + ++num_threads; + lprintf(3, "There are %d sessions and %d threads active.\n", + num_sessions, num_threads); + } } -/* - * Wake up occasionally and clean house +/** + * \brief Wake up occasionally and clean house */ void housekeeping_loop(void) { while (1) { - sleep(HOUSEKEEPING); + sleeeeeeeeeep(HOUSEKEEPING); do_housekeeping(); } } -/* +/** + * \brief Create a Session id * Generate a unique WebCit session ID (which is not the same thing as the * Citadel session ID). * - * FIX ... ensure that session number is truly unique + * \todo FIXME ... ensure that session number is truly unique * */ int GenerateSessionID(void) @@ -124,29 +171,37 @@ int GenerateSessionID(void) /* * Collapse multiple cookies on one line */ -int req_gets(int sock, char *buf, char *hold) +int req_gets(int *sock, char *buf, char *hold, size_t hlen) { - int a; + int a, b; - if (strlen(hold) == 0) { + if (IsEmptyStr(hold)) { strcpy(buf, ""); - a = client_gets(sock, buf); + a = client_getln(sock, buf, SIZ); if (a<1) return(-1); } else { - strcpy(buf, hold); + safestrncpy(buf, hold, SIZ); } strcpy(hold, ""); if (!strncasecmp(buf, "Cookie: ", 8)) { - for (a = 0; a < strlen(buf); ++a) + int len; + len = strlen(buf); + for (a = 0; a < len; ++a) if (buf[a] == ';') { - sprintf(hold, "Cookie: %s", &buf[a + 1]); + // we don't refresh len, because of we + // only exit from here. + snprintf(hold, hlen, "Cookie: %s", &buf[a + 1]); buf[a] = 0; - while (isspace(hold[8])) - strcpy(&hold[8], &hold[9]); + b = 8; + while (isspace(hold[b])) + b++; + + memmove(&hold[8], &hold[b], len - b + 1); return(0); } } + return(0); } @@ -154,10 +209,9 @@ int req_gets(int sock, char *buf, char *hold) * lingering_close() a`la Apache. see * http://www.apache.org/docs/misc/fin_wait_2.html for rationale */ - int lingering_close(int fd) { - char buf[256]; + char buf[SIZ]; int i; fd_set set; struct timeval tv, start; @@ -189,24 +243,43 @@ int lingering_close(int fd) -/* - * Check for bogus requests coming from (for example) brain-dead - * Windoze boxes that are infected with the latest worm-of-the-week. - * If we detect one of these, bail out without bothering our Citadel - * server. +/** + * \brief sanity requests + * Check for bogus requests coming from brain-dead Windows boxes. + * + * \param http_cmd The HTTP request to check */ int is_bogus(char *http_cmd) { - - if (!strncasecmp(http_cmd, "GET /scripts/root.exe", 21)) return(1); - if (!strncasecmp(http_cmd, "GET /c/winnt", 12)) return(2); - if (!strncasecmp(http_cmd, "GET /MSADC/", 11)) return(3); + char *url; + int i, max; + + url = strstr(http_cmd, " "); + if (url == NULL) return(1); + ++url; + + char *bogus_prefixes[] = { + "/scripts/root.exe", /**< Worms and trojans and viruses, oh my! */ + "/c/winnt", + "/MSADC/", + "/_vti", /**< Broken Microsoft DAV implementation */ + "/MSOffice" /**< Stoopid MSOffice thinks everyone is IIS */ + }; + + max = sizeof(bogus_prefixes) / sizeof(char *); + + for (i=0; inext = NULL; last = hptr; - strcpy(hptr->line, buf); + safestrncpy(hptr->line, buf, sizeof hptr->line); - } while (strlen(buf) > 0); + } while (!IsEmptyStr(buf)); - strcpy(buf, req->line); - fprintf(stderr, "%s\n", buf); + /** + * If the request is prefixed by "/webcit" then chop that off. This + * allows a front end web server to forward all /webcit requests to us + * while still using the same web server port for other things. + */ + + ptr = strstr(req->line, " /webcit "); /*< Handle "/webcit" */ + if (ptr != NULL) { + strcpy(ptr+2, ptr+8); + } - /* Check for bogus requests */ - if (is_bogus(buf)) goto bail; + ptr = strstr(req->line, " /webcit"); /*< Handle "/webcit/" */ + if (ptr != NULL) { + strcpy(ptr+1, ptr+8); + } - /* - * If requesting a non-root page, there should already be a cookie - * set. If there isn't, the client browser has cookies turned off - * (or doesn't support them) and we have to barf & bail. + safestrncpy(buf, req->line, sizeof buf); + /** Begin parsing the request. */ +#ifdef TECH_PREVIEW + if ((strncmp(req->line+4, "/sslg", 5) != 0) && + (strncmp(req->line+4, "/static/", 8) != 0) && + (strncmp(req->line+4, "/wholist_section", 16) != 0)) { +#endif + lprintf(5, "HTTP: %s\n", buf); +#ifdef TECH_PREVIEW + } +#endif + + /** Check for bogus requests */ + if (is_bogus(buf)) { + strcpy(req->line, "GET /404 HTTP/1.1"); + strcpy(buf, "GET /404 HTTP/1.1"); + } + + /** + * Strip out the method, leaving the URL up front... */ - if (!strncasecmp(buf, "GET ", 4)) strcpy(buf, &buf[4]); - else if (!strncasecmp(buf, "HEAD ", 5)) strcpy(buf, &buf[5]); + remove_token(buf, 0, ' '); if (buf[1]==' ') buf[1]=0; - /* + /** * While we're at it, gracefully handle requests for the - * robots.txt file... + * robots.txt and favicon.ico files. */ if (!strncasecmp(buf, "/robots.txt", 11)) { strcpy(req->line, "GET /static/robots.txt" - "?force_close_session=yes HTTP/1.0"); + "?force_close_session=yes HTTP/1.1"); + } + else if (!strncasecmp(buf, "/favicon.ico", 12)) { + strcpy(req->line, "GET /static/favicon.ico"); } - /* Do the non-root-cookie check now. */ - else if ( (strcmp(buf, "/")) && (got_cookie == 0)) { + /** + * These are the URL's which may be executed without a + * session cookie already set. If it's not one of these, + * force the session to close because cookies are + * probably disabled on the client browser. + */ + else if ( (strcmp(buf, "/")) + && (strncasecmp(buf, "/listsub", 8)) + && (strncasecmp(buf, "/freebusy", 9)) + && (strncasecmp(buf, "/do_logout", 10)) + && (strncasecmp(buf, "/groupdav", 9)) + && (strncasecmp(buf, "/static", 7)) + && (strncasecmp(buf, "/rss", 4)) + && (strncasecmp(buf, "/404", 4)) + && (got_cookie == 0)) { strcpy(req->line, "GET /static/nocookies.html" - "?force_close_session=yes HTTP/1.0"); + "?force_close_session=yes HTTP/1.1"); } - - - /* - * See if there's an existing session open with the desired ID + /** + * See if there's an existing session open with the desired ID or user/pass */ TheSession = NULL; - if (desired_session != 0) { + + if (TheSession == NULL) { pthread_mutex_lock(&SessionListMutex); for (sptr = SessionList; sptr != NULL; sptr = sptr->next) { - if (sptr->wc_session == desired_session) { + + /** If HTTP-AUTH, look for a session with matching credentials */ + if ( (!IsEmptyStr(httpauth_user)) + &&(!strcasecmp(sptr->httpauth_user, httpauth_user)) + &&(!strcasecmp(sptr->httpauth_pass, httpauth_pass)) ) { + TheSession = sptr; + } + + /** If cookie-session, look for a session with matching session ID */ + if ( (desired_session != 0) && (sptr->wc_session == desired_session)) { TheSession = sptr; } + } pthread_mutex_unlock(&SessionListMutex); } - /* + /** * Create a new session if we have to */ if (TheSession == NULL) { - fprintf(stderr, "Creating a new session\n"); + lprintf(3, "Creating a new session\n"); TheSession = (struct wcsession *) malloc(sizeof(struct wcsession)); memset(TheSession, 0, sizeof(struct wcsession)); - TheSession->wc_session = GenerateSessionID(); - pthread_mutex_init(&TheSession->SessionMutex, NULL); + TheSession->serv_sock = (-1); + TheSession->chat_sock = (-1); + + /* If we're recreating a session that expired, it's best to give it the same + * session number that it had before. The client browser ought to pick up + * the new session number and start using it, but in some rare situations it + * doesn't, and that's a Bad Thing because it causes lots of spurious sessions + * to get created. + */ + if (desired_session == 0) { + TheSession->wc_session = GenerateSessionID(); + } + else { + TheSession->wc_session = desired_session; + } + strcpy(TheSession->httpauth_user, httpauth_user); + strcpy(TheSession->httpauth_pass, httpauth_pass); + TheSession->hash_prefs = NewHash(1,NULL); /* Get a hash table for the user preferences */ + pthread_mutex_init(&TheSession->SessionMutex, NULL); pthread_mutex_lock(&SessionListMutex); + TheSession->urlstrings = NULL; + TheSession->vars = NULL; + TheSession->nonce = rand(); + TheSession->WBuf = NULL; TheSession->next = SessionList; SessionList = TheSession; pthread_mutex_unlock(&SessionListMutex); + session_is_new = 1; } - /* - * - * FIX ... check session integrity here before continuing - * + * A future improvement might be to check the session integrity + * at this point before continuing. */ - /* * Bind to the session and perform the transaction */ pthread_mutex_lock(&TheSession->SessionMutex); /* bind */ pthread_setspecific(MyConKey, (void *)TheSession); - TheSession->http_sock = sock; + + TheSession->urlstrings = NewHash(1,NULL); + TheSession->vars = NewHash(1,NULL); + TheSession->http_sock = *sock; TheSession->lastreq = time(NULL); /* log */ - session_loop(req); /* perform the requested transaction */ + TheSession->gzip_ok = gzip_ok; +#ifdef ENABLE_NLS + if (session_is_new) { + httplang_to_locale(accept_language); + } + go_selected_language(); /* set locale */ +#endif + session_loop(req); /* do transaction */ +#ifdef ENABLE_NLS + stop_selected_language(); /* unset locale */ +#endif + DeleteHash(&TheSession->urlstrings); + DeleteHash(&TheSession->vars); + FreeStrBuf(&TheSession->WBuf); + FreeStrBuf(&TheSession->HBuf); + + pthread_mutex_unlock(&TheSession->SessionMutex); /* unbind */ /* Free the request buffer */ -bail: while (req != NULL) { + while (req != NULL) { hptr = req->next; free(req); req = hptr; } + + /* + * Free up any session-local substitution variables which + * were set during this transaction + */ + + +} + +void tmpl_nonce(StrBuf *Target, int nArgs, WCTemplateToken *Tokens, void *Context) +{ + struct wcsession *WCC = WC; + StrBufAppendPrintf(Target, "%ld", + (WCC != NULL)? WCC->nonce:0); +} + +void +InitModule_CONTEXT +(void) +{ + RegisterNamespace("NONCE", 0, 0, tmpl_nonce); }