a3cd5a6ee2129f9ea1d64022ff42ce93c83b0af6
[citadel.git] / webcit / sysdep.c
1 /*
2  * WebCit "system dependent" code.
3  *
4  * Copyright (c) 1996-2012 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  * 
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * 
17  * 
18  * 
19  */
20
21 #include "sysdep.h"
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <stdio.h>
25 #include <fcntl.h>
26 #include <ctype.h>
27 #include <signal.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/wait.h>
31 #include <sys/socket.h>
32 #include <syslog.h>
33 #include <sys/syslog.h>
34
35 #if TIME_WITH_SYS_TIME
36 # include <sys/time.h>
37 # include <time.h>
38 #else
39 # if HAVE_SYS_TIME_H
40 #  include <sys/time.h>
41 # else
42 #  include <time.h>
43 # endif
44 #endif
45
46 #include <limits.h>
47 #include <sys/resource.h>
48 #include <netinet/in.h>
49 #include <netinet/tcp.h>
50 #include <arpa/inet.h>
51 #include <netdb.h>
52 #include <sys/un.h>
53 #include <string.h>
54 #include <pwd.h>
55 #include <errno.h>
56 #include <stdarg.h>
57 #include <grp.h>
58 #ifdef HAVE_PTHREAD_H
59 #include <pthread.h>
60 #endif
61 #include "webcit.h"
62 #include "sysdep.h"
63
64 #ifdef HAVE_SYS_SELECT_H
65 #include <sys/select.h>
66 #endif
67
68 #ifndef HAVE_SNPRINTF
69 #include "snprintf.h"
70 #endif
71 #include "webserver.h"
72 #include "modules_init.h"
73 #if HAVE_BACKTRACE
74 #include <execinfo.h>
75 #endif
76
77 pthread_mutex_t Critters[MAX_SEMAPHORES];       /* Things needing locking */
78 pthread_key_t MyConKey;                         /* TSD key for MyContext() */
79 pthread_key_t MyReq;                            /* TSD key for MyReq() */
80 int msock;                      /* master listening socket */
81 int time_to_die = 0;            /* Nonzero if server is shutting down */
82
83 extern void *context_loop(ParsedHttpHdrs *Hdr);
84 extern void *housekeeping_loop(void);
85 extern void do_housekeeping(void);
86
87 char ctdl_key_dir[PATH_MAX]=SSL_DIR;
88 char file_crpt_file_key[PATH_MAX]="";
89 char file_crpt_file_csr[PATH_MAX]="";
90 char file_crpt_file_cer[PATH_MAX]="";
91
92 const char editor_absolut_dir[PATH_MAX]=EDITORDIR;      /* nailed to what configure gives us. */
93 char static_dir[PATH_MAX];              /* calculated on startup */
94 char static_local_dir[PATH_MAX];                /* calculated on startup */
95 char static_icon_dir[PATH_MAX];          /* where should we find our mime icons? */
96 char  *static_dirs[]={                          /* needs same sort order as the web mapping */
97         (char*)static_dir,                      /* our templates on disk */
98         (char*)static_local_dir,                /* user provided templates disk */
99         (char*)editor_absolut_dir,              /* the editor on disk */
100         (char*)static_icon_dir                  /* our icons... */
101 };
102
103 int ExitPipe[2];
104
105 void InitialiseSemaphores(void)
106 {
107         int i;
108
109         /* Set up a bunch of semaphores to be used for critical sections */
110         for (i=0; i<MAX_SEMAPHORES; ++i) {
111                 pthread_mutex_init(&Critters[i], NULL);
112         }
113
114         if (pipe(ExitPipe))
115         {
116                 syslog(2, "Failed to open exit pipe: %d [%s]\n", 
117                        errno, 
118                        strerror(errno));
119                 
120                 exit(-1);
121         }
122 }
123
124 /*
125  * Obtain a semaphore lock to begin a critical section.
126  */
127 void begin_critical_section(int which_one)
128 {
129         pthread_mutex_lock(&Critters[which_one]);
130 }
131
132 /*
133  * Release a semaphore lock to end a critical section.
134  */
135 void end_critical_section(int which_one)
136 {
137         pthread_mutex_unlock(&Critters[which_one]);
138 }
139
140
141 void ShutDownWebcit(void)
142 {
143         free_zone_directory ();
144         icaltimezone_release_zone_tab ();
145         icalmemory_free_ring ();
146         ShutDownLibCitadel ();
147         shutdown_modules ();
148 #ifdef HAVE_OPENSSL
149         if (is_https) {
150                 shutdown_ssl();
151         }
152 #endif
153 }
154
155 /*
156  * Entry point for worker threads
157  */
158 void worker_entry(void)
159 {
160         int ssock;
161         int i = 0;
162         int fail_this_transaction = 0;
163         ParsedHttpHdrs Hdr;
164
165         memset(&Hdr, 0, sizeof(ParsedHttpHdrs));
166         Hdr.HR.eReqType = eGET;
167         http_new_modules(&Hdr); 
168
169         do {
170                 /* Each worker thread blocks on accept() while waiting for something to do. */
171                 fail_this_transaction = 0;
172                 ssock = -1; 
173                 errno = EAGAIN;
174                 do {
175                         fd_set wset;
176                         --num_threads_executing;
177                         FD_ZERO(&wset);
178                         FD_SET(msock, &wset);
179                         FD_SET(ExitPipe[1], &wset);
180
181                         select(msock + 1, NULL, &wset, NULL, NULL);
182                         if (time_to_die)
183                                 break;
184
185                         ssock = accept(msock, NULL, 0);
186                         ++num_threads_executing;
187                         if (ssock < 0) fail_this_transaction = 1;
188                 } while ((msock > 0) && (ssock < 0)  && (time_to_die == 0));
189
190                 if ((msock == -1)||(time_to_die))
191                 {/* ok, we're going down. */
192                         int shutdown = 0;
193
194                         /* The first thread to get here will have to do the cleanup.
195                          * Make sure it's really just one.
196                          */
197                         begin_critical_section(S_SHUTDOWN);
198                         if (msock == -1)
199                         {
200                                 msock = -2;
201                                 shutdown = 1;
202                         }
203                         end_critical_section(S_SHUTDOWN);
204                         if (shutdown == 1)
205                         {/* we're the one to cleanup the mess. */
206                                 http_destroy_modules(&Hdr);
207                                 syslog(2, "I'm master shutdown: tagging sessions to be killed.\n");
208                                 shutdown_sessions();
209                                 syslog(2, "master shutdown: waiting for others\n");
210                                 sleeeeeeeeeep(1); /* wait so some others might finish... */
211                                 syslog(2, "master shutdown: cleaning up sessions\n");
212                                 do_housekeeping();
213                                 syslog(2, "master shutdown: cleaning up libical\n");
214
215                                 ShutDownWebcit();
216
217                                 syslog(2, "master shutdown exiting.\n");                                
218                                 exit(0);
219                         }
220                         break;
221                 }
222                 if (ssock < 0 ) continue;
223
224                 check_thread_pool_size();
225
226                 /* Now do something. */
227                 if (msock < 0) {
228                         if (ssock > 0) close (ssock);
229                         syslog(2, "in between.");
230                         pthread_exit(NULL);
231                 } else {
232                         /* Got it? do some real work! */
233                         /* Set the SO_REUSEADDR socket option */
234                         i = 1;
235                         setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
236
237                         /* If we are an HTTPS server, go crypto now. */
238 #ifdef HAVE_OPENSSL
239                         if (is_https) {
240                                 if (starttls(ssock) != 0) {
241                                         fail_this_transaction = 1;
242                                         close(ssock);
243                                 }
244                         }
245                         else 
246 #endif
247                         {
248                                 int fdflags; 
249                                 fdflags = fcntl(ssock, F_GETFL);
250                                 if (fdflags < 0)
251                                         syslog(1, "unable to get server socket flags! %s \n",
252                                                 strerror(errno));
253                                 fdflags = fdflags | O_NONBLOCK;
254                                 if (fcntl(ssock, F_SETFL, fdflags) < 0)
255                                         syslog(1, "unable to set server socket nonblocking flags! %s \n",
256                                                 strerror(errno));
257                         }
258
259                         if (fail_this_transaction == 0) {
260                                 Hdr.http_sock = ssock;
261
262                                 /* Perform an HTTP transaction... */
263                                 context_loop(&Hdr);
264
265                                 /* Shut down SSL/TLS if required... */
266 #ifdef HAVE_OPENSSL
267                                 if (is_https) {
268                                         endtls();
269                                 }
270 #endif
271
272                                 /* ...and close the socket. */
273                                 if (Hdr.http_sock > 0) {
274                                         lingering_close(ssock);
275                                 }
276                                 http_detach_modules(&Hdr);
277
278                         }
279
280                 }
281
282         } while (!time_to_die);
283
284         http_destroy_modules(&Hdr);
285         syslog(1, "Thread exiting.\n");
286         pthread_exit(NULL);
287 }
288
289
290 /*
291  * Shut us down the regular way.
292  * signum is the signal we want to forward
293  */
294 pid_t current_child;
295 void graceful_shutdown_watcher(int signum) {
296         syslog(1, "Watcher thread exiting.\n");
297         write(ExitPipe[0], HKEY("                              "));
298         kill(current_child, signum);
299         if (signum != SIGHUP)
300                 exit(0);
301 }
302
303
304 /*
305  * Shut us down the regular way.
306  * signum is the signal we want to forward
307  */
308 pid_t current_child;
309 void graceful_shutdown(int signum) {
310         FILE *FD;
311         int fd;
312
313         syslog(1, "WebCit is being shut down on signal %d.\n", signum);
314         fd = msock;
315         msock = -1;
316         time_to_die = 1;
317         FD=fdopen(fd, "a+");
318         fflush (FD);
319         fclose (FD);
320         close(fd);
321         write(ExitPipe[0], HKEY("                              "));
322 }
323
324
325 /*
326  * Start running as a daemon.
327  */
328 void start_daemon(char *pid_file) 
329 {
330         int status = 0;
331         pid_t child = 0;
332         FILE *fp;
333         int do_restart = 0;
334
335         current_child = 0;
336
337         /* Close stdin/stdout/stderr and replace them with /dev/null.
338          * We don't just call close() because we don't want these fd's
339          * to be reused for other files.
340          */
341         chdir("/");
342
343         signal(SIGHUP, SIG_IGN);
344         signal(SIGINT, SIG_IGN);
345         signal(SIGQUIT, SIG_IGN);
346
347         child = fork();
348         if (child != 0) {
349                 exit(0);
350         }
351
352         setsid();
353         umask(0);
354         freopen("/dev/null", "r", stdin);
355         freopen("/dev/null", "w", stdout);
356         freopen("/dev/null", "w", stderr);
357         signal(SIGTERM, graceful_shutdown_watcher);
358         signal(SIGHUP, graceful_shutdown_watcher);
359
360         do {
361                 current_child = fork();
362
363         
364                 if (current_child < 0) {
365                         perror("fork");
366                         ShutDownLibCitadel ();
367                         exit(errno);
368                 }
369         
370                 else if (current_child == 0) {  /* child process */
371                         signal(SIGHUP, graceful_shutdown);
372
373                         return; /* continue starting webcit. */
374                 }
375                 else { /* watcher process */
376                         if (pid_file) {
377                                 fp = fopen(pid_file, "w");
378                                 if (fp != NULL) {
379                                         fprintf(fp, "%d\n", getpid());
380                                         fclose(fp);
381                                 }
382                         }
383                         waitpid(current_child, &status, 0);
384                 }
385
386                 do_restart = 0;
387
388                 /* Did the main process exit with an actual exit code? */
389                 if (WIFEXITED(status)) {
390
391                         /* Exit code 0 means the watcher should exit */
392                         if (WEXITSTATUS(status) == 0) {
393                                 do_restart = 0;
394                         }
395
396                         /* Exit code 101-109 means the watcher should exit */
397                         else if ( (WEXITSTATUS(status) >= 101) && (WEXITSTATUS(status) <= 109) ) {
398                                 do_restart = 0;
399                         }
400
401                         /* Any other exit code means we should restart. */
402                         else {
403                                 do_restart = 1;
404                         }
405                 }
406
407                 /* Any other type of termination (signals, etc.) should also restart. */
408                 else {
409                         do_restart = 1;
410                 }
411
412         } while (do_restart);
413
414         if (pid_file) {
415                 unlink(pid_file);
416         }
417         ShutDownLibCitadel ();
418         exit(WEXITSTATUS(status));
419 }
420
421
422 /*
423  * Spawn an additional worker thread into the pool.
424  */
425 void spawn_another_worker_thread()
426 {
427         pthread_t SessThread;   /* Thread descriptor */
428         pthread_attr_t attr;    /* Thread attributes */
429         int ret;
430
431         ++num_threads_existing;
432         ++num_threads_executing;
433
434         /* set attributes for the new thread */
435         pthread_attr_init(&attr);
436         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
437
438         /*
439          * Our per-thread stacks need to be bigger than the default size,
440          * otherwise the MIME parser crashes on FreeBSD.
441          */
442         if ((ret = pthread_attr_setstacksize(&attr, 1024 * 1024))) {
443                 syslog(1, "pthread_attr_setstacksize: %s\n", strerror(ret));
444                 pthread_attr_destroy(&attr);
445         }
446
447         /* now create the thread */
448         if (pthread_create(&SessThread, &attr, (void *(*)(void *)) worker_entry, NULL) != 0) {
449                 syslog(1, "Can't create thread: %s\n", strerror(errno));
450         }
451
452         /* free up the attributes */
453         pthread_attr_destroy(&attr);
454 }
455
456
457 void
458 webcit_calc_dirs_n_files(int relh, const char *basedir, int home, char *webcitdir, char *relhome)
459 {
460         char dirbuffer[PATH_MAX]="";
461         /* calculate all our path on a central place */
462     /* where to keep our config */
463         
464 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
465         snprintf(SUBDIR,sizeof SUBDIR,  "%s%s%s%s%s%s%s", \
466                          (home&!relh)?webcitdir:basedir, \
467              ((basedir!=webcitdir)&(home&!relh))?basedir:"/", \
468              ((basedir!=webcitdir)&(home&!relh))?"/":"", \
469                          relhome, \
470              (relhome[0]!='\0')?"/":"",\
471                          dirbuffer,\
472                          (dirbuffer[0]!='\0')?"/":"");
473         basedir=RUNDIR;
474         COMPUTE_DIRECTORY(socket_dir);
475         basedir=WWWDIR "/static";
476         COMPUTE_DIRECTORY(static_dir);
477         basedir=WWWDIR "/static/icons";
478         COMPUTE_DIRECTORY(static_icon_dir);
479         basedir=WWWDIR "/static.local";
480         COMPUTE_DIRECTORY(static_local_dir);
481         StripSlashes(static_dir, 1);
482         StripSlashes(static_icon_dir, 1);
483         StripSlashes(static_local_dir, 1);
484
485         snprintf(file_crpt_file_key,
486                  sizeof file_crpt_file_key, 
487                  "%s/citadel.key",
488                  ctdl_key_dir);
489         snprintf(file_crpt_file_csr,
490                  sizeof file_crpt_file_csr, 
491                  "%s/citadel.csr",
492                  ctdl_key_dir);
493         snprintf(file_crpt_file_cer,
494                  sizeof file_crpt_file_cer, 
495                  "%s/citadel.cer",
496                  ctdl_key_dir);
497
498         /* we should go somewhere we can leave our coredump, if enabled... */
499         syslog(9, "Changing directory to %s\n", socket_dir);
500         if (chdir(webcitdir) != 0) {
501                 perror("chdir");
502         }
503 }
504
505 void drop_root(uid_t UID)
506 {
507         struct passwd pw, *pwp = NULL;
508
509         /*
510          * Now that we've bound the sockets, change to the Citadel user id and its
511          * corresponding group ids
512          */
513         if (UID != -1) {
514                 
515 #ifdef HAVE_GETPWUID_R
516 #ifdef SOLARIS_GETPWUID
517                 pwp = getpwuid_r(UID, &pw, pwbuf, sizeof(pwbuf));
518 #else /* SOLARIS_GETPWUID */
519                 getpwuid_r(UID, &pw, pwbuf, sizeof(pwbuf), &pwp);
520 #endif /* SOLARIS_GETPWUID */
521 #else /* HAVE_GETPWUID_R */
522                 pwp = NULL;
523 #endif /* HAVE_GETPWUID_R */
524
525                 if (pwp == NULL)
526                         syslog(LOG_CRIT, "WARNING: getpwuid(%d): %s\n"
527                                 "Group IDs will be incorrect.\n", UID,
528                                 strerror(errno));
529                 else {
530                         initgroups(pw.pw_name, pw.pw_gid);
531                         if (setgid(pw.pw_gid))
532                                 syslog(LOG_CRIT, "setgid(%ld): %s\n", (long)pw.pw_gid,
533                                         strerror(errno));
534                 }
535                 syslog(LOG_INFO, "Changing uid to %ld\n", (long)UID);
536                 if (setuid(UID) != 0) {
537                         syslog(LOG_CRIT, "setuid() failed: %s\n", strerror(errno));
538                 }
539 #if defined (HAVE_SYS_PRCTL_H) && defined (PR_SET_DUMPABLE)
540                 prctl(PR_SET_DUMPABLE, 1);
541 #endif
542         }
543 }
544
545
546 /*
547  * print the actual stack frame.
548  */
549 void wc_backtrace(void)
550 {
551 #ifdef HAVE_BACKTRACE
552         void *stack_frames[50];
553         size_t size, i;
554         char **strings;
555
556
557         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
558         strings = backtrace_symbols(stack_frames, size);
559         for (i = 0; i < size; i++) {
560                 if (strings != NULL)
561                         syslog(1, "%s\n", strings[i]);
562                 else
563                         syslog(1, "%p\n", stack_frames[i]);
564         }
565         free(strings);
566 #endif
567 }