]> code.citadel.org Git - citadel.git/blob - webcit/webserver.c
a25ea478ccb707ef37a03b86c07c2a58e60f165b
[citadel.git] / webcit / webserver.c
1 /*
2  * $Id$
3  *
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.
7  *
8  */
9
10 /*
11  * Uncomment to dump an HTTP trace to stderr
12 #define HTTP_TRACING 1
13  */
14
15 #include <ctype.h>
16 #include <stdlib.h>
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 #include <stdio.h>
21 #ifdef HAVE_FCNTL_H
22 #include <fcntl.h>
23 #endif
24 #include <signal.h>
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 #include <sys/socket.h>
28 #ifdef HAVE_SYS_TIME_H
29 #include <sys/time.h>
30 #endif
31 #ifdef HAVE_LIMITS_H
32 #include <limits.h>
33 #endif
34 #include <netinet/in.h>
35 #include <arpa/inet.h>
36 #include <netdb.h>
37 #include <string.h>
38 #include <pwd.h>
39 #include <errno.h>
40 #include <stdarg.h>
41 #include <pthread.h>
42 #include <signal.h>
43 #include "webcit.h"
44 #include "webserver.h"
45
46 #ifndef HAVE_SNPRINTF
47 int vsnprintf(char *buf, size_t max, const char *fmt, va_list argp);
48 #endif
49
50 int verbosity = 9;              /* Logging level */
51 int msock;                      /* master listening socket */
52 int is_https = 0;               /* Nonzero if I am an HTTPS service */
53 extern void *context_loop(int);
54 extern void *housekeeping_loop(void);
55 extern pthread_mutex_t SessionListMutex;
56 extern pthread_key_t MyConKey;
57
58
59 char *server_cookie = NULL;
60
61
62 char *ctdlhost = DEFAULT_HOST;
63 char *ctdlport = DEFAULT_PORT;
64
65 /*
66  * This is a generic function to set up a master socket for listening on
67  * a TCP port.  The server shuts down if the bind fails.
68  */
69 int ig_tcp_server(char *ip_addr, int port_number, int queue_len)
70 {
71         struct sockaddr_in sin;
72         int s, i;
73
74         memset(&sin, 0, sizeof(sin));
75         sin.sin_family = AF_INET;
76         if (ip_addr == NULL) {
77                 sin.sin_addr.s_addr = INADDR_ANY;
78         } else {
79                 sin.sin_addr.s_addr = inet_addr(ip_addr);
80         }
81
82         if (sin.sin_addr.s_addr == INADDR_NONE) {
83                 sin.sin_addr.s_addr = INADDR_ANY;
84         }
85
86         if (port_number == 0) {
87                 lprintf(1, "Cannot start: no port number specified.\n");
88                 exit(1);
89         }
90         sin.sin_port = htons((u_short) port_number);
91
92         s = socket(PF_INET, SOCK_STREAM, (getprotobyname("tcp")->p_proto));
93         if (s < 0) {
94                 lprintf(1, "Can't create a socket: %s\n", strerror(errno));
95                 exit(errno);
96         }
97         /* Set some socket options that make sense. */
98         i = 1;
99         setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
100
101         if (bind(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
102                 lprintf(1, "Can't bind: %s\n", strerror(errno));
103                 exit(errno);
104         }
105         if (listen(s, queue_len) < 0) {
106                 lprintf(1, "Can't listen: %s\n", strerror(errno));
107                 exit(errno);
108         }
109         return (s);
110 }
111
112
113 /*
114  * Read data from the client socket.
115  * Return values are:
116  *      1       Requested number of bytes has been read.
117  *      0       Request timed out.
118  *      -1      Connection is broken, or other error.
119  */
120 int client_read_to(int sock, char *buf, int bytes, int timeout)
121 {
122         int len, rlen;
123         fd_set rfds;
124         struct timeval tv;
125         int retval;
126
127
128 #ifdef HAVE_OPENSSL
129         if (is_https) {
130                 return (client_read_ssl(buf, bytes, timeout));
131         }
132 #endif
133
134         len = 0;
135         while (len < bytes) {
136                 FD_ZERO(&rfds);
137                 FD_SET(sock, &rfds);
138                 tv.tv_sec = timeout;
139                 tv.tv_usec = 0;
140
141                 retval = select((sock) + 1, &rfds, NULL, NULL, &tv);
142                 if (FD_ISSET(sock, &rfds) == 0) {
143                         return (0);
144                 }
145
146                 rlen = read(sock, &buf[len], bytes - len);
147
148                 if (rlen < 1) {
149                         lprintf(2, "client_read() failed: %s\n",
150                                 strerror(errno));
151                         return (-1);
152                 }
153                 len = len + rlen;
154         }
155
156 #ifdef HTTP_TRACING
157         write(2, "\033[32m", 5);
158         write(2, buf, bytes);
159         write(2, "\033[30m", 5);
160 #endif
161         return (1);
162 }
163
164
165 ssize_t client_write(const void *buf, size_t count)
166 {
167
168         if (WC->burst != NULL) {
169                 WC->burst =
170                     realloc(WC->burst, (WC->burst_len + count + 2));
171                 memcpy(&WC->burst[WC->burst_len], buf, count);
172                 WC->burst_len += count;
173                 return (count);
174         }
175 #ifdef HAVE_OPENSSL
176         if (is_https) {
177                 client_write_ssl((char *) buf, count);
178                 return (count);
179         }
180 #endif
181 #ifdef HTTP_TRACING
182         write(2, "\033[34m", 5);
183         write(2, buf, count);
184         write(2, "\033[30m", 5);
185 #endif
186         return (write(WC->http_sock, buf, count));
187 }
188
189
190 void begin_burst(void)
191 {
192         if (WC->burst != NULL) {
193                 free(WC->burst);
194                 WC->burst = NULL;
195         }
196         WC->burst_len = 0;
197         WC->burst = malloc(SIZ);
198 }
199
200
201 /*
202  * compress_gzip() uses the same calling syntax as compress2(), but it
203  * creates a stream compatible with HTTP "Content-encoding: gzip"
204  */
205 #ifdef HAVE_ZLIB
206 #define DEF_MEM_LEVEL 8
207 #define OS_CODE 0x03    /* unix */
208 int ZEXPORT compress_gzip(Bytef * dest, uLongf * destLen,
209                           const Bytef * source, uLong sourceLen, int level)
210 {
211         const int gz_magic[2] = { 0x1f, 0x8b }; /* gzip magic header */
212
213         /* write gzip header */
214         sprintf((char *) dest, "%c%c%c%c%c%c%c%c%c%c",
215                 gz_magic[0], gz_magic[1], Z_DEFLATED,
216                 0 /*flags */ , 0, 0, 0, 0 /*time */ , 0 /*xflags */ ,
217                 OS_CODE);
218
219         /* normal deflate */
220         z_stream stream;
221         int err;
222         stream.next_in = (Bytef *) source;
223         stream.avail_in = (uInt) sourceLen;
224         stream.next_out = dest + 10L;   // after header
225         stream.avail_out = (uInt) * destLen;
226         if ((uLong) stream.avail_out != *destLen)
227                 return Z_BUF_ERROR;
228
229         stream.zalloc = (alloc_func) 0;
230         stream.zfree = (free_func) 0;
231         stream.opaque = (voidpf) 0;
232
233         err = deflateInit2(&stream, level, Z_DEFLATED, -MAX_WBITS,
234                            DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
235         if (err != Z_OK)
236                 return err;
237
238         err = deflate(&stream, Z_FINISH);
239         if (err != Z_STREAM_END) {
240                 deflateEnd(&stream);
241                 return err == Z_OK ? Z_BUF_ERROR : err;
242         }
243         *destLen = stream.total_out + 10L;
244
245         /* write CRC and Length */
246         uLong crc = crc32(0L, source, sourceLen);
247         int n;
248         for (n = 0; n < 4; ++n, ++*destLen) {
249                 dest[*destLen] = (int) (crc & 0xff);
250                 crc >>= 8;
251         }
252         uLong len = stream.total_in;
253         for (n = 0; n < 4; ++n, ++*destLen) {
254                 dest[*destLen] = (int) (len & 0xff);
255                 len >>= 8;
256         }
257         err = deflateEnd(&stream);
258         return err;
259 }
260 #endif
261
262 void end_burst(void)
263 {
264         size_t the_len;
265         char *the_data;
266
267         if (WC->burst == NULL)
268                 return;
269
270         the_len = WC->burst_len;
271         the_data = WC->burst;
272
273         WC->burst_len = 0;
274         WC->burst = NULL;
275
276 #ifdef HAVE_ZLIB
277         /* Handle gzip compression */
278         if (WC->gzip_ok) {
279                 char *compressed_data = NULL;
280                 uLongf compressed_len;
281
282                 compressed_len = (uLongf) ((the_len * 101) / 100) + 100;
283                 compressed_data = malloc(compressed_len);
284
285                 if (compress_gzip((Bytef *) compressed_data,
286                                   &compressed_len,
287                                   (Bytef *) the_data,
288                                   (uLongf) the_len, Z_BEST_SPEED) == Z_OK) {
289                         wprintf("Content-encoding: gzip\r\n");
290                         free(the_data);
291                         the_data = compressed_data;
292                         the_len = compressed_len;
293                 } else {
294                         free(compressed_data);
295                 }
296         }
297 #endif                          /* HAVE_ZLIB */
298
299         wprintf("Content-length: %d\r\n\r\n", the_len);
300         client_write(the_data, the_len);
301         free(the_data);
302         return;
303 }
304
305
306
307 /*
308  * Read data from the client socket with default timeout.
309  * (This is implemented in terms of client_read_to() and could be
310  * justifiably moved out of sysdep.c)
311  */
312 int client_read(int sock, char *buf, int bytes)
313 {
314         return (client_read_to(sock, buf, bytes, SLEEPING));
315 }
316
317
318 /*
319  * client_gets()   ...   Get a LF-terminated line of text from the client.
320  * (This is implemented in terms of client_read() and could be
321  * justifiably moved out of sysdep.c)
322  */
323 int client_gets(int sock, char *buf)
324 {
325         int i, retval;
326
327         /* Read one character at a time.
328          */
329         for (i = 0;; i++) {
330                 retval = client_read(sock, &buf[i], 1);
331                 if (retval != 1 || buf[i] == '\n' || i == 255)
332                         break;
333         }
334
335         /* If we got a long line, discard characters until the newline.
336          */
337         if (i == 255)
338                 while (buf[i] != '\n' && retval == 1)
339                         retval = client_read(sock, &buf[i], 1);
340
341         /*
342          * Strip any trailing non-printable characters.
343          */
344         buf[i] = 0;
345         while ((strlen(buf) > 0) && (!isprint(buf[strlen(buf) - 1]))) {
346                 buf[strlen(buf) - 1] = 0;
347         }
348         return (retval);
349 }
350
351
352 /*
353  * Start running as a daemon.  Only close stdio if do_close_stdio is set.
354  */
355 void start_daemon(int do_close_stdio)
356 {
357         if (do_close_stdio) {
358                 /* close(0); */
359                 close(1);
360                 close(2);
361         }
362         signal(SIGHUP, SIG_IGN);
363         signal(SIGINT, SIG_IGN);
364         signal(SIGQUIT, SIG_IGN);
365         if (fork() != 0)
366                 exit(0);
367 }
368
369 void spawn_another_worker_thread()
370 {
371         pthread_t SessThread;   /* Thread descriptor */
372         pthread_attr_t attr;    /* Thread attributes */
373         int ret;
374
375         lprintf(3, "Creating a new thread\n");
376
377         /* set attributes for the new thread */
378         pthread_attr_init(&attr);
379         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
380
381         /* Our per-thread stacks need to be bigger than the default size, otherwise
382          * the MIME parser crashes on FreeBSD, and the IMAP service crashes on
383          * 64-bit Linux.
384          */
385         if ((ret = pthread_attr_setstacksize(&attr, 1024 * 1024))) {
386                 lprintf(1, "pthread_attr_setstacksize: %s\n",
387                         strerror(ret));
388                 pthread_attr_destroy(&attr);
389         }
390
391         /* now create the thread */
392         if (pthread_create(&SessThread, &attr,
393                            (void *(*)(void *)) worker_entry, NULL)
394             != 0) {
395                 lprintf(1, "Can't create thread: %s\n", strerror(errno));
396         }
397
398         /* free up the attributes */
399         pthread_attr_destroy(&attr);
400 }
401
402 /*
403  * Here's where it all begins.
404  */
405 int main(int argc, char **argv)
406 {
407         pthread_t SessThread;   /* Thread descriptor */
408         pthread_attr_t attr;    /* Thread attributes */
409         int a, i;               /* General-purpose variables */
410         int port = PORT_NUM;    /* Port to listen on */
411         char tracefile[PATH_MAX];
412         char ip_addr[256];
413
414         /* Parse command line */
415 #ifdef HAVE_OPENSSL
416         while ((a = getopt(argc, argv, "hi:p:t:x:cs")) != EOF)
417 #else
418         while ((a = getopt(argc, argv, "hi:p:t:x:c")) != EOF)
419 #endif
420                 switch (a) {
421                 case 'i':
422                         strcpy(ip_addr, optarg);
423                         break;
424                 case 'p':
425                         port = atoi(optarg);
426                         break;
427                 case 't':
428                         strcpy(tracefile, optarg);
429                         freopen(tracefile, "w", stdout);
430                         freopen(tracefile, "w", stderr);
431                         freopen(tracefile, "r", stdin);
432                         break;
433                 case 'x':
434                         verbosity = atoi(optarg);
435                         break;
436                 case 'c':
437                         server_cookie = malloc(SIZ);
438                         if (server_cookie != NULL) {
439                                 strcpy(server_cookie,
440                                        "Set-cookie: wcserver=");
441                                 if (gethostname
442                                     (&server_cookie[strlen(server_cookie)],
443                                      200) != 0) {
444                                         lprintf(2, "gethostname: %s\n",
445                                                 strerror(errno));
446                                         free(server_cookie);
447                                 }
448                         }
449                         break;
450                 case 's':
451                         is_https = 1;
452                         break;
453                 default:
454                         fprintf(stderr, "usage: webserver "
455                                 "[-i ip_addr] [-p http_port] "
456                                 "[-t tracefile] [-c] "
457 #ifdef HAVE_OPENSSL
458                                 "[-s] "
459 #endif
460                                 "[remotehost [remoteport]]\n");
461                         return 1;
462                 }
463
464         if (optind < argc) {
465                 ctdlhost = argv[optind];
466                 if (++optind < argc)
467                         ctdlport = argv[optind];
468         }
469         /* Tell 'em who's in da house */
470         lprintf(1, SERVER "\n"
471                 "Copyright (C) 1996-2005 by the Citadel/UX development team.\n"
472                 "This software is distributed under the terms of the GNU General Public\n"
473                 "License.  If you paid for this software, someone is ripping you off.\n\n");
474
475         if (chdir(WEBCITDIR) != 0)
476                 perror("chdir");
477
478         /*
479          * Set up a place to put thread-specific data.
480          * We only need a single pointer per thread - it points to the
481          * wcsession struct to which the thread is currently bound.
482          */
483         if (pthread_key_create(&MyConKey, NULL) != 0) {
484                 lprintf(1, "Can't create TSD key: %s\n", strerror(errno));
485         }
486
487         /*
488          * Set up a place to put thread-specific SSL data.
489          * We don't stick this in the wcsession struct because SSL starts
490          * up before the session is bound, and it gets torn down between
491          * transactions.
492          */
493 #ifdef HAVE_OPENSSL
494         if (pthread_key_create(&ThreadSSL, NULL) != 0) {
495                 lprintf(1, "Can't create TSD key: %s\n", strerror(errno));
496         }
497 #endif
498
499         /*
500          * Bind the server to our favorite port.
501          * There is no need to check for errors, because ig_tcp_server()
502          * exits if it doesn't succeed.
503          */
504         lprintf(2, "Attempting to bind to port %d...\n", port);
505         msock = ig_tcp_server(ip_addr, port, LISTEN_QUEUE_LENGTH);
506         lprintf(2, "Listening on socket %d\n", msock);
507         signal(SIGPIPE, SIG_IGN);
508
509         pthread_mutex_init(&SessionListMutex, NULL);
510
511         /*
512          * Start up the housekeeping thread
513          */
514         pthread_attr_init(&attr);
515         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
516         pthread_create(&SessThread, &attr,
517                        (void *(*)(void *)) housekeeping_loop, NULL);
518
519
520         /*
521          * If this is an HTTPS server, fire up SSL
522          */
523 #ifdef HAVE_OPENSSL
524         if (is_https) {
525                 init_ssl();
526         }
527 #endif
528
529         /* Start a few initial worker threads */
530         for (i = 0; i < (MIN_WORKER_THREADS); ++i) {
531                 spawn_another_worker_thread();
532         }
533
534         /* now the original thread becomes another worker */
535         worker_entry();
536         return 0;
537 }
538
539
540 /*
541  * Entry point for worker threads
542  */
543 void worker_entry(void)
544 {
545         int ssock;
546         int i = 0;
547         int time_to_die = 0;
548         int fail_this_transaction = 0;
549
550         do {
551                 /* Only one thread can accept at a time */
552                 fail_this_transaction = 0;
553                 ssock = accept(msock, NULL, 0);
554                 if (ssock < 0) {
555                         lprintf(2, "accept() failed: %s\n",
556                                 strerror(errno));
557                 } else {
558                         /* Set the SO_REUSEADDR socket option */
559                         i = 1;
560                         setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR,
561                                    &i, sizeof(i));
562
563                         /* If we are an HTTPS server, go crypto now. */
564 #ifdef HAVE_OPENSSL
565                         if (is_https) {
566                                 if (starttls(ssock) != 0) {
567                                         fail_this_transaction = 1;
568                                         close(ssock);
569                                 }
570                         }
571 #endif
572
573                         if (fail_this_transaction == 0) {
574                                 /* Perform an HTTP transaction... */
575                                 context_loop(ssock);
576                                 /* ...and close the socket. */
577                                 lingering_close(ssock);
578                         }
579
580                 }
581
582         } while (!time_to_die);
583
584         pthread_exit(NULL);
585 }
586
587
588 int lprintf(int loglevel, const char *format, ...)
589 {
590         va_list ap;
591
592         if (loglevel <= verbosity) {
593                 va_start(ap, format);
594                 vfprintf(stderr, format, ap);
595                 va_end(ap);
596                 fflush(stderr);
597         }
598         return 1;
599 }