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