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