random boilerplate
[citadel.git] / citadel / context.c
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 }