* Got HTTPS to work with Mozilla (by twiddling stuff that I still don't
[citadel.git] / webcit / webserver.c
1 /*
2  * webserver.c
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  * $Id$
9  */
10
11 #include <ctype.h>
12 #include <stdlib.h>
13 #ifdef HAVE_UNISTD_H
14 #include <unistd.h>
15 #endif
16 #include <stdio.h>
17 #ifdef HAVE_FCNTL_H
18 #include <fcntl.h>
19 #endif
20 #include <signal.h>
21 #include <sys/types.h>
22 #include <sys/wait.h>
23 #include <sys/socket.h>
24 #ifdef HAVE_SYS_TIME_H
25 #include <sys/time.h>
26 #endif
27 #ifdef HAVE_LIMITS_H
28 #include <limits.h>
29 #endif
30 #include <netinet/in.h>
31 #include <netdb.h>
32 #include <string.h>
33 #include <pwd.h>
34 #include <errno.h>
35 #include <stdarg.h>
36 #include <pthread.h>
37 #include <signal.h>
38 #include "webcit.h"
39 #include "webserver.h"
40
41 #ifndef HAVE_SNPRINTF
42 int vsnprintf(char *buf, size_t max, const char *fmt, va_list argp);
43 #endif
44
45 int verbosity = 9;              /* Logging level */
46 int msock;                      /* master listening socket */
47 int is_https = 0;               /* Nonzero if I am an HTTPS service */
48 extern void *context_loop(int);
49 extern void *housekeeping_loop(void);
50 extern pthread_mutex_t SessionListMutex;
51 extern pthread_key_t MyConKey;
52
53
54 char *server_cookie = NULL;
55
56
57 char *ctdlhost = DEFAULT_HOST;
58 char *ctdlport = DEFAULT_PORT;
59
60 /*
61  * This is a generic function to set up a master socket for listening on
62  * a TCP port.  The server shuts down if the bind fails.
63  */
64 int ig_tcp_server(int port_number, int queue_len)
65 {
66         struct sockaddr_in sin;
67         int s, i;
68
69         memset(&sin, 0, sizeof(sin));
70         sin.sin_family = AF_INET;
71         sin.sin_addr.s_addr = INADDR_ANY;
72
73         if (port_number == 0) {
74                 lprintf(1, "Cannot start: no port number specified.\n");
75                 exit(1);
76         }
77         sin.sin_port = htons((u_short) port_number);
78
79         s = socket(PF_INET, SOCK_STREAM, (getprotobyname("tcp")->p_proto));
80         if (s < 0) {
81                 lprintf(1, "Can't create a socket: %s\n",
82                        strerror(errno));
83                 exit(errno);
84         }
85         /* Set some socket options that make sense. */
86         i = 1;
87         setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
88
89         if (bind(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
90                 lprintf(1, "Can't bind: %s\n", strerror(errno));
91                 exit(errno);
92         }
93         if (listen(s, queue_len) < 0) {
94                 lprintf(1, "Can't listen: %s\n", strerror(errno));
95                 exit(errno);
96         }
97         return (s);
98 }
99
100
101 /*
102  * Read data from the client socket.
103  * Return values are:
104  *      1       Requested number of bytes has been read.
105  *      0       Request timed out.
106  *      -1      Connection is broken, or other error.
107  */
108 int client_read_to(int sock, char *buf, int bytes, int timeout)
109 {
110         int len, rlen;
111         fd_set rfds;
112         struct timeval tv;
113         int retval;
114
115
116 #ifdef HAVE_OPENSSL
117         if (is_https) {
118                 return(client_read_ssl(buf, bytes, timeout));
119         }
120 #endif
121
122         len = 0;
123         while (len < bytes) {
124                 FD_ZERO(&rfds);
125                 FD_SET(sock, &rfds);
126                 tv.tv_sec = timeout;
127                 tv.tv_usec = 0;
128
129                 retval = select((sock) + 1,
130                                 &rfds, NULL, NULL, &tv);
131                 if (FD_ISSET(sock, &rfds) == 0) {
132                         return (0);
133                 }
134
135                 rlen = read(sock, &buf[len], bytes - len);
136
137                 if (rlen < 1) {
138                         lprintf(2, "client_read() failed: %s\n",
139                                strerror(errno));
140                         return(-1);
141                 }
142                 len = len + rlen;
143         }
144         return (1);
145 }
146
147
148 ssize_t client_write(const void *buf, size_t count) {
149 #ifdef HAVE_OPENSSL
150         if (is_https) {
151                 client_write_ssl((char *)buf, count);
152                 return(count);
153         }
154 #endif
155         return(write(WC->http_sock, buf, count));
156 }
157
158
159 /*
160  * Read data from the client socket with default timeout.
161  * (This is implemented in terms of client_read_to() and could be
162  * justifiably moved out of sysdep.c)
163  */
164 int client_read(int sock, char *buf, int bytes)
165 {
166         return (client_read_to(sock, buf, bytes, SLEEPING));
167 }
168
169
170 /*
171  * client_gets()   ...   Get a LF-terminated line of text from the client.
172  * (This is implemented in terms of client_read() and could be
173  * justifiably moved out of sysdep.c)
174  */
175 int client_gets(int sock, char *buf)
176 {
177         int i, retval;
178
179         /* Read one character at a time.
180          */
181         for (i = 0;; i++) {
182                 retval = client_read(sock, &buf[i], 1);
183                 if (retval != 1 || buf[i] == '\n' || i == 255)
184                         break;
185         }
186
187         /* If we got a long line, discard characters until the newline.
188          */
189         if (i == 255)
190                 while (buf[i] != '\n' && retval == 1)
191                         retval = client_read(sock, &buf[i], 1);
192
193         /*
194          * Strip any trailing non-printable characters.
195          */
196         buf[i] = 0;
197         while ((strlen(buf) > 0) && (!isprint(buf[strlen(buf) - 1]))) {
198                 buf[strlen(buf) - 1] = 0;
199         }
200         return (retval);
201 }
202
203
204 /*
205  * Start running as a daemon.  Only close stdio if do_close_stdio is set.
206  */
207 void start_daemon(int do_close_stdio)
208 {
209         if (do_close_stdio) {
210                 /* close(0); */
211                 close(1);
212                 close(2);
213         }
214         signal(SIGHUP, SIG_IGN);
215         signal(SIGINT, SIG_IGN);
216         signal(SIGQUIT, SIG_IGN);
217         if (fork() != 0)
218                 exit(0);
219 }
220
221 void spawn_another_worker_thread() {
222         pthread_t SessThread;   /* Thread descriptor */
223         pthread_attr_t attr;    /* Thread attributes */
224
225         lprintf(3, "Creating a new thread\n");
226
227         /* set attributes for the new thread */
228         pthread_attr_init(&attr);
229         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
230
231         /* now create the thread */
232         if (pthread_create(&SessThread, &attr,
233                         (void *(*)(void *)) worker_entry, NULL)
234                    != 0) {
235                 lprintf(1, "Can't create thread: %s\n",
236                         strerror(errno));
237         }
238 }
239
240 /*
241  * Here's where it all begins.
242  */
243 int main(int argc, char **argv)
244 {
245         pthread_t SessThread;   /* Thread descriptor */
246         pthread_attr_t attr;    /* Thread attributes */
247         int a, i;               /* General-purpose variables */
248         int port = PORT_NUM;    /* Port to listen on */
249         char tracefile[PATH_MAX];
250
251         /* Parse command line */
252 #ifdef HAVE_OPENSSL
253         while ((a = getopt(argc, argv, "hp:t:cs")) != EOF)
254 #else
255         while ((a = getopt(argc, argv, "hp:t:c")) != EOF)
256 #endif
257                 switch (a) {
258                 case 'p':
259                         port = atoi(optarg);
260                         break;
261                 case 't':
262                         strcpy(tracefile, optarg);
263                         freopen(tracefile, "w", stdout);
264                         freopen(tracefile, "w", stderr);
265                         freopen(tracefile, "r", stdin);
266                         break;
267                 case 'x':
268                         verbosity = atoi(optarg);
269                         break;
270                 case 'c':
271                         server_cookie = malloc(SIZ);
272                         if (server_cookie != NULL) {
273                                 strcpy(server_cookie, "Set-cookie: wcserver=");
274                                 if (gethostname(
275                                    &server_cookie[strlen(server_cookie)],
276                                    200) != 0) {
277                                         lprintf(2, "gethostname: %s\n",
278                                                 strerror(errno));
279                                         free(server_cookie);
280                                 }
281                         }
282                         break;
283                 case 's':
284                         is_https = 1;
285                         break;
286                 default:
287                         fprintf(stderr, "usage: webserver [-p localport] "
288                                 "[-t tracefile] [-c] "
289 #ifdef HAVE_OPENSSL
290                                 "[-s] "
291 #endif
292                                 "[remotehost [remoteport]]\n");
293                         return 1;
294                 }
295
296         if (optind < argc) {
297                 ctdlhost = argv[optind];
298                 if (++optind < argc)
299                         ctdlport = argv[optind];
300         }
301         /* Tell 'em who's in da house */
302         lprintf(1, SERVER "\n"
303 "Copyright (C) 1996-2004 by the Citadel/UX development team.\n"
304 "This software is distributed under the terms of the GNU General Public\n"
305 "License.  If you paid for this software, someone is ripping you off.\n\n");
306
307         if (chdir(WEBCITDIR) != 0)
308                 perror("chdir");
309
310         /*
311          * Set up a place to put thread-specific data.
312          * We only need a single pointer per thread - it points to the
313          * wcsession struct to which the thread is currently bound.
314          */
315         if (pthread_key_create(&MyConKey, NULL) != 0) {
316                 lprintf(1, "Can't create TSD key: %s\n", strerror(errno));
317         }
318
319         /*
320          * Set up a place to put thread-specific SSL data.
321          * We don't stick this in the wcsession struct because SSL starts
322          * up before the session is bound, and it gets torn down between
323          * transactions.
324          */
325 #ifdef HAVE_OPENSSL
326         if (pthread_key_create(&ThreadSSL, NULL) != 0) {
327                 lprintf(1, "Can't create TSD key: %s\n", strerror(errno));
328         }
329 #endif
330
331         /*
332          * Bind the server to our favorite port.
333          * There is no need to check for errors, because ig_tcp_server()
334          * exits if it doesn't succeed.
335          */
336         lprintf(2, "Attempting to bind to port %d...\n", port);
337         msock = ig_tcp_server(port, LISTEN_QUEUE_LENGTH);
338         lprintf(2, "Listening on socket %d\n", msock);
339         signal(SIGPIPE, SIG_IGN);
340
341         pthread_mutex_init(&SessionListMutex, NULL);
342
343         /*
344          * Start up the housekeeping thread
345          */
346         pthread_attr_init(&attr);
347         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
348         pthread_create(&SessThread, &attr,
349                        (void *(*)(void *)) housekeeping_loop, NULL);
350
351
352         /*
353          * If this is an HTTPS server, fire up SSL
354          */
355 #ifdef HAVE_OPENSSL
356         if (is_https) {
357                 init_ssl();
358         }
359 #endif
360
361         /* Start a few initial worker threads */
362         for (i=0; i<(MIN_WORKER_THREADS); ++i) {
363                 spawn_another_worker_thread();
364         }
365
366         /* now the original thread becomes another worker */
367         worker_entry();
368         return 0;
369 }
370
371
372 /*
373  * Entry point for worker threads
374  */
375 void worker_entry(void) {
376         int ssock;
377         int i = 0;
378         int time_to_die = 0;
379         int fail_this_transaction = 0;
380
381         do {
382                 /* Only one thread can accept at a time */
383                 fail_this_transaction = 0;
384                 ssock = accept(msock, NULL, 0);
385                 if (ssock < 0) {
386                         lprintf(2, "accept() failed: %s\n", strerror(errno));
387                 } else {
388                         /* Set the SO_REUSEADDR socket option */
389                         i = 1;
390                         setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR,
391                                 &i, sizeof(i));
392
393                         /* If we are an HTTPS server, go crypto now. */
394 #ifdef HAVE_OPENSSL
395                         if (is_https) {
396                                 if (starttls(ssock) != 0) {
397                                         fail_this_transaction = 1;
398                                         close(ssock);
399                                 }
400                         }
401 #endif
402
403                         if (fail_this_transaction == 0) {
404                                 /* Perform an HTTP transaction... */
405                                 context_loop(ssock);
406                                 /* ...and close the socket. */
407                                 lingering_close(ssock);
408                         }
409
410                 }
411
412         } while (!time_to_die);
413
414         pthread_exit(NULL);
415 }
416
417
418 int lprintf(int loglevel, const char *format, ...)
419 {
420         va_list ap;
421         char buf[4096];
422
423         va_start(ap, format);
424         vsprintf(buf, format, ap);
425         va_end(ap);
426
427         if (loglevel <= verbosity) {
428                 struct timeval tv;
429                 struct tm *tim;
430
431                 gettimeofday(&tv, NULL);
432                 tim = localtime((time_t *)&(tv.tv_sec));
433
434                 if (WC && WC->wc_session) {
435                         fprintf(stderr,
436                                 "%04d/%02d/%02d %2d:%02d:%02d.%03ld [%ld:%d] %s",
437                                 tim->tm_year + 1900, tim->tm_mon + 1,
438                                 tim->tm_mday, tim->tm_hour, tim->tm_min,
439                                 tim->tm_sec, (long)tv.tv_usec / 1000,
440                                 (long)pthread_self(),
441                                 WC->wc_session, buf);
442                 } else {
443                         fprintf(stderr,
444                                 "%04d/%02d/%02d %2d:%02d:%02d.%03ld [%ld] %s",
445                                 tim->tm_year + 1900, tim->tm_mon + 1,
446                                 tim->tm_mday, tim->tm_hour, tim->tm_min,
447                                 tim->tm_sec, (long)tv.tv_usec / 1000,
448                                 (long)pthread_self(),
449                                 buf);
450                 }
451                 fflush(stderr);
452         }
453         return 1;
454 }