Merge pull request #278 from nmav/ocserv
[project/luci.git] / libs / nixio / axTLS / httpd / axhttpd.c
1 /*
2 * Copyright (c) 2007, Cameron Rich
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 * * Neither the name of the axTLS project nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include <stdio.h>
32 #include <string.h>
33 #include <sys/types.h>
34 #include <signal.h>
35 #include <stdlib.h>
36 #include <sys/stat.h>
37 #include <pwd.h>
38 #include "axhttp.h"
39
40 struct serverstruct *servers;
41 struct connstruct *usedconns;
42 struct connstruct *freeconns;
43 const char * const server_version = "axhttpd/"AXTLS_VERSION;
44
45 static void addtoservers(int sd);
46 static int openlistener(int port);
47 static void handlenewconnection(int listenfd, int is_ssl);
48 static void addconnection(int sd, char *ip, int is_ssl);
49 static void ax_chdir(void);
50
51 #if defined(CONFIG_HTTP_HAS_CGI)
52 struct cgiextstruct *cgiexts;
53 static void addcgiext(const char *tp);
54
55 #if !defined(WIN32)
56 static void reaper(int sigtype)
57 {
58 wait3(NULL, WNOHANG, NULL);
59 }
60 #endif
61 #endif
62
63 #ifdef CONFIG_HTTP_VERBOSE /* should really be in debug mode or something */
64 /* clean up memory for valgrind */
65 static void sigint_cleanup(int sig)
66 {
67 struct serverstruct *sp;
68 struct connstruct *tp;
69
70 while (servers != NULL)
71 {
72 if (servers->is_ssl)
73 ssl_ctx_free(servers->ssl_ctx);
74
75 sp = servers->next;
76 free(servers);
77 servers = sp;
78 }
79
80 while (freeconns != NULL)
81 {
82 tp = freeconns->next;
83 free(freeconns);
84 freeconns = tp;
85 }
86
87 while (usedconns != NULL)
88 {
89 tp = usedconns->next;
90 free(usedconns);
91 usedconns = tp;
92 }
93
94 #if defined(CONFIG_HTTP_HAS_CGI)
95 while (cgiexts)
96 {
97 struct cgiextstruct *cp = cgiexts->next;
98 if (cp == NULL) /* last entry */
99 free(cgiexts->ext);
100 free(cgiexts);
101 cgiexts = cp;
102 }
103 #endif
104
105 exit(0);
106 }
107
108 static void die(int sigtype)
109 {
110 exit(0);
111 }
112 #endif
113
114 int main(int argc, char *argv[])
115 {
116 fd_set rfds, wfds;
117 struct connstruct *tp, *to;
118 struct serverstruct *sp;
119 int rnum, wnum, active;
120 int i;
121 time_t currtime;
122
123 #ifdef WIN32
124 WORD wVersionRequested = MAKEWORD(2, 2);
125 WSADATA wsaData;
126 WSAStartup(wVersionRequested,&wsaData);
127 #else
128 signal(SIGPIPE, SIG_IGN);
129 #if defined(CONFIG_HTTP_HAS_CGI)
130 signal(SIGCHLD, reaper);
131 #endif
132 #ifdef CONFIG_HTTP_VERBOSE
133 signal(SIGQUIT, die);
134 #endif
135 #endif
136
137 #ifdef CONFIG_HTTP_VERBOSE
138 signal(SIGTERM, die);
139 signal(SIGINT, sigint_cleanup);
140 #endif
141 tdate_init();
142
143 for (i = 0; i < INITIAL_CONNECTION_SLOTS; i++)
144 {
145 tp = freeconns;
146 freeconns = (struct connstruct *)calloc(1, sizeof(struct connstruct));
147 freeconns->next = tp;
148 }
149
150 if ((active = openlistener(CONFIG_HTTP_PORT)) == -1)
151 {
152 #ifdef CONFIG_HTTP_VERBOSE
153 fprintf(stderr, "ERR: Couldn't bind to port %d\n",
154 CONFIG_HTTP_PORT);
155 #endif
156 exit(1);
157 }
158
159 addtoservers(active);
160
161 if ((active = openlistener(CONFIG_HTTP_HTTPS_PORT)) == -1)
162 {
163 #ifdef CONFIG_HTTP_VERBOSE
164 fprintf(stderr, "ERR: Couldn't bind to port %d\n",
165 CONFIG_HTTP_HTTPS_PORT);
166 #endif
167 exit(1);
168 }
169
170 addtoservers(active);
171 servers->ssl_ctx = ssl_ctx_new(CONFIG_HTTP_DEFAULT_SSL_OPTIONS,
172 CONFIG_HTTP_SESSION_CACHE_SIZE);
173 servers->is_ssl = 1;
174
175 #if defined(CONFIG_HTTP_HAS_CGI)
176 addcgiext(CONFIG_HTTP_CGI_EXTENSIONS);
177 #endif
178
179 #if defined(CONFIG_HTTP_VERBOSE)
180 #if defined(CONFIG_HTTP_HAS_CGI)
181 printf("addcgiext %s\n", CONFIG_HTTP_CGI_EXTENSIONS);
182 #endif
183 printf("%s: listening on ports %d (http) and %d (https)\n",
184 server_version, CONFIG_HTTP_PORT, CONFIG_HTTP_HTTPS_PORT);
185 TTY_FLUSH();
186 #endif
187
188 ax_chdir();
189
190 #ifdef CONFIG_HTTP_ENABLE_DIFFERENT_USER
191 {
192 struct passwd *pd = getpwnam(CONFIG_HTTP_USER);
193
194 if (pd != NULL)
195 {
196 int res = setuid(pd->pw_uid);
197 res |= setgid(pd->pw_gid);
198
199 #if defined(CONFIG_HTTP_VERBOSE)
200 if (res == 0)
201 {
202 printf("change to '%s' successful\n", CONFIG_HTTP_USER);
203 TTY_FLUSH();
204 }
205 #endif
206 }
207
208 }
209 #endif
210
211
212 #ifndef WIN32
213 #ifdef CONFIG_HTTP_IS_DAEMON
214 if (fork() > 0) /* parent will die */
215 exit(0);
216
217 setsid();
218 #endif
219 #endif
220
221 /* main loop */
222 while (1)
223 {
224 FD_ZERO(&rfds);
225 FD_ZERO(&wfds);
226 rnum = wnum = -1;
227 sp = servers;
228
229 while (sp != NULL) /* read each server port */
230 {
231 FD_SET(sp->sd, &rfds);
232
233 if (sp->sd > rnum)
234 rnum = sp->sd;
235 sp = sp->next;
236 }
237
238 /* Add the established sockets */
239 tp = usedconns;
240 currtime = time(NULL);
241
242 while (tp != NULL)
243 {
244 if (currtime > tp->timeout) /* timed out? Kill it. */
245 {
246 to = tp;
247 tp = tp->next;
248 removeconnection(to);
249 continue;
250 }
251
252 if (tp->state == STATE_WANT_TO_READ_HEAD)
253 {
254 FD_SET(tp->networkdesc, &rfds);
255 if (tp->networkdesc > rnum)
256 rnum = tp->networkdesc;
257 }
258
259 if (tp->state == STATE_WANT_TO_SEND_HEAD)
260 {
261 FD_SET(tp->networkdesc, &wfds);
262 if (tp->networkdesc > wnum)
263 wnum = tp->networkdesc;
264 }
265
266 if (tp->state == STATE_WANT_TO_READ_FILE)
267 {
268 FD_SET(tp->filedesc, &rfds);
269 if (tp->filedesc > rnum)
270 rnum = tp->filedesc;
271 }
272
273 if (tp->state == STATE_WANT_TO_SEND_FILE)
274 {
275 FD_SET(tp->networkdesc, &wfds);
276 if (tp->networkdesc > wnum)
277 wnum = tp->networkdesc;
278 }
279
280 #if defined(CONFIG_HTTP_DIRECTORIES)
281 if (tp->state == STATE_DOING_DIR)
282 {
283 FD_SET(tp->networkdesc, &wfds);
284 if (tp->networkdesc > wnum)
285 wnum = tp->networkdesc;
286 }
287 #endif
288 tp = tp->next;
289 }
290
291 active = select(wnum > rnum ? wnum+1 : rnum+1,
292 rnum != -1 ? &rfds : NULL,
293 wnum != -1 ? &wfds : NULL,
294 NULL, NULL);
295
296 /* New connection? */
297 sp = servers;
298 while (active > 0 && sp != NULL)
299 {
300 if (FD_ISSET(sp->sd, &rfds))
301 {
302 handlenewconnection(sp->sd, sp->is_ssl);
303 active--;
304 }
305
306 sp = sp->next;
307 }
308
309 /* Handle the established sockets */
310 tp = usedconns;
311
312 while (active > 0 && tp != NULL)
313 {
314 to = tp;
315 tp = tp->next;
316
317 if (to->state == STATE_WANT_TO_READ_HEAD &&
318 FD_ISSET(to->networkdesc, &rfds))
319 {
320 active--;
321 #if defined(CONFIG_HTTP_HAS_CGI)
322 if (to->post_state)
323 read_post_data(to);
324 else
325 #endif
326 procreadhead(to);
327 }
328
329 if (to->state == STATE_WANT_TO_SEND_HEAD &&
330 FD_ISSET(to->networkdesc, &wfds))
331 {
332 active--;
333 procsendhead(to);
334 }
335
336 if (to->state == STATE_WANT_TO_READ_FILE &&
337 FD_ISSET(to->filedesc, &rfds))
338 {
339 active--;
340 procreadfile(to);
341 }
342
343 if (to->state == STATE_WANT_TO_SEND_FILE &&
344 FD_ISSET(to->networkdesc, &wfds))
345 {
346 active--;
347 procsendfile(to);
348 }
349
350 #if defined(CONFIG_HTTP_DIRECTORIES)
351 if (to->state == STATE_DOING_DIR &&
352 FD_ISSET(to->networkdesc, &wfds))
353 {
354 active--;
355 procdodir(to);
356 }
357 #endif
358 }
359 }
360
361 return 0;
362 }
363
364 #if defined(CONFIG_HTTP_HAS_CGI)
365 static void addcgiext(const char *cgi_exts)
366 {
367 char *cp = strdup(cgi_exts);
368
369 /* extenstions are comma separated */
370 do
371 {
372 struct cgiextstruct *ex = (struct cgiextstruct *)
373 malloc(sizeof(struct cgiextstruct));
374 ex->ext = cp;
375 ex->next = cgiexts;
376 cgiexts = ex;
377 if ((cp = strchr(cp, ',')) != NULL)
378 *cp++ = 0;
379 } while (cp != NULL);
380 }
381 #endif
382
383 static void addtoservers(int sd)
384 {
385 struct serverstruct *tp = (struct serverstruct *)
386 calloc(1, sizeof(struct serverstruct));
387 tp->next = servers;
388 tp->sd = sd;
389 servers = tp;
390 }
391
392 #ifdef HAVE_IPV6
393 static void handlenewconnection(int listenfd, int is_ssl)
394 {
395 struct sockaddr_in6 their_addr;
396 int tp = sizeof(their_addr);
397 char ipbuf[100];
398 int connfd = accept(listenfd, (struct sockaddr *)&their_addr, &tp);
399
400 if (tp == sizeof(struct sockaddr_in6))
401 inet_ntop(AF_INET6, &their_addr.sin6_addr, ipbuf, sizeof(ipbuf));
402 else if (tp == sizeof(struct sockaddr_in))
403 inet_ntop(AF_INET, &(((struct sockaddr_in *)&their_addr)->sin_addr),
404 ipbuf, sizeof(ipbuf));
405 else
406 *ipbuf = '\0';
407
408 addconnection(connfd, ipbuf, is_ssl);
409 }
410
411 #else
412 static void handlenewconnection(int listenfd, int is_ssl)
413 {
414 struct sockaddr_in their_addr;
415 socklen_t tp = sizeof(struct sockaddr_in);
416 int connfd = accept(listenfd, (struct sockaddr *)&their_addr, &tp);
417 addconnection(connfd, inet_ntoa(their_addr.sin_addr), is_ssl);
418 }
419 #endif
420
421 static int openlistener(int port)
422 {
423 int sd;
424 #ifdef WIN32
425 char tp = 1;
426 #else
427 int tp = 1;
428 #endif
429 #ifndef HAVE_IPV6
430 struct sockaddr_in my_addr;
431
432 if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
433 return -1;
434
435 memset(&my_addr, 0, sizeof(my_addr));
436 my_addr.sin_family = AF_INET;
437 my_addr.sin_port = htons((short)port);
438 my_addr.sin_addr.s_addr = INADDR_ANY;
439 #else
440 struct sockaddr_in6 my_addr;
441
442 if ((sd = socket(AF_INET6, SOCK_STREAM, 0)) == -1)
443 return -1;
444
445 memset(&my_addr, 0, sizeof(my_addr));
446 my_addr.sin6_family = AF_INET6;
447 my_addr.sin6_port = htons(port);
448 my_addr.sin6_addr.s_addr = INADDR_ANY;
449 #endif
450
451 setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &tp, sizeof(tp));
452 if (bind(sd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
453 {
454 close(sd);
455 return -1;
456 }
457
458 listen(sd, BACKLOG);
459 return sd;
460 }
461
462 /* Wrapper function for strncpy() that guarantees
463 a null-terminated string. This is to avoid any possible
464 issues due to strncpy()'s behaviour.
465 */
466 char *my_strncpy(char *dest, const char *src, size_t n)
467 {
468 strncpy(dest, src, n);
469 dest[n-1] = '\0';
470 return dest;
471 }
472
473 int isdir(const char *tpbuf)
474 {
475 struct stat st;
476 char path[MAXREQUESTLENGTH];
477 strcpy(path, tpbuf);
478
479 #ifdef WIN32 /* win32 stat() can't handle trailing '\' */
480 if (path[strlen(path)-1] == '\\')
481 path[strlen(path)-1] = 0;
482 #endif
483
484 if (stat(path, &st) == -1)
485 return 0;
486
487 if ((st.st_mode & S_IFMT) == S_IFDIR)
488 return 1;
489
490 return 0;
491 }
492
493 static void addconnection(int sd, char *ip, int is_ssl)
494 {
495 struct connstruct *tp;
496
497 /* Get ourselves a connstruct */
498 if (freeconns == NULL)
499 tp = (struct connstruct *)calloc(1, sizeof(struct connstruct));
500 else
501 {
502 tp = freeconns;
503 freeconns = tp->next;
504 }
505
506 /* Attach it to the used list */
507 tp->next = usedconns;
508 usedconns = tp;
509 tp->networkdesc = sd;
510
511 if (is_ssl)
512 tp->ssl = ssl_server_new(servers->ssl_ctx, sd);
513
514 tp->is_ssl = is_ssl;
515 tp->filedesc = -1;
516 #if defined(CONFIG_HTTP_HAS_DIRECTORIES)
517 tp->dirp = NULL;
518 #endif
519 *tp->actualfile = '\0';
520 *tp->filereq = '\0';
521 tp->state = STATE_WANT_TO_READ_HEAD;
522 tp->reqtype = TYPE_GET;
523 tp->close_when_done = 0;
524 tp->timeout = time(NULL) + CONFIG_HTTP_TIMEOUT;
525 #if defined(CONFIG_HTTP_HAS_CGI)
526 strcpy(tp->remote_addr, ip);
527 #endif
528 }
529
530 void removeconnection(struct connstruct *cn)
531 {
532 struct connstruct *tp;
533 int shouldret = 0;
534
535 tp = usedconns;
536
537 if (tp == NULL || cn == NULL)
538 shouldret = 1;
539 else if (tp == cn)
540 usedconns = tp->next;
541 else
542 {
543 while (tp != NULL)
544 {
545 if (tp->next == cn)
546 {
547 tp->next = (tp->next)->next;
548 shouldret = 0;
549 break;
550 }
551
552 tp = tp->next;
553 shouldret = 1;
554 }
555 }
556
557 if (shouldret)
558 return;
559
560 /* If we did, add it to the free list */
561 cn->next = freeconns;
562 freeconns = cn;
563
564 /* Close it all down */
565 if (cn->networkdesc != -1)
566 {
567 if (cn->is_ssl)
568 {
569 ssl_free(cn->ssl);
570 cn->ssl = NULL;
571 }
572
573 SOCKET_CLOSE(cn->networkdesc);
574 }
575
576 if (cn->filedesc != -1)
577 close(cn->filedesc);
578
579 #if defined(CONFIG_HTTP_HAS_DIRECTORIES)
580 if (cn->dirp != NULL)
581 #ifdef WIN32
582 FindClose(cn->dirp);
583 #else
584 closedir(cn->dirp);
585 #endif
586 #endif
587 }
588
589 /*
590 * Change directories one way or the other.
591 */
592 static void ax_chdir(void)
593 {
594 static char *webroot = CONFIG_HTTP_WEBROOT;
595
596 if (chdir(webroot))
597 {
598 #ifdef CONFIG_HTTP_VERBOSE
599 fprintf(stderr, "'%s' is not a directory\n", webroot);
600 #endif
601 exit(1);
602 }
603 }
604