* Working on async
[citadel.git] / citadel / context.c
1 /*
2  * $Id: sysdep.c 7989 2009-10-31 15:29:37Z davew $
3  *
4  * Citadel context management stuff.
5  * Here's where we (hopefully) have all the code that manipulates contexts.
6  *
7  * Copyright (c) 1987-2010 by the citadel.org team
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23
24 #include "sysdep.h"
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <stdio.h>
28 #include <fcntl.h>
29 #include <ctype.h>
30 #include <signal.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <sys/wait.h>
34 #include <sys/socket.h>
35 #include <syslog.h>
36 #include <sys/syslog.h>
37
38 #if TIME_WITH_SYS_TIME
39 # include <sys/time.h>
40 # include <time.h>
41 #else
42 # if HAVE_SYS_TIME_H
43 #  include <sys/time.h>
44 # else
45 #  include <time.h>
46 # endif
47 #endif
48
49 #include <limits.h>
50 #include <sys/resource.h>
51 #include <netinet/in.h>
52 #include <netinet/tcp.h>
53 #include <arpa/inet.h>
54 #include <netdb.h>
55 #include <sys/un.h>
56 #include <string.h>
57 #include <pwd.h>
58 #include <errno.h>
59 #include <stdarg.h>
60 #include <grp.h>
61 #include <libcitadel.h>
62 #include "citadel.h"
63 #include "server.h"
64 #include "sysdep_decls.h"
65 #include "citserver.h"
66 #include "support.h"
67 #include "config.h"
68 #include "database.h"
69 #include "housekeeping.h"
70 #include "modules/crypto/serv_crypto.h" /* Needed for init_ssl, client_write_ssl, client_read_ssl, destruct_ssl */
71 #include "ecrash.h"
72
73 #ifdef HAVE_SYS_SELECT_H
74 #include <sys/select.h>
75 #endif
76
77 #ifndef HAVE_SNPRINTF
78 #include "snprintf.h"
79 #endif
80
81 #include "ctdl_module.h"
82 #include "threads.h"
83 #include "user_ops.h"
84 #include "control.h"
85
86
87
88 citthread_key_t MyConKey;                               /* TSD key for MyContext() */
89
90
91 CitContext masterCC;
92 CitContext *ContextList = NULL;
93
94 time_t last_purge = 0;                          /* Last dead session purge */
95 int num_sessions = 0;                           /* Current number of sessions */
96
97 /* Flag for single user mode */
98 static int want_single_user = 0;
99
100 /* Try to go single user */
101
102 int CtdlTrySingleUser(void)
103 {
104         int can_do = 0;
105         
106         begin_critical_section(S_SINGLE_USER);
107         if (want_single_user)
108                 can_do = 0;
109         else
110         {
111                 can_do = 1;
112                 want_single_user = 1;
113         }
114         end_critical_section(S_SINGLE_USER);
115         return can_do;
116 }
117
118 void CtdlEndSingleUser(void)
119 {
120         begin_critical_section(S_SINGLE_USER);
121         want_single_user = 0;
122         end_critical_section(S_SINGLE_USER);
123 }
124
125
126 int CtdlWantSingleUser(void)
127 {
128         return want_single_user;
129 }
130
131 int CtdlIsSingleUser(void)
132 {
133         if (want_single_user)
134         {
135                 /* check for only one context here */
136                 if (num_sessions == 1)
137                         return TRUE;
138         }
139         return FALSE;
140 }
141
142
143
144
145
146 /*
147  * Locate a context by its session number and terminate it if the user is able.
148  * User can NOT terminate their current session.
149  * User CAN terminate any other session that has them logged in.
150  * Aide CAN terminate any session except the current one.
151  */
152 int CtdlTerminateOtherSession (int session_num)
153 {
154         int ret = 0;
155         CitContext *ccptr;
156
157         if (session_num == CC->cs_pid) {
158                 return TERM_NOTALLOWED;
159         }
160
161         CtdlLogPrintf(CTDL_DEBUG, "Locating session to kill\n");
162         begin_critical_section(S_SESSION_TABLE);
163         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
164                 if (session_num == ccptr->cs_pid) {
165                         ret |= TERM_FOUND;
166                         if ((ccptr->user.usernum == CC->user.usernum)
167                            || (CC->user.axlevel >= 6)) {
168                                 ret |= TERM_ALLOWED;
169                                 ccptr->kill_me = 1;
170                         }
171                 }
172         }
173         end_critical_section(S_SESSION_TABLE);
174         return ret;
175 }
176
177
178
179 /*
180  * Check to see if the user who we just sent mail to is logged in.  If yes,
181  * bump the 'new mail' counter for their session.  That enables them to
182  * receive a new mail notification without having to hit the database.
183  */
184 void BumpNewMailCounter(long which_user) 
185 {
186         CtdlBumpNewMailCounter(which_user);
187 }
188
189 void CtdlBumpNewMailCounter(long which_user)
190 {
191         CitContext *ptr;
192
193         begin_critical_section(S_SESSION_TABLE);
194
195         for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
196                 if (ptr->user.usernum == which_user) {
197                         ptr->newmail += 1;
198                 }
199         }
200
201         end_critical_section(S_SESSION_TABLE);
202 }
203
204
205 /*
206  * Check to see if a user is currently logged in
207  * Take care with what you do as a result of this test.
208  * The user may not have been logged in when this function was called BUT
209  * because of threading the user might be logged in before you test the result.
210  */
211 int CtdlIsUserLoggedIn (char *user_name)
212 {
213         CitContext *cptr;
214         int ret = 0;
215
216         begin_critical_section (S_SESSION_TABLE);
217         for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
218                 if (!strcasecmp(cptr->user.fullname, user_name)) {
219                         ret = 1;
220                         break;
221                 }
222         }
223         end_critical_section(S_SESSION_TABLE);
224         return ret;
225 }
226
227
228
229 /*
230  * Check to see if a user is currently logged in.
231  * Basically same as CtdlIsUserLoggedIn() but uses the user number instead.
232  * Take care with what you do as a result of this test.
233  * The user may not have been logged in when this function was called BUT
234  * because of threading the user might be logged in before you test the result.
235  */
236 int CtdlIsUserLoggedInByNum (long usernum)
237 {
238         CitContext *cptr;
239         int ret = 0;
240
241         begin_critical_section(S_SESSION_TABLE);
242         for (cptr = ContextList; cptr != NULL; cptr = cptr->next) {
243                 if (cptr->user.usernum == usernum) {
244                         ret = 1;
245                 }
246         }
247         end_critical_section(S_SESSION_TABLE);
248         return ret;
249 }
250
251
252
253 /*
254  * Return a pointer to the CitContext structure bound to the thread which
255  * called this function.  If there's no such binding (for example, if it's
256  * called by the housekeeper thread) then a generic 'master' CC is returned.
257  *
258  * This function is used *VERY* frequently and must be kept small.
259  */
260 CitContext *MyContext(void) {
261
262         register CitContext *c;
263
264         return ((c = (CitContext *) citthread_getspecific(MyConKey),
265                 c == NULL) ? &masterCC : c
266         );
267 }
268
269
270
271
272 /*
273  * Terminate idle sessions.  This function pounds through the session table
274  * comparing the current time to each session's time-of-last-command.  If an
275  * idle session is found it is terminated, then the search restarts at the
276  * beginning because the pointer to our place in the list becomes invalid.
277  */
278 void terminate_idle_sessions(void)
279 {
280         CitContext *ccptr;
281         time_t now;
282         int session_to_kill;
283         int killed = 0;
284         int longrunners = 0;
285
286         now = time(NULL);
287         session_to_kill = 0;
288         begin_critical_section(S_SESSION_TABLE);
289         for (ccptr = ContextList; ccptr != NULL; ccptr = ccptr->next) {
290                 if (  (ccptr!=CC)
291                 && (config.c_sleeping > 0)
292                 && (now - (ccptr->lastcmd) > config.c_sleeping) ) {
293                         if (!ccptr->dont_term) {
294                                 ccptr->kill_me = 1;
295                                 ++killed;
296                         }
297                         else 
298                                 longrunners ++;
299                 }
300         }
301         end_critical_section(S_SESSION_TABLE);
302         if (killed > 0)
303                 CtdlLogPrintf(CTDL_INFO, "Terminated %d idle sessions\n", killed);
304         if (longrunners > 0)
305                 CtdlLogPrintf(CTDL_INFO, "Didn't terminate %d protected idle sessions;\n", killed);
306 }
307
308
309
310 /*
311  * Terminate a session.
312  */
313 void RemoveContext (CitContext *con)
314 {
315         if (con==NULL) {
316                 CtdlLogPrintf(CTDL_ERR,
317                         "WARNING: RemoveContext() called with NULL!\n");
318                 return;
319         }
320         CtdlLogPrintf(CTDL_DEBUG, "RemoveContext() session %d\n", con->cs_pid);
321
322         /* Run any cleanup routines registered by loadable modules.
323          * Note: We have to "become_session()" because the cleanup functions
324          *       might make references to "CC" assuming it's the right one.
325          */
326         become_session(con);
327         CtdlUserLogout();
328         PerformSessionHooks(EVT_STOP);
329         become_session(NULL);
330
331         CtdlLogPrintf(CTDL_NOTICE, "[%3d] Session ended.\n", con->cs_pid);
332
333         /* If the client is still connected, blow 'em away. */
334         CtdlLogPrintf(CTDL_DEBUG, "Closing socket %d\n", con->client_socket);
335         close(con->client_socket);
336
337         /* If using AUTHMODE_LDAP, free the DN */
338         if (con->ldap_dn) {
339                 free(con->ldap_dn);
340                 con->ldap_dn = NULL;
341         }
342
343         FreeStrBuf(&con->MigrateBuf);
344         FreeStrBuf(&con->ReadBuf);
345         CtdlLogPrintf(CTDL_DEBUG, "Done with RemoveContext()\n");
346 }
347
348
349
350
351 /*
352  * Initialize a new context and place it in the list.  The session number
353  * used to be the PID (which is why it's called cs_pid), but that was when we
354  * had one process per session.  Now we just assign them sequentially, starting
355  * at 1 (don't change it to 0 because masterCC uses 0).
356  */
357 CitContext *CreateNewContext(void) {
358         CitContext *me;
359         static int next_pid = 0;
360
361         me = (CitContext *) malloc(sizeof(CitContext));
362         if (me == NULL) {
363                 CtdlLogPrintf(CTDL_ALERT, "citserver: can't allocate memory!!\n");
364                 return NULL;
365         }
366         memset(me, 0, sizeof(CitContext));
367         
368         /* Give the contaxt a name. Hopefully makes it easier to track */
369         strcpy (me->user.fullname, "SYS_notauth");
370         
371         /* The new context will be created already in the CON_EXECUTING state
372          * in order to prevent another thread from grabbing it while it's
373          * being set up.
374          */
375         me->state = CON_EXECUTING;
376         /*
377          * Generate a unique session number and insert this context into
378          * the list.
379          */
380         me->MigrateBuf = NewStrBuf();
381         me->ReadBuf = NewStrBuf();
382         begin_critical_section(S_SESSION_TABLE);
383         me->cs_pid = ++next_pid;
384         me->prev = NULL;
385         me->next = ContextList;
386         ContextList = me;
387         if (me->next != NULL) {
388                 me->next->prev = me;
389         }
390         ++num_sessions;
391         end_critical_section(S_SESSION_TABLE);
392         return (me);
393 }
394
395
396 /*
397  * Return an array containing a copy of the context list.
398  * This allows worker threads to perform "for each context" operations without
399  * having to lock and traverse the live list.
400  */
401 CitContext *CtdlGetContextArray(int *count)
402 {
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 /*
425  * This function fills in a context and its user field correctly
426  * Then creates/loads that user
427  */
428 void CtdlFillSystemContext(CitContext *context, char *name)
429 {
430         char sysname[USERNAME_SIZE];
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         /* internal_create_user has the side effect of loading the user regardless of wether they
438          * already existed or needed to be created
439          */
440         internal_create_user (sysname, &(context->user), -1) ;
441         
442         /* Check to see if the system user needs upgrading */
443         if (context->user.usernum == 0)
444         {       /* old system user with number 0, upgrade it */
445                 context->user.usernum = get_new_user_number();
446                 CtdlLogPrintf(CTDL_DEBUG, "Upgrading system user \"%s\" from user number 0 to user number %d\n", context->user.fullname, context->user.usernum);
447                 /* add user to the database */
448                 CtdlPutUser(&(context->user));
449                 cdb_store(CDB_USERSBYNUMBER, &(context->user.usernum), sizeof(long), context->user.fullname, strlen(context->user.fullname)+1);
450         }
451 }
452
453 /*
454  * Cleanup any contexts that are left lying around
455  */
456 void context_cleanup(void)
457 {
458         CitContext *ptr = NULL;
459         CitContext *rem = NULL;
460
461         /*
462          * Clean up the contexts.
463          * There are no threads so no critical_section stuff is needed.
464          */
465         ptr = ContextList;
466         
467         /* We need to update the ContextList because some modules may want to itterate it
468          * Question is should we NULL it before iterating here or should we just keep updating it
469          * as we remove items?
470          *
471          * Answer is to NULL it first to prevent modules from doing any actions on the list at all
472          */
473         ContextList=NULL;
474         while (ptr != NULL){
475                 /* Remove the session from the active list */
476                 rem = ptr->next;
477                 --num_sessions;
478                 
479                 CtdlLogPrintf(CTDL_DEBUG, "Purging session %d\n", ptr->cs_pid);
480                 RemoveContext(ptr);
481                 free (ptr);
482                 ptr = rem;
483         }
484 }
485
486
487
488 /*
489  * Terminate another session.
490  * (This could justifiably be moved out of sysdep.c because it
491  * no longer does anything that is system-dependent.)
492  */
493 void kill_session(int session_to_kill) {
494         CitContext *ptr;
495
496         begin_critical_section(S_SESSION_TABLE);
497         for (ptr = ContextList; ptr != NULL; ptr = ptr->next) {
498                 if (ptr->cs_pid == session_to_kill) {
499                         ptr->kill_me = 1;
500                 }
501         }
502         end_critical_section(S_SESSION_TABLE);
503 }
504
505 /*
506  * Purge all sessions which have the 'kill_me' flag set.
507  * This function has code to prevent it from running more than once every
508  * few seconds, because running it after every single unbind would waste a lot
509  * of CPU time and keep the context list locked too much.  To force it to run
510  * anyway, set "force" to nonzero.
511  */
512 void dead_session_purge(int force) {
513         CitContext *ptr, *ptr2;         /* general-purpose utility pointer */
514         CitContext *rem = NULL; /* list of sessions to be destroyed */
515         
516         if (force == 0) {
517                 if ( (time(NULL) - last_purge) < 5 ) {
518                         return; /* Too soon, go away */
519                 }
520         }
521         time(&last_purge);
522
523         if (try_critical_section(S_SESSION_TABLE))
524                 return;
525                 
526         ptr = ContextList;
527         while (ptr) {
528                 ptr2 = ptr;
529                 ptr = ptr->next;
530                 
531                 if ( (ptr2->state == CON_IDLE) && (ptr2->kill_me) ) {
532                         /* Remove the session from the active list */
533                         if (ptr2->prev) {
534                                 ptr2->prev->next = ptr2->next;
535                         }
536                         else {
537                                 ContextList = ptr2->next;
538                         }
539                         if (ptr2->next) {
540                                 ptr2->next->prev = ptr2->prev;
541                         }
542
543                         --num_sessions;
544                         /* And put it on our to-be-destroyed list */
545                         ptr2->next = rem;
546                         rem = ptr2;
547                 }
548         }
549         end_critical_section(S_SESSION_TABLE);
550
551         /* Now that we no longer have the session list locked, we can take
552          * our time and destroy any sessions on the to-be-killed list, which
553          * is allocated privately on this thread's stack.
554          */
555         while (rem != NULL) {
556                 CtdlLogPrintf(CTDL_DEBUG, "Purging session %d\n", rem->cs_pid);
557                 RemoveContext(rem);
558                 ptr = rem;
559                 rem = rem->next;
560                 free(ptr);
561         }
562 }
563
564
565
566
567
568 /*
569  * masterCC is the context we use when not attached to a session.  This
570  * function initializes it.
571  */
572 void InitializeMasterCC(void) {
573         memset(&masterCC, 0, sizeof( CitContext));
574         masterCC.internal_pgm = 1;
575         masterCC.cs_pid = 0;
576 }
577
578
579
580
581 /*
582  * Bind a thread to a context.  (It's inline merely to speed things up.)
583  */
584 INLINE void become_session(CitContext *which_con) {
585         citthread_setspecific(MyConKey, (void *)which_con );
586 }
587
588
589 /*
590  * Set the "async waiting" flag for a session, if applicable
591  */
592 void set_async_waiting(struct CitContext *ccptr)
593 {
594         CtdlLogPrintf(CTDL_DEBUG, "Setting async_waiting flag for session %d\n", ccptr->cs_pid);
595         if (ccptr->is_async) {
596                 ccptr->async_waiting++;
597                 if (ccptr->state == CON_IDLE) {
598                         ccptr->state = CON_READY;
599                 }
600         }
601 }