citadel
About: Citadel is an advanced messaging and collaboration system for groupware and BBS applications (preferred OS: Linux).
  Fossies Dox: citadel.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

Loading...
Searching...
No Matches
context.c
Go to the documentation of this file.
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
18pthread_key_t MyConKey; // TSD key for MyContext()
21int num_sessions = 0; // Current number of sessions
22int next_pid = 0;
23
24// Flag for single user mode
25static int want_single_user = 0;
26
27// Try to go single user
28
30 int can_do = 0;
31
33 if (want_single_user) {
34 can_do = 0;
35 }
36 else {
37 can_do = 1;
39 }
41 return can_do;
42}
43
44
50
51
53 return want_single_user;
54}
55
56
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.
71int 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");
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) {
95 }
96 else {
97 ccptr->kill_me = KILLME_IDLE;
98 }
100 }
101 else {
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.
112void CtdlBumpNewMailCounter(long which_user) {
113 CitContext *ptr;
114
116
117 for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
118 if (ptr->user.usernum == which_user) {
119 ptr->newmail += 1;
120 }
121 }
122
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.
131int CtdlIsUserLoggedIn(char *user_name) {
132 CitContext *cptr;
133 int ret = 0;
134
136 for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
137 if (!strcasecmp(cptr->user.fullname, user_name)) {
138 ret = 1;
139 break;
140 }
141 }
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.
152int CtdlIsUserLoggedInByNum (long usernum) {
153 CitContext *cptr;
154 int ret = 0;
155
157 for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
158 if (cptr->user.usernum == usernum) {
159 ret = 1;
160 }
161 }
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.
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.
183 CitContext *ccptr;
184 time_t now;
185 int killed = 0;
186
187 now = time(NULL);
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 }
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.
208 CitContext *ccptr;
209 int killed = 0;
210
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 }
221 if (killed > 0) {
222 syslog(LOG_INFO, "context: flushed %d stuck sessions", killed);
223 }
224}
225
226
227// Terminate a session.
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);
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).
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
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;
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).
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#ifdef HAVE_OPENSSL
322 me->ssl = NULL;
323#endif
324
325 me->download_fp = NULL;
326 me->upload_fp = NULL;
327 me->ma = NULL;
328 me->ldap_dn = NULL;
329 me->session_specific_data = NULL;
330
331 me->CIT_ICAL = NULL;
332
333 me->cached_msglist = NULL;
334 me->download_fp = NULL;
335 me->upload_fp = NULL;
336 me->client_socket = 0;
337
338 me->MigrateBuf = NewStrBuf();
339 me->RecvBuf.Buf = NewStrBuf();
340
342
343 me->cs_pid = ++next_pid;
344 me->prev = NULL;
345 me->next = ContextList;
346 me->lastcmd = time(NULL); // set lastcmd to now to prevent idle timer infanticide
347 ContextList = me;
348 if (me->next != NULL) {
349 me->next->prev = me;
350 }
351 ++num_sessions;
352
354 return (me);
355}
356
357
358// Return an array containing a copy of the context list.
359// This allows worker threads to perform "for each context" operations without
360// having to lock and traverse the live list.
362 int nContexts, i;
363 CitContext *nptr, *cptr;
364
365 nContexts = num_sessions;
366 nptr = malloc(sizeof(CitContext) * nContexts);
367 if (!nptr) {
368 *count = 0;
369 return NULL;
370 }
372 for (cptr = ContextList, i=0; cptr != NULL && i < nContexts; cptr = cptr->next, i++) {
373 memcpy(&nptr[i], cptr, sizeof (CitContext));
374 }
376
377 *count = i;
378 return nptr;
379}
380
381
382// Back-end function for starting a session
384
385 // Initialize some variables specific to our context.
386 con->logged_in = 0;
387 con->internal_pgm = 0;
388 con->download_fp = NULL;
389 con->upload_fp = NULL;
390 con->cached_msglist = NULL;
391 con->cached_num_msgs = 0;
392 con->FirstExpressMessage = NULL;
393 time(&con->lastcmd);
394 time(&con->lastidle);
395 strcpy(con->lastcmdname, " ");
396 strcpy(con->cs_clientname, "(unknown)");
397 strcpy(con->curr_user, NLI);
398 *con->cs_clientinfo = '\0';
399 safestrncpy(con->cs_host, CtdlGetConfigStr("c_fqdn"), sizeof con->cs_host);
400 safestrncpy(con->cs_addr, "", sizeof con->cs_addr);
401 con->cs_UDSclientUID = -1;
402 con->cs_host[sizeof con->cs_host - 1] = 0;
403 if (!CC->is_local_client) {
404 locate_host(con->cs_host, sizeof con->cs_host,
405 con->cs_addr, sizeof con->cs_addr,
406 con->client_socket
407 );
408 }
409 else {
410 con->cs_host[0] = 0;
411 con->cs_addr[0] = 0;
412 }
413 con->cs_flags = 0;
414 con->nologin = 0;
415
416 if (((CtdlGetConfigInt("c_maxsessions") > 0)&&(num_sessions > CtdlGetConfigInt("c_maxsessions"))) || CtdlWantSingleUser()) {
417 con->nologin = 1;
418 }
419
420 syslog(LOG_INFO, "context: session (%s) started from %s (%s) uid=%d",
421 con->ServiceName, con->cs_host, con->cs_addr, con->cs_UDSclientUID
422 );
423
424 // Run any session startup routines registered by loadable modules
426}
427
428
429// This function fills in a context and its user field correctly
430// Then creates/loads that user
431void CtdlFillSystemContext(CitContext *context, char *name) {
432 char sysname[SIZ];
433 long len;
434
435 memset(context, 0, sizeof(CitContext));
436 context->internal_pgm = 1;
437 context->cs_pid = 0;
438 strcpy (sysname, "SYS_");
439 strcat (sysname, name);
440 len = strlen(sysname);
441 memcpy(context->curr_user, sysname, len + 1);
442 context->client_socket = (-1);
443 context->state = CON_SYS;
444 context->ServiceName = name;
445
446 // internal_create_user has the side effect of loading the user regardless of whether they
447 // already existed or needed to be created
448 internal_create_user(sysname, &(context->user), -1) ;
449
450 // Check to see if the system user needs upgrading
451 if (context->user.usernum == 0) { // old system user with number 0, upgrade it
452 context->user.usernum = get_new_user_number();
453 syslog(LOG_INFO, "context: upgrading system user \"%s\" from user number 0 to user number %ld", context->user.fullname, context->user.usernum);
454 // add user to the database
455 CtdlPutUser(&(context->user));
456 cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1);
457 }
458}
459
460
461// Cleanup any contexts that are left lying around
462void context_cleanup(void) {
463 CitContext *ptr = NULL;
464 CitContext *rem = NULL;
465
466 // Clean up the contexts.
467 // There are no threads so no critical_section stuff is needed.
468 ptr = ContextList;
469
470 // We need to update the ContextList because some modules may want to iterate it
471 // Question is should we NULL it before iterating here or should we just keep updating it as we remove items?
472 // Answer is to NULL it first to prevent modules from doing any actions on the list at all.
473 ContextList=NULL;
474 while (ptr != NULL){
475 // Remove the session from the active list
476 rem = ptr->next;
477 --num_sessions;
478
479 syslog(LOG_DEBUG, "context: context_cleanup() purging session %d", ptr->cs_pid);
480 RemoveContext(ptr);
481 free (ptr);
482 ptr = rem;
483 }
484}
485
486
487// Purge all sessions which have the 'kill_me' flag set.
488// This function has code to prevent it from running more than once every
489// few seconds, because running it after every single unbind would waste a lot
490// of CPU time and keep the context list locked too much. To force it to run
491// anyway, set "force" to nonzero.
492void dead_session_purge(int force) {
493 CitContext *ptr, *ptr2; // general-purpose utility pointer
494 CitContext *rem = NULL; // list of sessions to be destroyed
495 static time_t last_purge = 0; // Last dead session purge
496
497 if (force == 0) {
498 if ( (time(NULL) - last_purge) < 5 ) {
499 return; // Too soon, go away
500 }
501 }
502 time(&last_purge);
503
505 ptr = ContextList;
506 while (ptr) {
507 ptr2 = ptr;
508 ptr = ptr->next;
509
510 if ( (ptr2->state == CON_IDLE) && (ptr2->kill_me) ) {
511 // Remove the session from the active list
512 if (ptr2->prev) {
513 ptr2->prev->next = ptr2->next;
514 }
515 else {
516 ContextList = ptr2->next;
517 }
518 if (ptr2->next) {
519 ptr2->next->prev = ptr2->prev;
520 }
521
522 --num_sessions;
523 // And put it on our to-be-destroyed list
524 ptr2->next = rem;
525 rem = ptr2;
526 }
527 //else if (ptr2->kill_me) {
528 // 2023: this was a source of segfaults but I think we fixed it
529 //syslog(LOG_DEBUG, "context: session %d is timed out but non-idle", ptr->cs_pid);
530 //}
531 }
533
534 // Now that we no longer have the session list locked, we can take
535 // our time and destroy any sessions on the to-be-killed list, which
536 // is allocated privately on this thread's stack.
537 while (rem != NULL) {
538 syslog(LOG_DEBUG, "context: dead_session_purge() purging session %d, reason=%d", rem->cs_pid, rem->kill_me);
539 RemoveContext(rem);
540 ptr = rem;
541 rem = rem->next;
542 free(ptr);
543 }
544}
545
546
547// masterCC is the context we use when not attached to a session. This function initializes it.
549 memset(&masterCC, 0, sizeof(struct CitContext));
551 masterCC.cs_pid = 0;
552}
553
554
555// Set the "async waiting" flag for a session, if applicable
556void set_async_waiting(struct CitContext *ccptr) {
557 syslog(LOG_DEBUG, "context: setting async_waiting flag for session %d", ccptr->cs_pid);
558 if (ccptr->is_async) {
559 ccptr->async_waiting++;
560 if (ccptr->state == CON_IDLE) {
561 ccptr->state = CON_READY;
562 }
563 }
564}
565
566
568 return "session";
569}
@ KILLME_IDLE
@ KILLME_ADMIN_TERMINATE
@ CDB_USERSBYNUMBER
@ EVT_START
@ EVT_STOP
@ S_SINGLE_USER
@ S_SESSION_TABLE
char * CtdlGetConfigStr(char *key)
Definition config.c:360
int CtdlGetConfigInt(char *key)
Definition config.c:386
long CtdlGetConfigLong(char *key)
Definition config.c:394
void terminate_all_sessions(void)
Definition context.c:207
void terminate_idle_sessions(void)
Definition context.c:182
int num_sessions
Definition context.c:21
void InitializeMasterCC(void)
Definition context.c:548
CitContext * MyContext(void)
Definition context.c:172
int CtdlIsUserLoggedInByNum(long usernum)
Definition context.c:152
CitContext * ContextList
Definition context.c:20
int CtdlWantSingleUser(void)
Definition context.c:52
CitContext masterCC
Definition context.c:19
CitContext * CreateNewContext(void)
Definition context.c:269
pthread_key_t MyConKey
Definition context.c:18
void CtdlBumpNewMailCounter(long which_user)
Definition context.c:112
void RemoveContext(CitContext *con)
Definition context.c:228
int CtdlTerminateOtherSession(int session_num)
Definition context.c:71
int CtdlTrySingleUser(void)
Definition context.c:29
int next_pid
Definition context.c:22
void set_async_waiting(struct CitContext *ccptr)
Definition context.c:556
void dead_session_purge(int force)
Definition context.c:492
int CtdlIsUserLoggedIn(char *user_name)
Definition context.c:131
void context_cleanup(void)
Definition context.c:462
void CtdlFillSystemContext(CitContext *context, char *name)
Definition context.c:431
void CtdlEndSingleUser(void)
Definition context.c:45
static int want_single_user
Definition context.c:25
int CtdlIsSingleUser(void)
Definition context.c:57
CitContext * CloneContext(CitContext *CloneMe)
Definition context.c:305
void begin_session(CitContext *con)
Definition context.c:383
CitContext * CtdlGetContextArray(int *count)
Definition context.c:361
#define TERM_FOUND
Definition context.h:155
#define CC
Definition context.h:131
#define TERM_NOTALLOWED
Definition context.h:158
#define TERM_ALLOWED
Definition context.h:156
static INLINE void become_session(CitContext *which_con)
Definition context.h:161
@ CON_READY
Definition context.h:24
@ CON_SYS
Definition context.h:26
@ CON_IDLE
Definition context.h:21
@ CON_EXECUTING
Definition context.h:25
long get_new_user_number(void)
Definition control.c:178
void CtdlPutUser(struct ctdluser *usbuf)
Definition user_ops.c:86
void CtdlUserLogout(void)
Definition user_ops.c:617
#define CTDL_MODULE_INIT(module_name)
Definition ctdl_module.h:51
int(* cdb_store)(int, const void *, int, void *, int)
Definition database.c:30
void locate_host(char *tbuf, size_t n, char *abuf, size_t na, int client_socket)
Definition locate_host.c:50
void * malloc(unsigned)
void free(void *)
void PerformSessionHooks(int EventType)
int cs_pid
Definition context.h:40
int logged_in
Definition context.h:65
StrBuf * MigrateBuf
Definition context.h:51
char lastcmdname[5]
Definition context.h:71
char cs_clientname[32]
Definition context.h:81
FILE * download_fp
Definition context.h:91
StrBuf * StatusMessage
Definition context.h:58
int is_async
Definition context.h:73
struct ctdluser user
Definition context.h:101
time_t lastidle
Definition context.h:43
int newmail
Definition context.h:107
struct cit_ical * CIT_ICAL
Definition context.h:115
const char * ServiceName
Definition context.h:117
CitContext * next
Definition context.h:38
unsigned cs_flags
Definition context.h:72
int nologin
Definition context.h:67
CitContext * prev
Definition context.h:37
char curr_user[64]
Definition context.h:64
void * session_specific_data
Definition context.h:114
IOBuffer SBuf
Definition context.h:49
char cs_addr[64]
Definition context.h:83
StrBuf * sMigrateBuf
Definition context.h:52
FILE * upload_fp
Definition context.h:94
struct ExpressMessage * FirstExpressMessage
Definition context.h:105
int kill_me
Definition context.h:45
CCState state
Definition context.h:44
int cached_num_msgs
Definition context.h:126
struct ma_info * ma
Definition context.h:116
time_t lastcmd
Definition context.h:42
StrBuf * redirect_buffer
Definition context.h:57
int async_waiting
Definition context.h:74
IOBuffer SendBuf
Definition context.h:47
char * ldap_dn
Definition context.h:119
char cs_host[64]
Definition context.h:82
long * cached_msglist
Definition context.h:125
uid_t cs_UDSclientUID
Definition context.h:80
int client_socket
Definition context.h:54
IOBuffer RecvBuf
Definition context.h:48
int internal_pgm
Definition context.h:66
char cs_clientinfo[256]
Definition context.h:79
char fullname[64]
Definition server.h:152
long usernum
Definition server.h:149
#define SIZ
Definition sysconfig.h:22
#define NLI
Definition sysconfig.h:11
void client_close(void)
Definition sysdep.c:280
void begin_critical_section(int which_one)
Definition threads.c:50
void end_critical_section(int which_one)
Definition threads.c:63
int internal_create_user(char *username, struct ctdluser *usbuf, uid_t uid)
Definition user_ops.c:864