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