"Fossies" - the Fresh Open Source Software Archive

Member "citadel/context.c" (5 Jun 2021, 18472 Bytes) of package /linux/www/citadel.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "context.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.01_vs_902.

    1 //
    2 // Citadel context management stuff.
    3 // Here's where we (hopefully) have all the code that manipulates contexts.
    4 //
    5 // Copyright (c) 1987-2020 by the citadel.org team
    6 //
    7 // This program is open source software.  Use, duplication, or disclosure
    8 // is subject to the terms of the GNU General Public License, version 3.
    9 // The program is distributed without any warranty, expressed or implied.
   10 
   11 #include "ctdl_module.h"
   12 #include "serv_extensions.h"
   13 #include "citserver.h"
   14 #include "user_ops.h"
   15 #include "locate_host.h"
   16 #include "context.h"
   17 #include "control.h"
   18 #include "config.h"
   19 
   20 pthread_key_t MyConKey;             /* TSD key for MyContext() */
   21 CitContext masterCC;
   22 CitContext *ContextList = NULL;
   23 time_t last_purge = 0;              /* Last dead session purge */
   24 int num_sessions = 0;               /* Current number of sessions */
   25 int next_pid = 0;
   26 
   27 /* Flag for single user mode */
   28 static int want_single_user = 0;
   29 
   30 /* Try to go single user */
   31 
   32 int CtdlTrySingleUser(void)
   33 {
   34     int can_do = 0;
   35     
   36     begin_critical_section(S_SINGLE_USER);
   37     if (want_single_user)
   38         can_do = 0;
   39     else
   40     {
   41         can_do = 1;
   42         want_single_user = 1;
   43     }
   44     end_critical_section(S_SINGLE_USER);
   45     return can_do;
   46 }
   47 
   48 
   49 void CtdlEndSingleUser(void)
   50 {
   51     begin_critical_section(S_SINGLE_USER);
   52     want_single_user = 0;
   53     end_critical_section(S_SINGLE_USER);
   54 }
   55 
   56 
   57 int CtdlWantSingleUser(void)
   58 {
   59     return want_single_user;
   60 }
   61 
   62 
   63 int CtdlIsSingleUser(void)
   64 {
   65     if (want_single_user)
   66     {
   67         /* check for only one context here */
   68         if (num_sessions == 1)
   69             return 1;
   70     }
   71     return 0;
   72 }
   73 
   74 
   75 /*
   76  * Locate a context by its session number and terminate it if the user is able.
   77  * User can NOT terminate their current session.
   78  * User CAN terminate any other session that has them logged in.
   79  * Aide CAN terminate any session except the current one.
   80  */
   81 int CtdlTerminateOtherSession (int session_num)
   82 {
   83     struct CitContext *CCC = CC;
   84     int ret = 0;
   85     CitContext *ccptr;
   86     int aide;
   87 
   88     if (session_num == CCC->cs_pid) return TERM_NOTALLOWED;
   89 
   90     aide = ( (CCC->user.axlevel >= AxAideU) || (CCC->internal_pgm) ) ;
   91 
   92     syslog(LOG_DEBUG, "context: locating session to kill");
   93     begin_critical_section(S_SESSION_TABLE);
   94     for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
   95         if (session_num == ccptr->cs_pid) {
   96             ret |= TERM_FOUND;
   97             if ((ccptr->user.usernum == CCC->user.usernum) || aide) {
   98                 ret |= TERM_ALLOWED;
   99             }
  100             break;
  101         }
  102     }
  103 
  104     if (((ret & TERM_FOUND) != 0) && ((ret & TERM_ALLOWED) != 0))
  105     {
  106         if (ccptr->user.usernum == CCC->user.usernum)
  107             ccptr->kill_me = KILLME_ADMIN_TERMINATE;
  108         else
  109             ccptr->kill_me = KILLME_IDLE;
  110         end_critical_section(S_SESSION_TABLE);
  111     }
  112     else
  113         end_critical_section(S_SESSION_TABLE);
  114 
  115     return ret;
  116 }
  117 
  118 
  119 
  120 /*
  121  * Check to see if the user who we just sent mail to is logged in.  If yes,
  122  * bump the 'new mail' counter for their session.  That enables them to
  123  * receive a new mail notification without having to hit the database.
  124  */
  125 void BumpNewMailCounter(long which_user) 
  126 {
  127     CtdlBumpNewMailCounter(which_user);
  128 }
  129 
  130 void CtdlBumpNewMailCounter(long which_user)
  131 {
  132     CitContext *ptr;
  133 
  134     begin_critical_section(S_SESSION_TABLE);
  135 
  136     for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
  137         if (ptr->user.usernum == which_user) {
  138             ptr->newmail += 1;
  139         }
  140     }
  141 
  142     end_critical_section(S_SESSION_TABLE);
  143 }
  144 
  145 
  146 /*
  147  * Check to see if a user is currently logged in
  148  * Take care with what you do as a result of this test.
  149  * The user may not have been logged in when this function was called BUT
  150  * because of threading the user might be logged in before you test the result.
  151  */
  152 int CtdlIsUserLoggedIn (char *user_name)
  153 {
  154     CitContext *cptr;
  155     int ret = 0;
  156 
  157     begin_critical_section (S_SESSION_TABLE);
  158     for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
  159         if (!strcasecmp(cptr->user.fullname, user_name)) {
  160             ret = 1;
  161             break;
  162         }
  163     }
  164     end_critical_section(S_SESSION_TABLE);
  165     return ret;
  166 }
  167 
  168 
  169 
  170 /*
  171  * Check to see if a user is currently logged in.
  172  * Basically same as CtdlIsUserLoggedIn() but uses the user number instead.
  173  * Take care with what you do as a result of this test.
  174  * The user may not have been logged in when this function was called BUT
  175  * because of threading the user might be logged in before you test the result.
  176  */
  177 int CtdlIsUserLoggedInByNum (long usernum)
  178 {
  179     CitContext *cptr;
  180     int ret = 0;
  181 
  182     begin_critical_section(S_SESSION_TABLE);
  183     for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
  184         if (cptr->user.usernum == usernum) {
  185             ret = 1;
  186         }
  187     }
  188     end_critical_section(S_SESSION_TABLE);
  189     return ret;
  190 }
  191 
  192 
  193 
  194 /*
  195  * Return a pointer to the CitContext structure bound to the thread which
  196  * called this function.  If there's no such binding (for example, if it's
  197  * called by the housekeeper thread) then a generic 'master' CC is returned.
  198  *
  199  * This function is used *VERY* frequently and must be kept small.
  200  */
  201 CitContext *MyContext(void) {
  202     register CitContext *c;
  203     return ((c = (CitContext *) pthread_getspecific(MyConKey), c == NULL) ? &masterCC : c);
  204 }
  205 
  206 
  207 
  208 
  209 /*
  210  * Terminate idle sessions.  This function pounds through the session table
  211  * comparing the current time to each session's time-of-last-command.  If an
  212  * idle session is found it is terminated, then the search restarts at the
  213  * beginning because the pointer to our place in the list becomes invalid.
  214  */
  215 void terminate_idle_sessions(void)
  216 {
  217     CitContext *ccptr;
  218     time_t now;
  219     int killed = 0;
  220     int longrunners = 0;
  221 
  222     now = time(NULL);
  223     begin_critical_section(S_SESSION_TABLE);
  224     for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
  225         if (
  226             (ccptr != CC)
  227             && (CtdlGetConfigLong("c_sleeping") > 0)
  228             && (now - (ccptr->lastcmd) > CtdlGetConfigLong("c_sleeping"))
  229         ) {
  230             if (!ccptr->dont_term) {
  231                 ccptr->kill_me = KILLME_IDLE;
  232                 ++killed;
  233             }
  234             else {
  235                 ++longrunners;
  236             }
  237         }
  238     }
  239     end_critical_section(S_SESSION_TABLE);
  240     if (killed > 0) {
  241         syslog(LOG_INFO, "context: scheduled %d idle sessions for termination", killed);
  242     }
  243     if (longrunners > 0) {
  244         syslog(LOG_INFO, "context: did not terminate %d protected idle sessions", longrunners);
  245     }
  246 }
  247 
  248 
  249 /*
  250  * During shutdown, close the sockets of any sessions still connected.
  251  */
  252 void terminate_all_sessions(void)
  253 {
  254     CitContext *ccptr;
  255     int killed = 0;
  256 
  257     begin_critical_section(S_SESSION_TABLE);
  258     for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
  259         if (ccptr->client_socket != -1)
  260         {
  261             syslog(LOG_INFO, "context: terminate_all_sessions() is murdering %s CC[%d]", ccptr->curr_user, ccptr->cs_pid);
  262             close(ccptr->client_socket);
  263             ccptr->client_socket = -1;
  264             killed++;
  265         }
  266     }
  267     end_critical_section(S_SESSION_TABLE);
  268     if (killed > 0) {
  269         syslog(LOG_INFO, "context: flushed %d stuck sessions", killed);
  270     }
  271 }
  272 
  273 
  274 
  275 /*
  276  * Terminate a session.
  277  */
  278 void RemoveContext (CitContext *con)
  279 {
  280     const char *c;
  281     if (con == NULL) {
  282         syslog(LOG_ERR, "context: RemoveContext() called with NULL, this should not happen");
  283         return;
  284     }
  285     c = con->ServiceName;
  286     if (c == NULL) {
  287         c = "WTF?";
  288     }
  289     syslog(LOG_DEBUG, "context: RemoveContext(%s) session %d", c, con->cs_pid);
  290 
  291     /* Run any cleanup routines registered by loadable modules.
  292      * Note: We have to "become_session()" because the cleanup functions
  293      *       might make references to "CC" assuming it's the right one.
  294      */
  295     become_session(con);
  296     CtdlUserLogout();
  297     PerformSessionHooks(EVT_STOP);
  298     client_close();             /* If the client is still connected, blow 'em away. */
  299     become_session(NULL);
  300 
  301     syslog(LOG_INFO, "context: [%3d]SRV[%s] Session ended.", con->cs_pid, c);
  302 
  303     /* 
  304      * If the client is still connected, blow 'em away. 
  305      * if the socket is 0 or -1, its already gone or was never there.
  306      */
  307     if (con->client_socket > 0)
  308     {
  309         syslog(LOG_INFO, "context: closing socket %d", con->client_socket);
  310         close(con->client_socket);
  311     }
  312 
  313     /* If using AUTHMODE_LDAP, free the DN */
  314     if (con->ldap_dn) {
  315         free(con->ldap_dn);
  316         con->ldap_dn = NULL;
  317     }
  318     FreeStrBuf(&con->StatusMessage);
  319     FreeStrBuf(&con->MigrateBuf);
  320     FreeStrBuf(&con->RecvBuf.Buf);
  321     if (con->cached_msglist) {
  322         free(con->cached_msglist);
  323     }
  324 
  325     syslog(LOG_DEBUG, "context: done with RemoveContext()");
  326 }
  327 
  328 
  329 
  330 /*
  331  * Initialize a new context and place it in the list.  The session number
  332  * used to be the PID (which is why it's called cs_pid), but that was when we
  333  * had one process per session.  Now we just assign them sequentially, starting
  334  * at 1 (don't change it to 0 because masterCC uses 0).
  335  */
  336 CitContext *CreateNewContext(void) {
  337     CitContext *me;
  338 
  339     me = (CitContext *) malloc(sizeof(CitContext));
  340     if (me == NULL) {
  341         syslog(LOG_ERR, "citserver: malloc() failed: %m");
  342         return NULL;
  343     }
  344     memset(me, 0, sizeof(CitContext));
  345 
  346     /* Give the context a name. Hopefully makes it easier to track */
  347     strcpy (me->user.fullname, "SYS_notauth");
  348     
  349     /* The new context will be created already in the CON_EXECUTING state
  350      * in order to prevent another thread from grabbing it while it's
  351      * being set up.
  352      */
  353     me->state = CON_EXECUTING;
  354     /*
  355      * Generate a unique session number and insert this context into
  356      * the list.
  357      */
  358     me->MigrateBuf = NewStrBuf();
  359 
  360     me->RecvBuf.Buf = NewStrBuf();
  361 
  362     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. */
  363 
  364 
  365     begin_critical_section(S_SESSION_TABLE);
  366     me->cs_pid = ++next_pid;
  367     me->prev = NULL;
  368     me->next = ContextList;
  369     ContextList = me;
  370     if (me->next != NULL) {
  371         me->next->prev = me;
  372     }
  373     ++num_sessions;
  374     end_critical_section(S_SESSION_TABLE);
  375     return (me);
  376 }
  377 
  378 
  379 /*
  380  * Initialize a new context and place it in the list.  The session number
  381  * used to be the PID (which is why it's called cs_pid), but that was when we
  382  * had one process per session.  Now we just assign them sequentially, starting
  383  * at 1 (don't change it to 0 because masterCC uses 0).
  384  */
  385 CitContext *CloneContext(CitContext *CloneMe) {
  386     CitContext *me;
  387 
  388     me = (CitContext *) malloc(sizeof(CitContext));
  389     if (me == NULL) {
  390         syslog(LOG_ERR, "citserver: malloc() failed: %m");
  391         return NULL;
  392     }
  393     memcpy(me, CloneMe, sizeof(CitContext));
  394 
  395     memset(&me->RecvBuf, 0, sizeof(IOBuffer));
  396     memset(&me->SendBuf, 0, sizeof(IOBuffer));
  397     memset(&me->SBuf, 0, sizeof(IOBuffer));
  398     me->MigrateBuf = NULL;
  399     me->sMigrateBuf = NULL;
  400     me->redirect_buffer = NULL;
  401 #ifdef HAVE_OPENSSL
  402     me->ssl = NULL;
  403 #endif
  404 
  405     me->download_fp = NULL;
  406     me->upload_fp = NULL;
  407     /// TODO: what about the room/user?
  408     me->ma = NULL;
  409     me->openid_data = NULL;
  410     me->ldap_dn = NULL;
  411     me->session_specific_data = NULL;
  412     
  413     me->CIT_ICAL = NULL;
  414 
  415     me->cached_msglist = NULL;
  416     me->download_fp = NULL;
  417     me->upload_fp = NULL;
  418     me->client_socket = 0;
  419 
  420     me->MigrateBuf = NewStrBuf();
  421     me->RecvBuf.Buf = NewStrBuf();
  422     
  423     begin_critical_section(S_SESSION_TABLE);
  424     {
  425         me->cs_pid = ++next_pid;
  426         me->prev = NULL;
  427         me->next = ContextList;
  428         me->lastcmd = time(NULL);   /* set lastcmd to now to prevent idle timer infanticide */
  429         ContextList = me;
  430         if (me->next != NULL) {
  431             me->next->prev = me;
  432         }
  433         ++num_sessions;
  434     }
  435     end_critical_section(S_SESSION_TABLE);
  436     return (me);
  437 }
  438 
  439 
  440 /*
  441  * Return an array containing a copy of the context list.
  442  * This allows worker threads to perform "for each context" operations without
  443  * having to lock and traverse the live list.
  444  */
  445 CitContext *CtdlGetContextArray(int *count)
  446 {
  447     int nContexts, i;
  448     CitContext *nptr, *cptr;
  449     
  450     nContexts = num_sessions;
  451     nptr = malloc(sizeof(CitContext) * nContexts);
  452     if (!nptr) {
  453         *count = 0;
  454         return NULL;
  455     }
  456     begin_critical_section(S_SESSION_TABLE);
  457     for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) {
  458         memcpy(&nptr[i], cptr, sizeof (CitContext));
  459     }
  460     end_critical_section (S_SESSION_TABLE);
  461     
  462     *count = i;
  463     return nptr;
  464 }
  465 
  466 
  467 
  468 /*
  469  * Back-end function for starting a session
  470  */
  471 void begin_session(CitContext *con)
  472 {
  473     /* 
  474      * Initialize some variables specific to our context.
  475      */
  476     con->logged_in = 0;
  477     con->internal_pgm = 0;
  478     con->download_fp = NULL;
  479     con->upload_fp = NULL;
  480     con->cached_msglist = NULL;
  481     con->cached_num_msgs = 0;
  482     con->FirstExpressMessage = NULL;
  483     time(&con->lastcmd);
  484     time(&con->lastidle);
  485     strcpy(con->lastcmdname, "    ");
  486     strcpy(con->cs_clientname, "(unknown)");
  487     strcpy(con->curr_user, NLI);
  488     *con->cs_clientinfo = '\0';
  489     safestrncpy(con->cs_host, CtdlGetConfigStr("c_fqdn"), sizeof con->cs_host);
  490     safestrncpy(con->cs_addr, "", sizeof con->cs_addr);
  491     con->cs_UDSclientUID = -1;
  492     con->cs_host[sizeof con->cs_host - 1] = 0;
  493     if (!CC->is_local_client) {
  494         locate_host(con->cs_host, sizeof con->cs_host,
  495             con->cs_addr, sizeof con->cs_addr,
  496             con->client_socket
  497         );
  498     }
  499     else {
  500         con->cs_host[0] = 0;
  501         con->cs_addr[0] = 0;
  502 #ifdef HAVE_STRUCT_UCRED
  503         {
  504             /* as http://www.wsinnovations.com/softeng/articles/uds.html told us... */
  505             struct ucred credentials;
  506             socklen_t ucred_length = sizeof(struct ucred);
  507             
  508             /*fill in the user data structure */
  509             if(getsockopt(con->client_socket, SOL_SOCKET, SO_PEERCRED, &credentials, &ucred_length)) {
  510                 syslog(LOG_ERR, "context: could obtain credentials from unix domain socket");
  511                 
  512             }
  513             else {      
  514                 /* the process ID of the process on the other side of the socket */
  515                 /* credentials.pid; */
  516                 
  517                 /* the effective UID of the process on the other side of the socket  */
  518                 con->cs_UDSclientUID = credentials.uid;
  519                 
  520                 /* the effective primary GID of the process on the other side of the socket */
  521                 /* credentials.gid; */
  522                 
  523                 /* To get supplemental groups, we will have to look them up in our account
  524                    database, after a reverse lookup on the UID to get the account name.
  525                    We can take this opportunity to check to see if this is a legit account.
  526                 */
  527                 snprintf(con->cs_clientinfo, sizeof(con->cs_clientinfo),
  528                      "PID: "F_PID_T"; UID: "F_UID_T"; GID: "F_XPID_T" ", 
  529                      credentials.pid,
  530                      credentials.uid,
  531                      credentials.gid);
  532             }
  533         }
  534 #endif
  535     }
  536     con->cs_flags = 0;
  537 
  538     con->nologin = 0;
  539     if (((CtdlGetConfigInt("c_maxsessions") > 0)&&(num_sessions > CtdlGetConfigInt("c_maxsessions"))) || CtdlWantSingleUser()) {
  540         con->nologin = 1;
  541     }
  542 
  543     syslog(LOG_INFO, "context: session (%s) started from %s (%s) uid=%d",
  544         con->ServiceName, con->cs_host, con->cs_addr, con->cs_UDSclientUID
  545     );
  546 
  547     /* Run any session startup routines registered by loadable modules */
  548     PerformSessionHooks(EVT_START);
  549 }
  550 
  551 
  552 /*
  553  * This function fills in a context and its user field correctly
  554  * Then creates/loads that user
  555  */
  556 void CtdlFillSystemContext(CitContext *context, char *name)
  557 {
  558     char sysname[SIZ];
  559     long len;
  560 
  561     memset(context, 0, sizeof(CitContext));
  562     context->internal_pgm = 1;
  563     context->cs_pid = 0;
  564     strcpy (sysname, "SYS_");
  565     strcat (sysname, name);
  566     len = strlen(sysname);
  567     memcpy(context->curr_user, sysname, len + 1);
  568     context->client_socket = (-1);
  569     context->state = CON_SYS;
  570     context->ServiceName = name;
  571 
  572     /* internal_create_user has the side effect of loading the user regardless of whether they
  573      * already existed or needed to be created
  574      */
  575     internal_create_user(sysname, &(context->user), -1) ;
  576     
  577     /* Check to see if the system user needs upgrading */
  578     if (context->user.usernum == 0)
  579     {   /* old system user with number 0, upgrade it */
  580         context->user.usernum = get_new_user_number();
  581         syslog(LOG_INFO, "context: upgrading system user \"%s\" from user number 0 to user number %ld", context->user.fullname, context->user.usernum);
  582         /* add user to the database */
  583         CtdlPutUser(&(context->user));
  584         cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1);
  585     }
  586 }
  587 
  588 
  589 /*
  590  * Cleanup any contexts that are left lying around
  591  */
  592 void context_cleanup(void)
  593 {
  594     CitContext *ptr = NULL;
  595     CitContext *rem = NULL;
  596 
  597     /*
  598      * Clean up the contexts.
  599      * There are no threads so no critical_section stuff is needed.
  600      */
  601     ptr = ContextList;
  602     
  603     /* We need to update the ContextList because some modules may want to itterate it
  604      * Question is should we NULL it before iterating here or should we just keep updating it
  605      * as we remove items?
  606      *
  607      * Answer is to NULL it first to prevent modules from doing any actions on the list at all
  608      */
  609     ContextList=NULL;
  610     while (ptr != NULL){
  611         /* Remove the session from the active list */
  612         rem = ptr->next;
  613         --num_sessions;
  614 
  615         syslog(LOG_DEBUG, "context: context_cleanup() purging session %d", ptr->cs_pid);
  616         RemoveContext(ptr);
  617         free (ptr);
  618         ptr = rem;
  619     }
  620 }
  621 
  622 
  623 
  624 /*
  625  * Purge all sessions which have the 'kill_me' flag set.
  626  * This function has code to prevent it from running more than once every
  627  * few seconds, because running it after every single unbind would waste a lot
  628  * of CPU time and keep the context list locked too much.  To force it to run
  629  * anyway, set "force" to nonzero.
  630  */
  631 void dead_session_purge(int force) {
  632     CitContext *ptr, *ptr2;     /* general-purpose utility pointer */
  633     CitContext *rem = NULL;     /* list of sessions to be destroyed */
  634     
  635     if (force == 0) {
  636         if ( (time(NULL) - last_purge) < 5 ) {
  637             return; /* Too soon, go away */
  638         }
  639     }
  640     time(&last_purge);
  641 
  642     if (try_critical_section(S_SESSION_TABLE))
  643         return;
  644         
  645     ptr = ContextList;
  646     while (ptr) {
  647         ptr2 = ptr;
  648         ptr = ptr->next;
  649         
  650         if ( (ptr2->state == CON_IDLE) && (ptr2->kill_me) ) {
  651             /* Remove the session from the active list */
  652             if (ptr2->prev) {
  653                 ptr2->prev->next = ptr2->next;
  654             }
  655             else {
  656                 ContextList = ptr2->next;
  657             }
  658             if (ptr2->next) {
  659                 ptr2->next->prev = ptr2->prev;
  660             }
  661 
  662             --num_sessions;
  663             /* And put it on our to-be-destroyed list */
  664             ptr2->next = rem;
  665             rem = ptr2;
  666         }
  667     }
  668     end_critical_section(S_SESSION_TABLE);
  669 
  670     /* Now that we no longer have the session list locked, we can take
  671      * our time and destroy any sessions on the to-be-killed list, which
  672      * is allocated privately on this thread's stack.
  673      */
  674     while (rem != NULL) {
  675         syslog(LOG_DEBUG, "context: dead_session_purge() purging session %d, reason=%d", rem->cs_pid, rem->kill_me);
  676         RemoveContext(rem);
  677         ptr = rem;
  678         rem = rem->next;
  679         free(ptr);
  680     }
  681 }
  682 
  683 
  684 
  685 
  686 
  687 /*
  688  * masterCC is the context we use when not attached to a session.  This
  689  * function initializes it.
  690  */
  691 void InitializeMasterCC(void) {
  692     memset(&masterCC, 0, sizeof(struct CitContext));
  693     masterCC.internal_pgm = 1;
  694     masterCC.cs_pid = 0;
  695 }
  696 
  697 
  698 
  699 
  700 
  701 /*
  702  * Set the "async waiting" flag for a session, if applicable
  703  */
  704 void set_async_waiting(struct CitContext *ccptr)
  705 {
  706     syslog(LOG_DEBUG, "context: setting async_waiting flag for session %d", ccptr->cs_pid);
  707     if (ccptr->is_async) {
  708         ccptr->async_waiting++;
  709         if (ccptr->state == CON_IDLE) {
  710             ccptr->state = CON_READY;
  711         }
  712     }
  713 }
  714 
  715 
  716 CTDL_MODULE_INIT(session)
  717 {
  718     if (!threading) {
  719     }
  720     return "session";
  721 }