e5029fe51566f46a132fa598641f8a9ce6c3985f
[citadel.git] / citadel / citserver.c
1 /* 
2  * Main source module for the Citadel server
3  *
4  * Copyright (c) 1987-2011 by the citadel.org team
5  *
6  * This program is open source software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License, version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 #include <stdio.h>
16 #include "sysdep.h"
17 #if TIME_WITH_SYS_TIME
18 # include <sys/time.h>
19 # include <time.h>
20 #else
21 # if HAVE_SYS_TIME_H
22 #  include <sys/time.h>
23 # else
24 #  include <time.h>
25 # endif
26 #endif
27
28 #if HAVE_BACKTRACE
29 #include <execinfo.h>
30 #endif
31 #include <libcitadel.h>
32
33 #include "ctdl_module.h"
34 #include "housekeeping.h"
35 #include "locate_host.h"
36 #include "citserver.h"
37 #include "user_ops.h"
38 #include "control.h"
39 #include "config.h"
40
41 char *unique_session_numbers;
42 int ScheduledShutdown = 0;
43 time_t server_startup_time;
44 int panic_fd;
45 int openid_level_supported = 0;
46
47 /*
48  * print the actual stack frame.
49  */
50 void cit_backtrace(void)
51 {
52 #ifdef HAVE_BACKTRACE
53         void *stack_frames[50];
54         size_t size, i;
55         char **strings;
56
57
58         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
59         strings = backtrace_symbols(stack_frames, size);
60         for (i = 0; i < size; i++) {
61                 if (strings != NULL)
62                         syslog(LOG_ALERT, "%s\n", strings[i]);
63                 else
64                         syslog(LOG_ALERT, "%p\n", stack_frames[i]);
65         }
66         free(strings);
67 #endif
68 }
69
70 void cit_oneline_backtrace(void)
71 {
72 #ifdef HAVE_BACKTRACE
73         void *stack_frames[50];
74         size_t size, i;
75         char **strings;
76         StrBuf *Buf;
77
78         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
79         strings = backtrace_symbols(stack_frames, size);
80         if (size > 0)
81         {
82                 Buf = NewStrBuf();
83                 for (i = 1; i < size; i++) {
84                         if (strings != NULL)
85                                 StrBufAppendPrintf(Buf, "%s : ", strings[i]);
86                         else
87                                 StrBufAppendPrintf(Buf, "%p : ", stack_frames[i]);
88                 }
89                 free(strings);
90                 syslog(LOG_ALERT, "%s\n", ChrPtr(Buf));
91                 FreeStrBuf(&Buf);
92         }
93 #endif
94 }
95
96 /*
97  * print the actual stack frame.
98  */
99 void cit_panic_backtrace(int SigNum)
100 {
101 #ifdef HAVE_BACKTRACE
102         void *stack_frames[10];
103         size_t size, i;
104         char **strings;
105
106         printf("caught signal 11\n");
107         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
108         strings = backtrace_symbols(stack_frames, size);
109         for (i = 0; i < size; i++) {
110                 if (strings != NULL)
111                         syslog(LOG_ALERT, "%s\n", strings[i]);
112                 else
113                         syslog(LOG_ALERT, "%p\n", stack_frames[i]);
114         }
115         free(strings);
116 #endif
117         exit(-1);
118 }
119
120 /*
121  * Various things that need to be initialized at startup
122  */
123 void master_startup(void) {
124         struct timeval tv;
125         unsigned int seed;
126         FILE *urandom;
127         struct ctdlroom qrbuf;
128         int rv;
129         
130         syslog(LOG_DEBUG, "master_startup() started\n");
131         time(&server_startup_time);
132         get_config();
133
134         syslog(LOG_INFO, "Opening databases\n");
135         open_databases();
136         check_ref_counts();
137
138         syslog(LOG_INFO, "Creating base rooms (if necessary)\n");
139         CtdlCreateRoom(config.c_baseroom,       0, "", 0, 1, 0, VIEW_BBS);
140         CtdlCreateRoom(AIDEROOM,                3, "", 0, 1, 0, VIEW_BBS);
141         CtdlCreateRoom(SYSCONFIGROOM,           3, "", 0, 1, 0, VIEW_BBS);
142         CtdlCreateRoom(config.c_twitroom,       0, "", 0, 1, 0, VIEW_BBS);
143
144         /* The "Local System Configuration" room doesn't need to be visible */
145         if (CtdlGetRoomLock(&qrbuf, SYSCONFIGROOM) == 0) {
146                 qrbuf.QRflags2 |= QR2_SYSTEM;
147                 CtdlPutRoomLock(&qrbuf);
148         }
149
150         /* Aide needs to be public postable, else we're not RFC conformant. */
151         if (CtdlGetRoomLock(&qrbuf, AIDEROOM) == 0) {
152                 qrbuf.QRflags2 |= QR2_SMTP_PUBLIC;
153                 CtdlPutRoomLock(&qrbuf);
154         }
155
156         syslog(LOG_INFO, "Seeding the pseudo-random number generator...\n");
157         urandom = fopen("/dev/urandom", "r");
158         if (urandom != NULL) {
159                 rv = fread(&seed, sizeof seed, 1, urandom);
160                 if (rv == -1)
161                         syslog(LOG_EMERG, "failed to read random seed: %s\n", 
162                                strerror(errno));
163                 fclose(urandom);
164         }
165         else {
166                 gettimeofday(&tv, NULL);
167                 seed = tv.tv_usec;
168         }
169         srand(seed);
170         srandom(seed);
171
172         put_config();
173
174         syslog(LOG_DEBUG, "master_startup() finished\n");
175 }
176
177
178 /*
179  * Cleanup routine to be called when the server is shutting down.
180  */
181 void master_cleanup(int exitcode) {
182         struct CleanupFunctionHook *fcn;
183         static int already_cleaning_up = 0;
184
185         if (already_cleaning_up) while(1) usleep(1000000);
186         already_cleaning_up = 1;
187
188         /* Run any cleanup routines registered by loadable modules */
189         for (fcn = CleanupHookTable; fcn != NULL; fcn = fcn->next) {
190                 (*fcn->h_function_pointer)();
191         }
192
193         /* Close the AdjRefCount queue file */
194         AdjRefCount(-1, 0);
195
196         /* Do system-dependent stuff */
197         sysdep_master_cleanup();
198         
199         /* Close databases */
200         syslog(LOG_INFO, "Closing databases\n");
201         close_databases();
202
203         /* If the operator requested a halt but not an exit, halt here. */
204         if (shutdown_and_halt) {
205                 syslog(LOG_NOTICE, "citserver: Halting server without exiting.\n");
206                 fflush(stdout); fflush(stderr);
207                 while(1) {
208                         sleep(32767);
209                 }
210         }
211         
212         release_control();
213
214         /* Now go away. */
215         syslog(LOG_NOTICE, "citserver: Exiting with status %d\n", exitcode);
216         fflush(stdout); fflush(stderr);
217         
218         if (restart_server != 0)
219                 exit(1);
220         if ((running_as_daemon != 0) && ((exitcode == 0) ))
221                 exitcode = CTDLEXIT_SHUTDOWN;
222         exit(exitcode);
223 }
224
225
226
227 /*
228  * returns an asterisk if there are any instant messages waiting,
229  * space otherwise.
230  */
231 char CtdlCheckExpress(void) {
232         if (CC->FirstExpressMessage == NULL) {
233                 return(' ');
234         }
235         else {
236                 return('*');
237         }
238 }
239
240
241 /*
242  * Check originating host against the public_clients file.  This determines
243  * whether the client is allowed to change the hostname for this session
244  * (for example, to show the location of the user rather than the location
245  * of the client).
246  */
247 int CtdlIsPublicClient(void)
248 {
249         char buf[1024];
250         char addrbuf[1024];
251         FILE *fp;
252         int i;
253         char *public_clientspos;
254         char *public_clientsend;
255         char *paddr = NULL;
256         struct stat statbuf;
257         static time_t pc_timestamp = 0;
258         static char public_clients[SIZ];
259         static char public_clients_file[SIZ];
260
261 #define LOCALHOSTSTR "127.0.0.1"
262
263         snprintf(public_clients_file, sizeof public_clients_file, "%s/public_clients", ctdl_etc_dir);
264
265         /*
266          * Check the time stamp on the public_clients file.  If it's been
267          * updated since the last time we were here (or if this is the first
268          * time we've been through the loop), read its contents and learn
269          * the IP addresses of the listed hosts.
270          */
271         if (stat(public_clients_file, &statbuf) != 0) {
272                 /* No public_clients file exists, so bail out */
273                 syslog(LOG_WARNING, "Warning: '%s' does not exist\n", 
274                                 public_clients_file);
275                 return(0);
276         }
277
278         if (statbuf.st_mtime > pc_timestamp) {
279                 begin_critical_section(S_PUBLIC_CLIENTS);
280                 syslog(LOG_INFO, "Loading %s\n", public_clients_file);
281
282                 public_clientspos = &public_clients[0];
283                 public_clientsend = public_clientspos + SIZ;
284                 safestrncpy(public_clientspos, LOCALHOSTSTR, sizeof public_clients);
285                 public_clientspos += sizeof(LOCALHOSTSTR) - 1;
286                 
287                 if (hostname_to_dotted_quad(addrbuf, config.c_fqdn) == 0) {
288                         *(public_clientspos++) = '|';
289                         paddr = &addrbuf[0];
290                         while (!IsEmptyStr (paddr) && 
291                                (public_clientspos < public_clientsend))
292                                 *(public_clientspos++) = *(paddr++);
293                 }
294
295                 fp = fopen(public_clients_file, "r");
296                 if (fp != NULL) 
297                         while ((fgets(buf, sizeof buf, fp)!=NULL) &&
298                                (public_clientspos < public_clientsend)){
299                                 char *ptr;
300                                 ptr = buf;
301                                 while (!IsEmptyStr(ptr)) {
302                                         if (*ptr == '#') {
303                                                 *ptr = 0;
304                                                 break;
305                                         }
306                                 else ptr++;
307                                 }
308                                 ptr--;
309                                 while (ptr>buf && isspace(*ptr)) {
310                                         *(ptr--) = 0;
311                                 }
312                                 if (hostname_to_dotted_quad(addrbuf, buf) == 0) {
313                                         *(public_clientspos++) = '|';
314                                         paddr = addrbuf;
315                                         while (!IsEmptyStr(paddr) && 
316                                                (public_clientspos < public_clientsend)){
317                                                 *(public_clientspos++) = *(paddr++);
318                                         }
319                                 }
320                         }
321                 if (fp != NULL) fclose(fp);
322                 pc_timestamp = time(NULL);
323                 end_critical_section(S_PUBLIC_CLIENTS);
324         }
325
326         syslog(LOG_DEBUG, "Checking whether %s is a local or public client\n",
327                 CC->cs_addr);
328         for (i=0; i<num_parms(public_clients); ++i) {
329                 extract_token(addrbuf, public_clients, i, '|', sizeof addrbuf);
330                 if (!strcasecmp(CC->cs_addr, addrbuf)) {
331                         syslog(LOG_DEBUG, "... yes its local.\n");
332                         return(1);
333                 }
334         }
335
336         /* No hits.  This is not a public client. */
337         syslog(LOG_DEBUG, "... no it isn't.\n");
338         return(0);
339 }
340
341
342
343
344
345 void citproto_begin_session() {
346         if (CC->nologin==1) {
347                 cprintf("%d %s: Too many users are already online (maximum is %d)\n",
348                         ERROR + MAX_SESSIONS_EXCEEDED,
349                         config.c_nodename, config.c_maxsessions
350                 );
351                 CC->kill_me = KILLME_MAX_SESSIONS_EXCEEDED;
352         }
353         else {
354                 cprintf("%d %s Citadel server ready.\n", CIT_OK, config.c_nodename);
355                 CC->can_receive_im = 1;
356         }
357 }
358
359
360 void citproto_begin_admin_session() {
361         CC->internal_pgm = 1;
362         cprintf("%d %s Citadel server ADMIN CONNECTION ready.\n", CIT_OK, config.c_nodename);
363 }
364
365
366
367
368 /*
369  * This loop recognizes all server commands.
370  */
371 void do_command_loop(void) {
372         char cmdbuf[SIZ];
373         
374         time(&CC->lastcmd);
375         memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */
376         if (client_getln(cmdbuf, sizeof cmdbuf) < 1) {
377                 syslog(LOG_ERR, "Citadel client disconnected: ending session.\n");
378                 CC->kill_me = KILLME_CLIENT_DISCONNECTED;
379                 return;
380         }
381
382         /* Log the server command, but don't show passwords... */
383         if ( (strncasecmp(cmdbuf, "PASS", 4)) && (strncasecmp(cmdbuf, "SETP", 4)) ) {
384                 syslog(LOG_INFO, "[%d][%s(%ld)] %s",
385                         CC->cs_pid, CC->curr_user, CC->user.usernum, cmdbuf
386                 );
387         }
388         else {
389                 syslog(LOG_INFO, "[%d][%s(%ld)] <password command hidden from log>",
390                         CC->cs_pid, CC->curr_user, CC->user.usernum
391                 );
392         }
393
394         buffer_output();
395
396         /*
397          * Let other clients see the last command we executed, and
398          * update the idle time, but not NOOP, QNOP, PEXP, GEXP, RWHO, or TIME.
399          */
400         if ( (strncasecmp(cmdbuf, "NOOP", 4))
401            && (strncasecmp(cmdbuf, "QNOP", 4))
402            && (strncasecmp(cmdbuf, "PEXP", 4))
403            && (strncasecmp(cmdbuf, "GEXP", 4))
404            && (strncasecmp(cmdbuf, "RWHO", 4))
405            && (strncasecmp(cmdbuf, "TIME", 4)) ) {
406                 strcpy(CC->lastcmdname, "    ");
407                 safestrncpy(CC->lastcmdname, cmdbuf, sizeof(CC->lastcmdname));
408                 time(&CC->lastidle);
409         }
410         
411         if ((strncasecmp(cmdbuf, "ENT0", 4))
412            && (strncasecmp(cmdbuf, "MESG", 4))
413            && (strncasecmp(cmdbuf, "MSGS", 4)))
414         {
415            CC->cs_flags &= ~CS_POSTING;
416         }
417                    
418         if (!DLoader_Exec_Cmd(cmdbuf)) {
419                 cprintf("%d Unrecognized or unsupported command.\n", ERROR + CMD_NOT_SUPPORTED);
420         }       
421
422         unbuffer_output();
423
424         /* Run any after-each-command routines registered by modules */
425         PerformSessionHooks(EVT_CMD);
426 }
427
428
429 /*
430  * This loop performs all asynchronous functions.
431  */
432 void do_async_loop(void) {
433         PerformSessionHooks(EVT_ASYNC);
434 }
435