X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fcontext.c;h=953d1d6ae77b6e1c8dd1409f3eeae5bd9b985d2d;hb=aa7365c86de8e26e796d3aa3fd605c85d8c26220;hp=4985de9e1f6ae36b97a5d4f1500b2906d2c4fc11;hpb=1421d26887f154439d2cd27075c7e85dc22f644f;p=citadel.git diff --git a/citadel/context.c b/citadel/context.c index 4985de9e1..953d1d6ae 100644 --- a/citadel/context.c +++ b/citadel/context.c @@ -1,11 +1,16 @@ /* - * $Id: sysdep.c 7989 2009-10-31 15:29:37Z davew $ - * * Citadel context management stuff. - * See COPYING for copyright information. - * * Here's where we (hopefully) have all the code that manipulates contexts. * + * Copyright (c) 1987-2011 by the citadel.org team + * + * This program is open source software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. */ #include "sysdep.h" @@ -21,6 +26,9 @@ #include #include #include +/* +#include +*/ #if TIME_WITH_SYS_TIME # include @@ -70,14 +78,13 @@ #include "user_ops.h" #include "control.h" +int DebugSession = 0; +pthread_key_t MyConKey; /* TSD key for MyContext() */ -citthread_key_t MyConKey; /* TSD key for MyContext() */ - -struct CitContext masterCC; -struct CitContext *ContextList = NULL; -// struct CitContext* next_session = NULL; +CitContext masterCC; +CitContext *ContextList = NULL; time_t last_purge = 0; /* Last dead session purge */ int num_sessions = 0; /* Current number of sessions */ @@ -128,6 +135,116 @@ int CtdlIsSingleUser(void) } + + + +/* + * Locate a context by its session number and terminate it if the user is able. + * User can NOT terminate their current session. + * User CAN terminate any other session that has them logged in. + * Aide CAN terminate any session except the current one. + */ +int CtdlTerminateOtherSession (int session_num) +{ + int ret = 0; + CitContext *ccptr; + + if (session_num == CC->cs_pid) { + return TERM_NOTALLOWED; + } + + CONM_syslog(LOG_DEBUG, "Locating session to kill\n"); + begin_critical_section(S_SESSION_TABLE); + for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) { + if (session_num == ccptr->cs_pid) { + ret |= TERM_FOUND; + if ((ccptr->user.usernum == CC->user.usernum) + || (CC->user.axlevel >= AxAideU)) { + ret |= TERM_ALLOWED; + ccptr->kill_me = KILLME_ADMIN_TERMINATE; + } + } + } + end_critical_section(S_SESSION_TABLE); + return ret; +} + + + +/* + * Check to see if the user who we just sent mail to is logged in. If yes, + * bump the 'new mail' counter for their session. That enables them to + * receive a new mail notification without having to hit the database. + */ +void BumpNewMailCounter(long which_user) +{ + CtdlBumpNewMailCounter(which_user); +} + +void CtdlBumpNewMailCounter(long which_user) +{ + CitContext *ptr; + + begin_critical_section(S_SESSION_TABLE); + + for (ptr = ContextList; ptr != NULL; ptr = ptr->next) { + if (ptr->user.usernum == which_user) { + ptr->newmail += 1; + } + } + + end_critical_section(S_SESSION_TABLE); +} + + +/* + * Check to see if a user is currently logged in + * Take care with what you do as a result of this test. + * The user may not have been logged in when this function was called BUT + * because of threading the user might be logged in before you test the result. + */ +int CtdlIsUserLoggedIn (char *user_name) +{ + CitContext *cptr; + int ret = 0; + + begin_critical_section (S_SESSION_TABLE); + for (cptr = ContextList; cptr != NULL; cptr = cptr->next) { + if (!strcasecmp(cptr->user.fullname, user_name)) { + ret = 1; + break; + } + } + end_critical_section(S_SESSION_TABLE); + return ret; +} + + + +/* + * Check to see if a user is currently logged in. + * Basically same as CtdlIsUserLoggedIn() but uses the user number instead. + * Take care with what you do as a result of this test. + * The user may not have been logged in when this function was called BUT + * because of threading the user might be logged in before you test the result. + */ +int CtdlIsUserLoggedInByNum (long usernum) +{ + CitContext *cptr; + int ret = 0; + + begin_critical_section(S_SESSION_TABLE); + for (cptr = ContextList; cptr != NULL; cptr = cptr->next) { + if (cptr->user.usernum == usernum) { + ret = 1; + } + } + end_critical_section(S_SESSION_TABLE); + return ret; +} + + + /* * Return a pointer to the CitContext structure bound to the thread which * called this function. If there's no such binding (for example, if it's @@ -135,77 +252,152 @@ int CtdlIsSingleUser(void) * * This function is used *VERY* frequently and must be kept small. */ -struct CitContext *MyContext(void) { +CitContext *MyContext(void) { + register CitContext *c; + return ((c = (CitContext *) pthread_getspecific(MyConKey), c == NULL) ? &masterCC : c); +} + + - register struct CitContext *c; - return ((c = (struct CitContext *) citthread_getspecific(MyConKey), - c == NULL) ? &masterCC : c - ); +/* + * Terminate idle sessions. This function pounds through the session table + * comparing the current time to each session's time-of-last-command. If an + * idle session is found it is terminated, then the search restarts at the + * beginning because the pointer to our place in the list becomes invalid. + */ +void terminate_idle_sessions(void) +{ + CitContext *ccptr; + time_t now; + int killed = 0; + int longrunners = 0; + + now = time(NULL); + begin_critical_section(S_SESSION_TABLE); + for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) { + if ( + (ccptr != CC) + && (config.c_sleeping > 0) + && (now - (ccptr->lastcmd) > config.c_sleeping) + ) { + if (!ccptr->dont_term) { + ccptr->kill_me = KILLME_IDLE; + ++killed; + } + else { + ++longrunners; + } + } + } + end_critical_section(S_SESSION_TABLE); + if (killed > 0) + CON_syslog(LOG_INFO, "Scheduled %d idle sessions for termination\n", killed); + if (longrunners > 0) + CON_syslog(LOG_INFO, "Didn't terminate %d protected idle sessions", longrunners); } +/* + * During shutdown, close the sockets of any sessions still connected. + */ +void terminate_all_sessions(void) +{ + CitContext *ccptr; + int killed = 0; + + begin_critical_section(S_SESSION_TABLE); + for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) { + if (ccptr->client_socket != -1) + { + CON_syslog(LOG_INFO, "terminate_all_sessions() is murdering %s", ccptr->curr_user); + close(ccptr->client_socket); + ccptr->client_socket = -1; + killed++; + } + } + end_critical_section(S_SESSION_TABLE); + if (killed > 0) { + CON_syslog(LOG_INFO, "Flushed %d stuck sessions\n", killed); + } +} + /* * Terminate a session. */ -void RemoveContext (struct CitContext *con) +void RemoveContext (CitContext *con) { - if (con==NULL) { - CtdlLogPrintf(CTDL_ERR, - "WARNING: RemoveContext() called with NULL!\n"); + const char *c; + if (con == NULL) { + CONM_syslog(LOG_ERR, "WARNING: RemoveContext() called with NULL!"); return; } - CtdlLogPrintf(CTDL_DEBUG, "RemoveContext() session %d\n", con->cs_pid); + c = con->ServiceName; + if (c == NULL) { + c = "WTF?"; + } + CON_syslog(LOG_DEBUG, "RemoveContext(%s) session %d", c, con->cs_pid); +/// cit_backtrace(); /* Run any cleanup routines registered by loadable modules. * Note: We have to "become_session()" because the cleanup functions * might make references to "CC" assuming it's the right one. */ become_session(con); - logout(); + CtdlUserLogout(); PerformSessionHooks(EVT_STOP); + client_close(); /* If the client is still connected, blow 'em away. */ become_session(NULL); - CtdlLogPrintf(CTDL_NOTICE, "[%3d] Session ended.\n", con->cs_pid); + CON_syslog(LOG_NOTICE, "[%3d]SRV[%s] Session ended.", con->cs_pid, c); - /* If the client is still connected, blow 'em away. */ - CtdlLogPrintf(CTDL_DEBUG, "Closing socket %d\n", con->client_socket); - close(con->client_socket); + /* + * If the client is still connected, blow 'em away. + * if the socket is 0 or -1, its already gone or was never there. + */ + if (con->client_socket > 0) + { + CON_syslog(LOG_NOTICE, "Closing socket %d", con->client_socket); + close(con->client_socket); + } /* If using AUTHMODE_LDAP, free the DN */ if (con->ldap_dn) { free(con->ldap_dn); con->ldap_dn = NULL; } + FreeStrBuf(&con->StatusMessage); + FreeStrBuf(&con->MigrateBuf); + FreeStrBuf(&con->RecvBuf.Buf); + if (con->cached_msglist) { + free(con->cached_msglist); + } - CtdlLogPrintf(CTDL_DEBUG, "Done with RemoveContext()\n"); + CONM_syslog(LOG_DEBUG, "Done with RemoveContext()"); } - - /* * Initialize a new context and place it in the list. The session number * used to be the PID (which is why it's called cs_pid), but that was when we * had one process per session. Now we just assign them sequentially, starting * at 1 (don't change it to 0 because masterCC uses 0). */ -struct CitContext *CreateNewContext(void) { - struct CitContext *me; +CitContext *CreateNewContext(void) { + CitContext *me; static int next_pid = 0; - me = (struct CitContext *) malloc(sizeof(struct CitContext)); + me = (CitContext *) malloc(sizeof(CitContext)); if (me == NULL) { - CtdlLogPrintf(CTDL_ALERT, "citserver: can't allocate memory!!\n"); + CONM_syslog(LOG_ALERT, "citserver: can't allocate memory!!\n"); return NULL; } + memset(me, 0, sizeof(CitContext)); - memset(me, 0, sizeof(struct CitContext)); - - /* Give the contaxt a name. Hopefully makes it easier to track */ + /* Give the context a name. Hopefully makes it easier to track */ strcpy (me->user.fullname, "SYS_notauth"); /* The new context will be created already in the CON_EXECUTING state @@ -217,6 +409,13 @@ struct CitContext *CreateNewContext(void) { * Generate a unique session number and insert this context into * the list. */ + me->MigrateBuf = NewStrBuf(); + + me->RecvBuf.Buf = NewStrBuf(); + + me->lastcmd = time(NULL); /* set lastcmd to now to prevent idle timer infanticide TODO: if we have a valid IO, use that to set the timer. */ + + begin_critical_section(S_SESSION_TABLE); me->cs_pid = ++next_pid; me->prev = NULL; @@ -231,18 +430,85 @@ struct CitContext *CreateNewContext(void) { } -struct CitContext *CtdlGetContextArray(int *count) +/* + * Initialize a new context and place it in the list. The session number + * used to be the PID (which is why it's called cs_pid), but that was when we + * had one process per session. Now we just assign them sequentially, starting + * at 1 (don't change it to 0 because masterCC uses 0). + */ +CitContext *CloneContext(CitContext *CloneMe) { + CitContext *me; + static int next_pid = 0; + + me = (CitContext *) malloc(sizeof(CitContext)); + if (me == NULL) { + CONM_syslog(LOG_ALERT, "citserver: can't allocate memory!!\n"); + return NULL; + } + memcpy(me, CloneMe, sizeof(CitContext)); + + memset(&me->RecvBuf, 0, sizeof(IOBuffer)); + memset(&me->SendBuf, 0, sizeof(IOBuffer)); + memset(&me->SBuf, 0, sizeof(IOBuffer)); + me->MigrateBuf = NULL; + me->sMigrateBuf = NULL; + me->redirect_buffer = NULL; +#ifdef HAVE_OPENSSL + me->ssl = NULL; +#endif + + me->download_fp = NULL; + me->upload_fp = NULL; + /// TODO: what about the room/user? + me->ma = NULL; + me->openid_data = NULL; + me->ldap_dn = NULL; + me->session_specific_data = NULL; + + me->download_fp = NULL; + me->upload_fp = NULL; + me->client_socket = 0; + + me->MigrateBuf = NewStrBuf(); + me->RecvBuf.Buf = NewStrBuf(); + + begin_critical_section(S_SESSION_TABLE); + { + me->cs_pid = ++next_pid; + me->prev = NULL; + me->next = ContextList; + me->lastcmd = time(NULL); /* set lastcmd to now to prevent idle timer infanticide */ + ContextList = me; + if (me->next != NULL) { + me->next->prev = me; + } + ++num_sessions; + } + end_critical_section(S_SESSION_TABLE); + return (me); +} + + +/* + * Return an array containing a copy of the context list. + * This allows worker threads to perform "for each context" operations without + * having to lock and traverse the live list. + */ +CitContext *CtdlGetContextArray(int *count) { int nContexts, i; - struct CitContext *nptr, *cptr; + CitContext *nptr, *cptr; nContexts = num_sessions; - nptr = malloc(sizeof(struct CitContext) * nContexts); - if (!nptr) + nptr = malloc(sizeof(CitContext) * nContexts); + if (!nptr) { + *count = 0; return NULL; + } begin_critical_section(S_SESSION_TABLE); - for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) - memcpy(&nptr[i], cptr, sizeof (struct CitContext)); + for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) { + memcpy(&nptr[i], cptr, sizeof (CitContext)); + } end_critical_section (S_SESSION_TABLE); *count = i; @@ -251,42 +517,50 @@ struct CitContext *CtdlGetContextArray(int *count) -/** +/* * This function fills in a context and its user field correctly * Then creates/loads that user */ -void CtdlFillSystemContext(struct CitContext *context, char *name) +void CtdlFillSystemContext(CitContext *context, char *name) { - char sysname[USERNAME_SIZE]; + char sysname[SIZ]; + long len; - memset(context, 0, sizeof(struct CitContext)); + memset(context, 0, sizeof(CitContext)); context->internal_pgm = 1; context->cs_pid = 0; strcpy (sysname, "SYS_"); strcat (sysname, name); + len = cutuserkey(sysname); + memcpy(context->curr_user, sysname, len + 1); + context->client_socket = (-1); + context->state = CON_SYS; + context->ServiceName = name; + /* internal_create_user has the side effect of loading the user regardless of wether they * already existed or needed to be created */ - internal_create_user (sysname, &(context->user), -1) ; + internal_create_user (sysname, len, &(context->user), -1) ; /* Check to see if the system user needs upgrading */ if (context->user.usernum == 0) { /* old system user with number 0, upgrade it */ context->user.usernum = get_new_user_number(); - CtdlLogPrintf(CTDL_DEBUG, "Upgrading system user \"%s\" from user number 0 to user number %d\n", context->user.fullname, context->user.usernum); + CON_syslog(LOG_INFO, "Upgrading system user \"%s\" from user number 0 to user number %ld\n", context->user.fullname, context->user.usernum); /* add user to the database */ CtdlPutUser(&(context->user)); cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1); } } + /* * Cleanup any contexts that are left lying around */ void context_cleanup(void) { - struct CitContext *ptr = NULL; - struct CitContext *rem = NULL; + CitContext *ptr = NULL; + CitContext *rem = NULL; /* * Clean up the contexts. @@ -305,8 +579,8 @@ void context_cleanup(void) /* Remove the session from the active list */ rem = ptr->next; --num_sessions; - - CtdlLogPrintf(CTDL_DEBUG, "Purging session %d\n", ptr->cs_pid); + + CON_syslog(LOG_DEBUG, "context_cleanup(): purging session %d\n", ptr->cs_pid); RemoveContext(ptr); free (ptr); ptr = rem; @@ -315,23 +589,6 @@ void context_cleanup(void) -/* - * Terminate another session. - * (This could justifiably be moved out of sysdep.c because it - * no longer does anything that is system-dependent.) - */ -void kill_session(int session_to_kill) { - struct CitContext *ptr; - - begin_critical_section(S_SESSION_TABLE); - for (ptr = ContextList; ptr != NULL; ptr = ptr->next) { - if (ptr->cs_pid == session_to_kill) { - ptr->kill_me = 1; - } - } - end_critical_section(S_SESSION_TABLE); -} - /* * Purge all sessions which have the 'kill_me' flag set. * This function has code to prevent it from running more than once every @@ -340,8 +597,8 @@ void kill_session(int session_to_kill) { * anyway, set "force" to nonzero. */ void dead_session_purge(int force) { - struct CitContext *ptr, *ptr2; /* general-purpose utility pointer */ - struct CitContext *rem = NULL; /* list of sessions to be destroyed */ + CitContext *ptr, *ptr2; /* general-purpose utility pointer */ + CitContext *rem = NULL; /* list of sessions to be destroyed */ if (force == 0) { if ( (time(NULL) - last_purge) < 5 ) { @@ -383,7 +640,7 @@ void dead_session_purge(int force) { * is allocated privately on this thread's stack. */ while (rem != NULL) { - CtdlLogPrintf(CTDL_DEBUG, "Purging session %d\n", rem->cs_pid); + CON_syslog(LOG_DEBUG, "dead_session_purge(): purging session %d, reason=%d\n", rem->cs_pid, rem->kill_me); RemoveContext(rem); ptr = rem; rem = rem->next; @@ -408,12 +665,30 @@ void InitializeMasterCC(void) { + /* - * Bind a thread to a context. (It's inline merely to speed things up.) + * Set the "async waiting" flag for a session, if applicable */ -INLINE void become_session(struct CitContext *which_con) { - citthread_setspecific(MyConKey, (void *)which_con ); +void set_async_waiting(struct CitContext *ccptr) +{ + CON_syslog(LOG_DEBUG, "Setting async_waiting flag for session %d\n", ccptr->cs_pid); + if (ccptr->is_async) { + ccptr->async_waiting++; + if (ccptr->state == CON_IDLE) { + ccptr->state = CON_READY; + } + } } - +void DebugSessionEnable(const int n) +{ + DebugSession = n; +} +CTDL_MODULE_INIT(session) +{ + if (!threading) { + CtdlRegisterDebugFlagHook(HKEY("session"), DebugSessionEnable, &DebugSession); + } + return "session"; +}