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