X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=webcit%2Fcontext_loop.c;h=cb1a907f62771e01c281dd84139fbaba86025ab3;hb=bedf5c0b955473d8ad02eaf628e8d209f534f2b6;hp=5bd998c89a541ce760744585917b9e1cb18a9680;hpb=dd1b8c59b580b6cddafb764177036e784499647c;p=citadel.git diff --git a/webcit/context_loop.c b/webcit/context_loop.c index 5bd998c89..cb1a907f6 100644 --- a/webcit/context_loop.c +++ b/webcit/context_loop.c @@ -1,184 +1,171 @@ /* - * context_loop.c - * + * $Id$ + */ +/** + * \defgroup WebServerII some of the webserver stuff. * This is the other half of the webserver. It handles the task of hooking - * up HTTP requests with the session they belong to, using HTTP cookies to + * 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 spawned. + * active session, a new session is started. + * \ingroup WebcitHttpServer * - * $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" -/* - * We keep one of these around for each active session - */ -struct wc_session { - struct wc_session *next; /* Next session in list */ - int session_id; /* Session ID */ - pid_t webcit_pid; /* PID of the webcit process */ - int inpipe[2]; /* Data from webserver to session */ - int outpipe[2]; /* Data from session to webserver */ - pthread_mutex_t critter; /* Critical section uses pipes */ - time_t lastreq; /* Timestamp of most recent http */ -}; +/** Only one thread may manipulate SessionList at a time... */ +pthread_mutex_t SessionListMutex; -struct wc_session *SessionList = NULL; -extern const char *defaulthost; -extern const char *defaultport; +struct wcsession *SessionList = NULL; /**< our sessions ????*/ -/* Only one thread may manipulate SessionList at a time... */ -pthread_mutex_t MasterCritter; +pthread_key_t MyConKey; /**< TSD key for MySession() */ -/* - * Grab a lock on the session, so other threads don't try to access - * the pipes at the same time. - */ -static void lock_session(struct wc_session *session) -{ - printf("Locking session %d...\n", session->session_id); - pthread_mutex_lock(&session->critter); - printf(" ...got lock\n"); -} - -/* - * Let go of the lock. +/** + * \brief free the memory used for viewing atachments + * \param sess the session object to destroy */ -static void unlock_session(struct wc_session *session) -{ - printf("Unlocking.\n"); - pthread_mutex_unlock(&session->critter); +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); + } } -/* - * Remove a session context from the list +/** + * \brief what?????? */ -void remove_session(struct wc_session *TheSession, int do_lock) +void do_housekeeping(void) { - struct wc_session *sptr; - - printf("Removing session.\n"); - if (do_lock) - pthread_mutex_lock(&MasterCritter); + 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; - if (SessionList == TheSession) { - SessionList = SessionList->next; - } else { - for (sptr = SessionList; sptr != NULL; sptr = sptr->next) { - if (sptr->next == TheSession) { - sptr->next = TheSession->next; - } + /** Kill idle sessions */ + if ((time(NULL) - (sptr->lastreq)) > + (time_t) WEBCIT_TIMEOUT) { + sptr->killthis = 1; } - } - - close(TheSession->inpipe[1]); - close(TheSession->outpipe[0]); - if (do_lock) - unlock_session(TheSession); - free(TheSession); - - pthread_mutex_unlock(&MasterCritter); -} - - + /** Remove sessions flagged for kill */ + if (sptr->killthis) { -void do_housekeeping(void) -{ - struct wc_session *sptr; - - pthread_mutex_lock(&MasterCritter); + /** 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; + } + } - /* Kill idle sessions */ - for (sptr = SessionList; sptr != NULL; sptr = sptr->next) { - if ((time(NULL) - (sptr->lastreq)) > (time_t) WEBCIT_TIMEOUT) { - kill(sptr->webcit_pid, 15); + sptr->next = sessions_to_kill; + sessions_to_kill = sptr; } } + pthread_mutex_unlock(&SessionListMutex); - /* Remove dead sessions */ - for (sptr = SessionList; sptr != NULL; sptr = sptr->next) { - if (kill(sptr->webcit_pid, 0)) { - remove_session(sptr, 0); + /** + * 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); + 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); + pthread_mutex_unlock(&sessions_to_kill->SessionMutex); + sptr = sessions_to_kill->next; + free(sessions_to_kill); + sessions_to_kill = sptr; + --num_sessions; } - pthread_mutex_unlock(&MasterCritter); + /** + * 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). + * + * \todo FIXME ... ensure that session number is truly unique + * + */ int GenerateSessionID(void) { - return getpid(); -} - - -void gets0(int fd, char buf[]) -{ + static int seq = (-1); - buf[0] = 0; - do { - buf[strlen(buf) + 1] = 0; - read(fd, &buf[strlen(buf)], 1); - } while (buf[strlen(buf) - 1] >= 32); - buf[strlen(buf) - 1] = 0; + if (seq < 0) { + seq = (int) time(NULL); + } + + return ++seq; } -/* - * Collapse multiple cookies on one line + +/** + * \brief Collapse multiple cookies on one line + * \param sock a socket? + * \param buf some bunch of chars? + * \param hold hold what? */ -void req_gets(int sock, char *buf, char *hold) +int req_gets(int sock, char *buf, char *hold) { int a; if (strlen(hold) == 0) { - client_gets(sock, buf); + strcpy(buf, ""); + a = client_getln(sock, buf, SIZ); + if (a<1) return(-1); } else { - strcpy(buf, hold); + safestrncpy(buf, hold, SIZ); } strcpy(hold, ""); @@ -189,19 +176,23 @@ void req_gets(int sock, char *buf, char *hold) buf[a] = 0; while (isspace(hold[8])) strcpy(&hold[8], &hold[9]); - return; + return(0); } } + + return(0); } -/* +/** + * \brief close some fd for some reason??? + * \param fd the fd to close?????? * lingering_close() a`la Apache. see * http://www.apache.org/docs/misc/fin_wait_2.html for rationale */ -static int lingering_close(int fd) +int lingering_close(int fd) { - char buf[256]; + char buf[SIZ]; int i; fd_set set; struct timeval tv, start; @@ -231,205 +222,295 @@ static int lingering_close(int fd) return close(fd); } -/* - * This loop gets called once for every HTTP connection made to WebCit. + + +/** + * \brief sanity requests + * Check for bogus requests coming from brain-dead Windows boxes. + * + * \param http_cmd The HTTP request to check */ -void *context_loop(int sock) -{ - char (*req)[256]; - char buf[256], hold[256]; - char browser_host[256]; - char browser[256]; - int num_lines = 0; - int a; - int f; - int desired_session = 0; - char str_session[256]; - struct wc_session *sptr; - struct wc_session *TheSession; - int ContentLength; - int CloseSession = 0; - - if ((req = malloc((long) sizeof(char[256][256]))) == NULL) { - sprintf(buf, "Can't malloc buffers; dropping connection.\n"); - fprintf(stderr, "%s", buf); - write(sock, buf, strlen(buf)); - close(sock); - pthread_exit(NULL); +int is_bogus(char *http_cmd) { + 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 = hptr; + hptr->next = NULL; + last = hptr; + + safestrncpy(hptr->line, buf, sizeof hptr->line); + } while (strlen(buf) > 0); - /* - * See if there's an existing session open with the desired ID + /** + * 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); + } + + ptr = strstr(req->line, " /webcit"); /*< Handle "/webcit/" */ + if (ptr != NULL) { + strcpy(ptr+1, ptr+8); + } + + /** Begin parsing the request. */ + + safestrncpy(buf, req->line, sizeof buf); + lprintf(5, "HTTP: %s\n", buf); + + /** 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... + */ + remove_token(buf, 0, ' '); + if (buf[1]==' ') buf[1]=0; + + /** + * While we're at it, gracefully handle requests for the + * 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.1"); + } + else if (!strncasecmp(buf, "/favicon.ico", 12)) { + strcpy(req->line, "GET /static/favicon.ico"); + } + + /** + * 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.1"); + } + + /** + * See if there's an existing session open with the desired ID or user/pass */ TheSession = NULL; - if (desired_session != 0) { - pthread_mutex_lock(&MasterCritter); + + if (TheSession == NULL) { + pthread_mutex_lock(&SessionListMutex); for (sptr = SessionList; sptr != NULL; sptr = sptr->next) { - if (sptr->session_id == desired_session) { + + /** If HTTP-AUTH, look for a session with matching credentials */ + if ( (strlen(httpauth_user) > 0) + &&(!strcasecmp(sptr->httpauth_user, httpauth_user)) + &&(!strcasecmp(sptr->httpauth_pass, httpauth_pass)) ) { TheSession = sptr; - lock_session(TheSession); } + + /** 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(&MasterCritter); - } - /* - * Before we trumpet to the universe that the session we're looking - * for actually exists, check first to make sure it's still there. - */ - if (TheSession != NULL) { - if (kill(TheSession->webcit_pid, 0)) { - printf(" Session is *DEAD* !!\n"); - remove_session(TheSession, 1); - TheSession = NULL; - } + pthread_mutex_unlock(&SessionListMutex); } - /* + + /** * Create a new session if we have to */ if (TheSession == NULL) { - printf("Creating a new session\n"); - locate_host(browser_host, sock); - pthread_mutex_lock(&MasterCritter); - TheSession = (struct wc_session *) - malloc(sizeof(struct wc_session)); - TheSession->session_id = GenerateSessionID(); - pipe(TheSession->inpipe); - pipe(TheSession->outpipe); - pthread_mutex_init(&TheSession->critter, NULL); - lock_session(TheSession); + lprintf(3, "Creating a new session\n"); + TheSession = (struct wcsession *) + malloc(sizeof(struct wcsession)); + memset(TheSession, 0, sizeof(struct wcsession)); + 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); + pthread_mutex_init(&TheSession->SessionMutex, NULL); + pthread_mutex_lock(&SessionListMutex); + TheSession->nonce = rand(); TheSession->next = SessionList; SessionList = TheSession; - pthread_mutex_unlock(&MasterCritter); - sprintf(str_session, "%d", TheSession->session_id); - f = fork(); - if (f > 0) - TheSession->webcit_pid = f; - - fflush(stdout); - fflush(stdin); - if (f == 0) { - - /* Hook stdio to the ends of the pipe we're using */ - dup2(TheSession->inpipe[0], 0); - dup2(TheSession->outpipe[1], 1); - - /* Close the ends of the pipes that we're not using */ - close(TheSession->inpipe[1]); - close(TheSession->outpipe[0]); - - /* Close the HTTP socket in this pid; don't need it */ - close(sock); - - /* Run the actual WebCit session */ - execlp("./webcit", "webcit", str_session, defaulthost, - defaultport, browser_host, browser, NULL); - - /* Simple page to display if exec fails */ - printf("HTTP/1.0 404 WebCit Failure\n\n"); - printf("Server: %s\n", SERVER); - printf("X-WebCit-Session: close\n"); - printf("Content-type: text/html\n"); - printf("Content-length: 76\n"); - printf("\n"); - printf("Error\n"); - printf("execlp() failed: %s\n", strerror(errno)); - exit(0); - } else { - /* Close the ends of the pipes that we're not using */ - close(TheSession->inpipe[0]); - close(TheSession->outpipe[1]); - } - } - /* - * Send the request to the appropriate session... - */ - TheSession->lastreq = time(NULL); - printf(" Writing %d lines of command\n", num_lines); - printf("%s\n", &req[0][0]); - for (a = 0; a < num_lines; ++a) { - write(TheSession->inpipe[1], &req[a][0], strlen(&req[a][0])); - write(TheSession->inpipe[1], "\n", 1); - } - printf(" Writing %d bytes of content\n", ContentLength); - while (ContentLength > 0) { - a = ContentLength; - if (a > sizeof buf) - a = sizeof buf; - if (!client_read(sock, buf, a)) - goto end; - if (write(TheSession->inpipe[1], buf, a) != a) - goto end; - ContentLength -= a; + pthread_mutex_unlock(&SessionListMutex); + session_is_new = 1; } - /* - * ...and get the response. + /** + * A future improvement might be to check the session integrity + * at this point before continuing. */ - printf(" Reading response\n"); - ContentLength = 0; - do { - gets0(TheSession->outpipe[0], buf); - write(sock, buf, strlen(buf)); - write(sock, "\n", 1); - if (!strncasecmp(buf, "Content-length: ", 16)) - ContentLength = atoi(&buf[16]); - if (!strcasecmp(buf, "X-WebCit-Session: close")) { - CloseSession = 1; - } - } while (strlen(buf) > 0); - printf(" Reading %d bytes of content\n", ContentLength); - while (ContentLength--) { - read(TheSession->outpipe[0], buf, 1); - write(sock, buf, 1); - } - - /* - * If the last response included a "close session" directive, - * remove the context now. + /** + * Bind to the session and perform the transaction */ - if (CloseSession) { - remove_session(TheSession, 1); - } else { -end: unlock_session(TheSession); + pthread_mutex_lock(&TheSession->SessionMutex); /*< bind */ + pthread_setspecific(MyConKey, (void *)TheSession); + TheSession->http_sock = sock; + TheSession->lastreq = time(NULL); /*< log */ + TheSession->gzip_ok = gzip_ok; +#ifdef ENABLE_NLS + if (session_is_new) { + httplang_to_locale(accept_language); } - free(req); - + go_selected_language(); /*< set locale */ +#endif + session_loop(req); /*< do transaction */ +#ifdef ENABLE_NLS + stop_selected_language(); /*< unset locale */ +#endif + pthread_mutex_unlock(&TheSession->SessionMutex); /*< unbind */ - /* - * Now our HTTP connection is done. It would be relatively easy - * to support HTTP/1.1 "persistent" connections by looping back to - * the top of this function. For now, we'll just close. - */ - printf(" Closing socket %d ... ret=%d\n", sock, - lingering_close(sock)); + /** Free the request buffer */ + while (req != NULL) { + hptr = req->next; + free(req); + req = hptr; + } - /* - * The thread handling this HTTP connection is now finished. - * Instead of calling pthread_exit(), just return. It does the same - * thing, and supresses a compiler warning. + /** + * Free up any session-local substitution variables which + * were set during this transaction */ - return NULL; + clear_local_substs(); } +/*@}*/