4 * This contains a simple multithreaded TCP server manager. It sits around
5 * waiting on the specified port for incoming HTTP connections. When a
6 * connection is established, it calls context_loop() from context_loop.c.
8 * Copyright (c) 1996-2010 by the citadel.org developers.
9 * This program is released under the terms of the GNU General Public License v3.
14 #include "webserver.h"
19 #include "modules_init.h"
21 int vsnprintf(char *buf, size_t max, const char *fmt, va_list argp);
24 int verbosity = 9; /* Logging level */
25 int msock; /* master listening socket */
26 int is_https = 0; /* Nonzero if I am an HTTPS service */
27 int follow_xff = 0; /* Follow X-Forwarded-For: header */
28 int home_specified = 0; /* did the user specify a homedir? */
29 int time_to_die = 0; /* Nonzero if server is shutting down */
31 extern void *context_loop(ParsedHttpHdrs *Hdr);
32 extern void *housekeeping_loop(void);
33 extern pthread_mutex_t SessionListMutex;
34 extern pthread_key_t MyConKey;
36 extern int ig_tcp_server(char *ip_addr, int port_number, int queue_len);
37 extern int ig_uds_server(char *sockpath, int queue_len);
39 extern void drop_root(uid_t UID);
41 char ctdl_key_dir[PATH_MAX]=SSL_DIR;
42 char file_crpt_file_key[PATH_MAX]="";
43 char file_crpt_file_csr[PATH_MAX]="";
44 char file_crpt_file_cer[PATH_MAX]="";
46 char socket_dir[PATH_MAX]; /* where to talk to our citadel server */
47 const char editor_absolut_dir[PATH_MAX]=EDITORDIR; /* nailed to what configure gives us. */
48 char static_dir[PATH_MAX]; /* calculated on startup */
49 char static_local_dir[PATH_MAX]; /* calculated on startup */
50 char static_icon_dir[PATH_MAX]; /* where should we find our mime icons? */
51 char *static_dirs[]={ /* needs same sort order as the web mapping */
52 (char*)static_dir, /* our templates on disk */
53 (char*)static_local_dir, /* user provided templates disk */
54 (char*)editor_absolut_dir, /* the editor on disk */
55 (char*)static_icon_dir /* our icons... */
59 * Subdirectories from which the client may request static content
61 * (If you add more, remember to increment 'ndirs' below)
63 char *static_content_dirs[] = {
64 "static", /* static templates */
65 "static.local", /* site local static templates */
66 "tiny_mce" /* rich text editor */
72 char *server_cookie = NULL; /* our Cookie connection to the client */
73 int http_port = PORT_NUM; /* Port to listen on */
74 char *ctdlhost = DEFAULT_HOST; /* our name */
75 char *ctdlport = DEFAULT_PORT; /* our Port */
76 int setup_wizard = 0; /* should we run the setup wizard? \todo */
77 char wizard_filename[PATH_MAX]; /* where's the setup wizard? */
78 int running_as_daemon = 0; /* should we deamonize on startup? */
83 * Shut us down the regular way.
84 * signum is the signal we want to forward
87 void graceful_shutdown_watcher(int signum) {
88 lprintf (1, "bye; shutting down watcher.");
89 kill(current_child, signum);
98 * Shut us down the regular way.
99 * signum is the signal we want to forward
102 void graceful_shutdown(int signum) {
106 lprintf (1, "WebCit is being shut down on signal %d.\n", signum);
118 * Start running as a daemon.
120 void start_daemon(char *pid_file)
131 /* Close stdin/stdout/stderr and replace them with /dev/null.
132 * We don't just call close() because we don't want these fd's
133 * to be reused for other files.
137 signal(SIGHUP, SIG_IGN);
138 signal(SIGINT, SIG_IGN);
139 signal(SIGQUIT, SIG_IGN);
148 rvfp = freopen("/dev/null", "r", stdin);
149 rvfp = freopen("/dev/null", "w", stdout);
150 rvfp = freopen("/dev/null", "w", stderr);
151 signal(SIGTERM, graceful_shutdown_watcher);
152 signal(SIGHUP, graceful_shutdown_watcher);
155 current_child = fork();
158 if (current_child < 0) {
160 ShutDownLibCitadel ();
164 else if (current_child == 0) { /* child process */
165 signal(SIGHUP, graceful_shutdown);
167 return; /* continue starting webcit. */
169 else { /* watcher process */
171 fp = fopen(pid_file, "w");
173 fprintf(fp, "%d\n", getpid());
177 waitpid(current_child, &status, 0);
182 /* Did the main process exit with an actual exit code? */
183 if (WIFEXITED(status)) {
185 /* Exit code 0 means the watcher should exit */
186 if (WEXITSTATUS(status) == 0) {
190 /* Exit code 101-109 means the watcher should exit */
191 else if ( (WEXITSTATUS(status) >= 101) && (WEXITSTATUS(status) <= 109) ) {
195 /* Any other exit code means we should restart. */
201 /* Any other type of termination (signals, etc.) should also restart. */
206 } while (do_restart);
211 ShutDownLibCitadel ();
212 exit(WEXITSTATUS(status));
216 * Spawn an additional worker thread into the pool.
218 void spawn_another_worker_thread()
220 pthread_t SessThread; /* Thread descriptor */
221 pthread_attr_t attr; /* Thread attributes */
224 lprintf(3, "Creating a new thread. Pool size is now %d\n", ++num_threads);
226 /* set attributes for the new thread */
227 pthread_attr_init(&attr);
228 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
231 * Our per-thread stacks need to be bigger than the default size,
232 * otherwise the MIME parser crashes on FreeBSD.
234 if ((ret = pthread_attr_setstacksize(&attr, 1024 * 1024))) {
235 lprintf(1, "pthread_attr_setstacksize: %s\n",
237 pthread_attr_destroy(&attr);
240 /* now create the thread */
241 if (pthread_create(&SessThread, &attr,
242 (void *(*)(void *)) worker_entry, NULL)
244 lprintf(1, "Can't create thread: %s\n", strerror(errno));
247 /* free up the attributes */
248 pthread_attr_destroy(&attr);
251 /* #define DBG_PRINNT_HOOKS_AT_START */
252 #ifdef DBG_PRINNT_HOOKS_AT_START
253 const char foobuf[32];
254 const char *nix(void *vptr) {snprintf(foobuf, 32, "%0x", (long) vptr); return foobuf;}
256 extern int dbg_analyze_msg;
257 extern int dbg_bactrace_template_errors;
258 extern int DumpTemplateI18NStrings;
259 extern StrBuf *I18nDump;
260 void InitTemplateCache(void);
261 extern int LoadTemplates;
263 extern HashList *HandlerHash;
268 webcit_calc_dirs_n_files(int relh, const char *basedir, int home, char *webcitdir, char *relhome)
270 char dirbuffer[PATH_MAX]="";
271 /* calculate all our path on a central place */
272 /* where to keep our config */
274 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
275 snprintf(SUBDIR,sizeof SUBDIR, "%s%s%s%s%s%s%s", \
276 (home&!relh)?webcitdir:basedir, \
277 ((basedir!=webcitdir)&(home&!relh))?basedir:"/", \
278 ((basedir!=webcitdir)&(home&!relh))?"/":"", \
280 (relhome[0]!='\0')?"/":"",\
282 (dirbuffer[0]!='\0')?"/":"");
284 COMPUTE_DIRECTORY(socket_dir);
285 basedir=WWWDIR "/static";
286 COMPUTE_DIRECTORY(static_dir);
287 basedir=WWWDIR "/static/icons";
288 COMPUTE_DIRECTORY(static_icon_dir);
289 basedir=WWWDIR "/static.local";
290 COMPUTE_DIRECTORY(static_local_dir);
291 StripSlashes(static_dir, 1);
292 StripSlashes(static_icon_dir, 1);
293 StripSlashes(static_local_dir, 1);
295 snprintf(file_crpt_file_key,
296 sizeof file_crpt_file_key,
299 snprintf(file_crpt_file_csr,
300 sizeof file_crpt_file_csr,
303 snprintf(file_crpt_file_cer,
304 sizeof file_crpt_file_cer,
308 /* we should go somewhere we can leave our coredump, if enabled... */
309 lprintf(9, "Changing directory to %s\n", socket_dir);
310 if (chdir(webcitdir) != 0) {
315 * Here's where it all begins.
317 int main(int argc, char **argv)
320 size_t basesize = 2; /* how big should strbufs be on creation? */
321 pthread_t SessThread; /* Thread descriptor */
322 pthread_attr_t attr; /* Thread attributes */
323 int a, i; /* General-purpose variables */
324 char tracefile[PATH_MAX];
325 char ip_addr[256]="0.0.0.0";
328 int home_specified=0;
329 char relhome[PATH_MAX]="";
330 char webcitdir[PATH_MAX] = DATADIR;
331 char *pidfile = NULL;
333 const char *basedir = NULL;
334 char uds_listen_path[PATH_MAX]; /* listen on a unix domain socket? */
335 const char *I18nDumpFile = NULL;
339 WildFireInitBacktrace(argv[0], 2);
343 #ifdef DBG_PRINNT_HOOKS_AT_START
344 /* dbg_PrintHash(HandlerHash, nix, NULL);*/
347 /* Ensure that we are linked to the correct version of libcitadel */
348 if (libcitadel_version_number() < LIBCITADEL_VERSION_NUMBER) {
349 fprintf(stderr, " You are running libcitadel version %d.%02d\n",
350 (libcitadel_version_number() / 100), (libcitadel_version_number() % 100));
351 fprintf(stderr, "WebCit was compiled against version %d.%02d\n",
352 (LIBCITADEL_VERSION_NUMBER / 100), (LIBCITADEL_VERSION_NUMBER % 100));
356 strcpy(uds_listen_path, "");
358 /* Parse command line */
360 while ((a = getopt(argc, argv, "u:h:i:p:t:T:B:x:dD:G:cfsS:Z")) != EOF)
362 while ((a = getopt(argc, argv, "u:h:i:p:t:T:B:x:dD:G:cfZ")) != EOF)
369 hdir = strdup(optarg);
372 safestrncpy(webcitdir, hdir, sizeof webcitdir);
375 safestrncpy(relhome, relhome, sizeof relhome);
377 /* free(hdir); TODO: SHOULD WE DO THIS? */
382 running_as_daemon = 1;
385 pidfile = strdup(optarg);
386 running_as_daemon = 1;
388 case 'B': /* Basesize */
389 basesize = atoi(optarg);
391 StartLibCitadel(basesize);
394 safestrncpy(ip_addr, optarg, sizeof ip_addr);
397 http_port = atoi(optarg);
398 if (http_port == 0) {
399 safestrncpy(uds_listen_path, optarg, sizeof uds_listen_path);
403 safestrncpy(tracefile, optarg, sizeof tracefile);
404 rvfp = freopen(tracefile, "w", stdout);
405 rvfp = freopen(tracefile, "w", stderr);
406 rvfp = freopen(tracefile, "r", stdin);
409 LoadTemplates = atoi(optarg);
410 dbg_analyze_msg = (LoadTemplates && (1<<1)) != 0;
411 dbg_bactrace_template_errors = (LoadTemplates && (1<<2)) != 0;
417 verbosity = atoi(optarg);
423 server_cookie = malloc(256);
424 if (server_cookie != NULL) {
425 safestrncpy(server_cookie,
426 "Set-cookie: wcserver=",
429 (&server_cookie[strlen(server_cookie)],
431 lprintf(2, "gethostname: %s\n",
443 ssl_cipher_list = strdup(optarg);
447 DumpTemplateI18NStrings = 1;
448 I18nDump = NewStrBufPlain(HKEY("int templatestrings(void)\n{\n"));
449 I18nDumpFile = optarg;
452 fprintf(stderr, "usage: webcit "
453 "[-i ip_addr] [-p http_port] "
454 "[-t tracefile] [-c] [-f] "
455 "[-T Templatedebuglevel] "
456 "[-d] [-Z] [-G i18ndumpfile] "
458 "[-s] [-S cipher_suites]"
460 "[remotehost [remoteport]]\n");
465 ctdlhost = argv[optind];
467 ctdlport = argv[optind];
470 /* daemonize, if we were asked to */
471 if (!DumpTemplateI18NStrings && running_as_daemon) {
472 start_daemon(pidfile);
475 signal(SIGHUP, graceful_shutdown);
478 webcit_calc_dirs_n_files(relh, basedir, home, webcitdir, relhome);
479 LoadIconDir(static_icon_dir);
481 /* Tell 'em who's in da house */
482 lprintf(1, PACKAGE_STRING "\n");
483 lprintf(1, "Copyright (C) 1996-2010 by the Citadel development team.\n"
484 "This software is distributed under the terms of the "
485 "GNU General Public License.\n\n"
489 /* initialize the International Bright Young Thing */
491 initialise_modules();
492 initialize_viewdefs();
496 if (DumpTemplateI18NStrings) {
498 StrBufAppendBufPlain(I18nDump, HKEY("}\n"), 0);
499 if (StrLength(I18nDump) < 50) {
500 lprintf(1, "********************************************************************************\n");
501 lprintf(1, "* No strings found in templates! are you shure they're there? *\n");
502 lprintf(1, "********************************************************************************\n");
505 fd = fopen(I18nDumpFile, "w");
507 lprintf(1, "********************************************************************************\n");
508 lprintf(1, "* unable to open I18N dumpfile [%s] *\n", I18nDumpFile);
509 lprintf(1, "********************************************************************************\n");
512 rv = fwrite(ChrPtr(I18nDump), 1, StrLength(I18nDump), fd);
518 /* Tell libical to return an error instead of aborting if it sees badly formed iCalendar data. */
519 icalerror_errors_are_fatal = 0;
521 /* Use our own prefix on tzid's generated from system tzdata */
522 icaltimezone_set_tzid_prefix("/citadel.org/");
525 * Set up a place to put thread-specific data.
526 * We only need a single pointer per thread - it points to the
527 * wcsession struct to which the thread is currently bound.
529 if (pthread_key_create(&MyConKey, NULL) != 0) {
530 lprintf(1, "Can't create TSD key: %s\n", strerror(errno));
532 InitialiseSemaphores ();
535 * Set up a place to put thread-specific SSL data.
536 * We don't stick this in the wcsession struct because SSL starts
537 * up before the session is bound, and it gets torn down between
541 if (pthread_key_create(&ThreadSSL, NULL) != 0) {
542 lprintf(1, "Can't create TSD key: %s\n", strerror(errno));
547 * Bind the server to our favorite port.
548 * There is no need to check for errors, because ig_tcp_server()
549 * exits if it doesn't succeed.
552 if (!IsEmptyStr(uds_listen_path)) {
553 lprintf(2, "Attempting to create listener socket at %s...\n", uds_listen_path);
554 msock = ig_uds_server(uds_listen_path, LISTEN_QUEUE_LENGTH);
557 lprintf(2, "Attempting to bind to port %d...\n", http_port);
558 msock = ig_tcp_server(ip_addr, http_port, LISTEN_QUEUE_LENGTH);
566 lprintf(2, "Listening on socket %d\n", msock);
567 signal(SIGPIPE, SIG_IGN);
569 pthread_mutex_init(&SessionListMutex, NULL);
572 * Start up the housekeeping thread
574 pthread_attr_init(&attr);
575 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
576 pthread_create(&SessThread, &attr,
577 (void *(*)(void *)) housekeeping_loop, NULL);
581 * If this is an HTTPS server, fire up SSL
590 /* Start a few initial worker threads */
591 for (i = 0; i < (MIN_WORKER_THREADS); ++i) {
592 spawn_another_worker_thread();
595 /* now the original thread becomes another worker */
597 ShutDownLibCitadel ();
602 void ShutDownWebcit(void)
604 free_zone_directory ();
605 icaltimezone_release_zone_tab ();
606 icalmemory_free_ring ();
607 ShutDownLibCitadel ();
617 * Entry point for worker threads
619 void worker_entry(void)
623 int fail_this_transaction = 0;
626 fd_set readset, tempset;
629 memset(&Hdr, 0, sizeof(ParsedHttpHdrs));
630 Hdr.HR.eReqType = eGET;
631 http_new_modules(&Hdr);
635 FD_SET(msock, &readset);
638 /* Only one thread can accept at a time */
639 fail_this_transaction = 0;
643 ret = -1; /* just one at once should select... */
644 begin_critical_section(S_SELECT);
647 if (msock > 0) FD_SET(msock, &tempset);
650 if (msock > 0) ret = select(msock+1, &tempset, NULL, NULL, &tv);
651 end_critical_section(S_SELECT);
652 if ((ret < 0) && (errno != EINTR) && (errno != EAGAIN))
653 {/* EINTR and EAGAIN are thrown but not of interest. */
654 lprintf(2, "accept() failed:%d %s\n",
655 errno, strerror(errno));
657 else if ((ret > 0) && (msock > 0) && FD_ISSET(msock, &tempset))
658 {/* Successfully selected, and still not shutting down? Accept! */
659 ssock = accept(msock, NULL, 0);
662 } while ((msock > 0) && (ssock < 0) && (time_to_die == 0));
664 if ((msock == -1)||(time_to_die))
665 {/* ok, we're going down. */
668 /* the first to come here will have to do the cleanup.
669 * make shure its realy just one.
671 begin_critical_section(S_SHUTDOWN);
677 end_critical_section(S_SHUTDOWN);
679 {/* we're the one to cleanup the mess. */
680 http_destroy_modules(&Hdr);
681 lprintf(2, "I'm master shutdown: tagging sessions to be killed.\n");
683 lprintf(2, "master shutdown: waiting for others\n");
684 sleeeeeeeeeep(1); /* wait so some others might finish... */
685 lprintf(2, "master shutdown: cleaning up sessions\n");
687 lprintf(2, "master shutdown: cleaning up libical\n");
691 lprintf(2, "master shutdown exiting!.\n");
696 if (ssock < 0 ) continue;
699 if (ssock > 0) close (ssock);
700 lprintf(2, "inbetween.");
702 } else { /* Got it? do some real work! */
703 /* Set the SO_REUSEADDR socket option */
705 setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR,
708 /* If we are an HTTPS server, go crypto now. */
711 if (starttls(ssock) != 0) {
712 fail_this_transaction = 1;
720 fdflags = fcntl(ssock, F_GETFL);
722 lprintf(1, "unable to get server socket flags! %s \n",
724 fdflags = fdflags | O_NONBLOCK;
725 if (fcntl(ssock, F_SETFL, fdflags) < 0)
726 lprintf(1, "unable to set server socket nonblocking flags! %s \n",
730 if (fail_this_transaction == 0) {
731 Hdr.http_sock = ssock;
733 /* Perform an HTTP transaction... */
736 /* Shut down SSL/TLS if required... */
743 /* ...and close the socket. */
744 if (Hdr.http_sock > 0)
745 lingering_close(ssock);
746 http_detach_modules(&Hdr);
752 } while (!time_to_die);
754 http_destroy_modules(&Hdr);
755 lprintf (1, "bye\n");
761 * logs to stderr if loglevel is lower than the verbosity set at startup
763 * loglevel level of the message
764 * format the printf like format string
765 * ... the strings to put into format
767 int lprintf(int loglevel, const char *format, ...)
771 if (loglevel <= verbosity) {
772 va_start(ap, format);
773 vfprintf(stderr, format, ap);
782 * print the actual stack frame.
784 void wc_backtrace(void)
786 #ifdef HAVE_BACKTRACE
787 void *stack_frames[50];
792 size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
793 strings = backtrace_symbols(stack_frames, size);
794 for (i = 0; i < size; i++) {
796 lprintf(1, "%s\n", strings[i]);
798 lprintf(1, "%p\n", stack_frames[i]);