1 /*
2 * Copyright (c) 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
38 #if !defined(WIN32)
39 #include <pwd.h>
40 #endif
41 #include "axhttp.h"
42
43 struct serverstruct *servers;
44 struct connstruct *usedconns;
45 struct connstruct *freeconns;
46 const char * const server_version = "axhttpd/"AXTLS_VERSION;
47 static const char *webroot = CONFIG_HTTP_WEBROOT;
48
49 static void addtoservers(int sd);
50 static int openlistener(char *address, int port);
51 static void handlenewconnection(int listenfd, int is_ssl);
52 static void addconnection(int sd, char *ip, int is_ssl);
53 static void ax_chdir(void);
54
55 #if defined(CONFIG_HTTP_HAS_CGI)
56 struct cgiextstruct *cgiexts;
57 static void addcgiext(const char *tp);
58
59 #if !defined(WIN32)
reaper(int sigtype)60 static void reaper(int sigtype)
61 {
62 while (wait3(NULL, WNOHANG, NULL) > 0)
63 continue;
64 }
65 #endif
66 #endif
67
68 #ifdef CONFIG_HTTP_VERBOSE /* should really be in debug mode or something */
69 /* clean up memory for valgrind */
sigint_cleanup(int sig)70 static void sigint_cleanup(int sig)
71 {
72 struct serverstruct *sp;
73 struct connstruct *tp;
74
75 while (servers != NULL)
76 {
77 if (servers->is_ssl)
78 ssl_ctx_free(servers->ssl_ctx);
79
80 sp = servers->next;
81 free(servers);
82 servers = sp;
83 }
84
85 while (freeconns != NULL)
86 {
87 tp = freeconns->next;
88 free(freeconns);
89 freeconns = tp;
90 }
91
92 while (usedconns != NULL)
93 {
94 tp = usedconns->next;
95 free(usedconns);
96 usedconns = tp;
97 }
98
99 #if defined(CONFIG_HTTP_HAS_CGI)
100 while (cgiexts)
101 {
102 struct cgiextstruct *cp = cgiexts->next;
103 if (cp == NULL) /* last entry */
104 free(cgiexts->ext);
105 free(cgiexts);
106 cgiexts = cp;
107 }
108 #endif
109
110 exit(0);
111 }
112
die(int sigtype)113 static void die(int sigtype)
114 {
115 exit(0);
116 }
117 #endif
118
main(int argc,char * argv[])119 int main(int argc, char *argv[])
120 {
121 fd_set rfds, wfds;
122 struct connstruct *tp, *to;
123 struct serverstruct *sp;
124 int rnum, wnum, active;
125 int i = 1;
126 time_t currtime;
127 char *httpAddress = NULL;
128 int httpPort = CONFIG_HTTP_PORT;
129 char *httpsAddress = NULL;
130 int httpsPort = CONFIG_HTTP_HTTPS_PORT;
131 char *portStr;
132
133 #ifdef WIN32
134 WORD wVersionRequested = MAKEWORD(2, 2);
135 WSADATA wsaData;
136 WSAStartup(wVersionRequested,&wsaData);
137 #else
138 signal(SIGPIPE, SIG_IGN);
139 #if defined(CONFIG_HTTP_HAS_CGI)
140 signal(SIGCHLD, reaper);
141 #endif
142 #ifdef CONFIG_HTTP_VERBOSE
143 signal(SIGQUIT, die);
144 #endif
145 #endif
146
147 #ifdef CONFIG_HTTP_VERBOSE
148 signal(SIGTERM, die);
149 signal(SIGINT, sigint_cleanup);
150 #endif
151 tdate_init();
152
153 /* get some command-line parameters */
154 while (argv[i] != NULL)
155 {
156 if (strcmp(argv[i], "-p") == 0 && argv[i+1] != NULL)
157 {
158 if ((portStr = strchr(argv[i+1], ':')) != NULL)
159 {
160 httpAddress = argv[i+1];
161 *portStr = 0;
162 httpPort = atoi(portStr + 1);
163 }
164 else
165 httpPort = atoi(argv[i+1]);
166
167 i += 2;
168 continue;
169 }
170
171 if (strcmp(argv[i], "-s") == 0 && argv[i+1] != NULL)
172 {
173 if ((portStr = strchr(argv[i+1], ':')) != NULL)
174 {
175 httpsAddress = argv[i+1];
176 *portStr = 0;
177 httpsPort = atoi(portStr + 1);
178 }
179 else
180 httpsPort = atoi(argv[i+1]);
181
182 i += 2;
183 continue;
184 }
185
186 if (strcmp(argv[i], "-w") == 0 && argv[i+1] != NULL)
187 {
188 webroot = argv[i+1];
189 i += 2;
190 continue;
191 }
192
193 printf("%s:\n"
194 " [-p [address:]httpport]\n"
195 " [-s [address:]httpsport]\n"
196 " [-w webroot]\n", argv[0]);
197 exit(1);
198 }
199
200 for (i = 0; i < INITIAL_CONNECTION_SLOTS; i++)
201 {
202 tp = freeconns;
203 freeconns = (struct connstruct *)calloc(1, sizeof(struct connstruct));
204 freeconns->next = tp;
205 }
206
207 if ((active = openlistener(httpAddress, httpPort)) == -1)
208 {
209 #ifdef CONFIG_HTTP_VERBOSE
210 fprintf(stderr, "ERR: Couldn't bind to port %d\n", httpPort);
211 #endif
212 exit(1);
213 }
214
215 addtoservers(active);
216
217 if ((active = openlistener(httpsAddress, httpsPort)) == -1)
218 {
219 #ifdef CONFIG_HTTP_VERBOSE
220 fprintf(stderr, "ERR: Couldn't bind to port %d\n", httpsPort);
221 #endif
222 exit(1);
223 }
224
225 addtoservers(active);
226 servers->ssl_ctx = ssl_ctx_new(CONFIG_HTTP_DEFAULT_SSL_OPTIONS,
227 CONFIG_HTTP_SESSION_CACHE_SIZE);
228 servers->is_ssl = 1;
229
230 #if defined(CONFIG_HTTP_HAS_CGI)
231 addcgiext(CONFIG_HTTP_CGI_EXTENSIONS);
232 #endif
233
234 #if defined(CONFIG_HTTP_VERBOSE)
235 #if defined(CONFIG_HTTP_HAS_CGI)
236 printf("addcgiext %s\n", CONFIG_HTTP_CGI_EXTENSIONS);
237 #endif
238 printf("%s: listening on ports %d (http) and %d (https)\n",
239 server_version, httpPort, httpsPort);
240 TTY_FLUSH();
241 #endif
242
243 ax_chdir();
244
245 #ifdef CONFIG_HTTP_ENABLE_DIFFERENT_USER
246 {
247 struct passwd *pd = getpwnam(CONFIG_HTTP_USER);
248
249 if (pd != NULL)
250 {
251 int res = setuid(pd->pw_uid);
252 res |= setgid(pd->pw_gid);
253
254 #if defined(CONFIG_HTTP_VERBOSE)
255 if (res == 0)
256 {
257 printf("change to '%s' successful\n", CONFIG_HTTP_USER);
258 TTY_FLUSH();
259 }
260 #endif
261 }
262
263 }
264 #endif
265
266
267 #ifndef WIN32
268 #ifdef CONFIG_HTTP_IS_DAEMON
269 if (fork() > 0) /* parent will die */
270 exit(0);
271
272 setsid();
273 #endif
274 #endif
275
276 /* main loop */
277 while (1)
278 {
279 struct timeval tv = { 10, 0 };
280 FD_ZERO(&rfds);
281 FD_ZERO(&wfds);
282 rnum = wnum = -1;
283 sp = servers;
284
285 while (sp != NULL) /* read each server port */
286 {
287 FD_SET(sp->sd, &rfds);
288
289 if (sp->sd > rnum)
290 rnum = sp->sd;
291 sp = sp->next;
292 }
293
294 /* Add the established sockets */
295 tp = usedconns;
296 currtime = time(NULL);
297
298 while (tp != NULL)
299 {
300 if (currtime > tp->timeout) /* timed out? Kill it. */
301 {
302 to = tp;
303 tp = tp->next;
304 removeconnection(to);
305 continue;
306 }
307
308 if (tp->state == STATE_WANT_TO_READ_HEAD)
309 {
310 FD_SET(tp->networkdesc, &rfds);
311 if (tp->networkdesc > rnum)
312 rnum = tp->networkdesc;
313 }
314
315 if (tp->state == STATE_WANT_TO_SEND_HEAD)
316 {
317 FD_SET(tp->networkdesc, &wfds);
318 if (tp->networkdesc > wnum)
319 wnum = tp->networkdesc;
320 }
321
322 if (tp->state == STATE_WANT_TO_READ_FILE)
323 {
324 FD_SET(tp->filedesc, &rfds);
325 if (tp->filedesc > rnum)
326 rnum = tp->filedesc;
327 }
328
329 if (tp->state == STATE_WANT_TO_SEND_FILE)
330 {
331 FD_SET(tp->networkdesc, &wfds);
332 if (tp->networkdesc > wnum)
333 wnum = tp->networkdesc;
334 }
335
336 #if defined(CONFIG_HTTP_DIRECTORIES)
337 if (tp->state == STATE_DOING_DIR)
338 {
339 FD_SET(tp->networkdesc, &wfds);
340 if (tp->networkdesc > wnum)
341 wnum = tp->networkdesc;
342 }
343 #endif
344 tp = tp->next;
345 }
346
347 active = select(wnum > rnum ? wnum+1 : rnum+1,
348 rnum != -1 ? &rfds : NULL,
349 wnum != -1 ? &wfds : NULL,
350 NULL, usedconns ? &tv : NULL);
351
352 /* timeout? */
353 if (active == 0)
354 continue;
355
356 /* New connection? */
357 sp = servers;
358 while (active > 0 && sp != NULL)
359 {
360 if (FD_ISSET(sp->sd, &rfds))
361 {
362 handlenewconnection(sp->sd, sp->is_ssl);
363 active--;
364 }
365
366 sp = sp->next;
367 }
368
369 /* Handle the established sockets */
370 tp = usedconns;
371
372 while (active > 0 && tp != NULL)
373 {
374 to = tp;
375 tp = tp->next;
376
377 if (to->state == STATE_WANT_TO_READ_HEAD &&
378 FD_ISSET(to->networkdesc, &rfds))
379 {
380 active--;
381 #if defined(CONFIG_HTTP_HAS_CGI)
382 if (to->post_state)
383 read_post_data(to);
384 else
385 #endif
386 procreadhead(to);
387 }
388
389 if (to->state == STATE_WANT_TO_SEND_HEAD &&
390 FD_ISSET(to->networkdesc, &wfds))
391 {
392 active--;
393 procsendhead(to);
394 }
395
396 if (to->state == STATE_WANT_TO_READ_FILE &&
397 FD_ISSET(to->filedesc, &rfds))
398 {
399 active--;
400 procreadfile(to);
401 }
402
403 if (to->state == STATE_WANT_TO_SEND_FILE &&
404 FD_ISSET(to->networkdesc, &wfds))
405 {
406 active--;
407 procsendfile(to);
408 }
409
410 #if defined(CONFIG_HTTP_DIRECTORIES)
411 if (to->state == STATE_DOING_DIR &&
412 FD_ISSET(to->networkdesc, &wfds))
413 {
414 active--;
415 procdodir(to);
416 }
417 #endif
418 }
419 }
420
421 return 0;
422 }
423
424 #if defined(CONFIG_HTTP_HAS_CGI)
addcgiext(const char * cgi_exts)425 static void addcgiext(const char *cgi_exts)
426 {
427 char *cp = strdup(cgi_exts);
428
429 /* extenstions are comma separated */
430 do
431 {
432 struct cgiextstruct *ex = (struct cgiextstruct *)
433 malloc(sizeof(struct cgiextstruct));
434 ex->ext = cp;
435 ex->next = cgiexts;
436 cgiexts = ex;
437 if ((cp = strchr(cp, ',')) != NULL)
438 *cp++ = 0;
439 } while (cp != NULL);
440 }
441 #endif
442
addtoservers(int sd)443 static void addtoservers(int sd)
444 {
445 struct serverstruct *tp = (struct serverstruct *)
446 calloc(1, sizeof(struct serverstruct));
447 tp->next = servers;
448 tp->sd = sd;
449 servers = tp;
450 }
451
452 #ifdef HAVE_IPV6
handlenewconnection(int listenfd,int is_ssl)453 static void handlenewconnection(int listenfd, int is_ssl)
454 {
455 struct sockaddr_in6 their_addr;
456 socklen_t tp = sizeof(their_addr);
457 char ipbuf[100];
458 int connfd = accept(listenfd, (struct sockaddr *)&their_addr, &tp);
459
460 if (tp == sizeof(struct sockaddr_in6))
461 inet_ntop(AF_INET6, &their_addr.sin6_addr, ipbuf, sizeof(ipbuf));
462 else if (tp == sizeof(struct sockaddr_in))
463 inet_ntop(AF_INET, &(((struct sockaddr_in *)&their_addr)->sin_addr),
464 ipbuf, sizeof(ipbuf));
465 else
466 *ipbuf = '\0';
467
468 if (connfd != -1) /* check for error condition */
469 addconnection(connfd, ipbuf, is_ssl);
470 }
471
472 #else
handlenewconnection(int listenfd,int is_ssl)473 static void handlenewconnection(int listenfd, int is_ssl)
474 {
475 struct sockaddr_in their_addr;
476 socklen_t tp = sizeof(struct sockaddr_in);
477 int connfd = accept(listenfd, (struct sockaddr *)&their_addr, &tp);
478 addconnection(connfd, inet_ntoa(their_addr.sin_addr), is_ssl);
479 }
480 #endif
481
openlistener(char * address,int port)482 static int openlistener(char *address, int port)
483 {
484 int sd;
485 #ifdef WIN32
486 char tp = 1;
487 #else
488 int tp = 1;
489 #endif
490 #ifndef HAVE_IPV6
491 struct sockaddr_in my_addr;
492
493 if ((sd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
494 return -1;
495
496 memset(&my_addr, 0, sizeof(my_addr));
497 my_addr.sin_family = AF_INET;
498 my_addr.sin_port = htons((short)port);
499 my_addr.sin_addr.s_addr = address == NULL ?
500 INADDR_ANY : inet_addr(address);
501 #else
502 struct sockaddr_in6 my_addr;
503
504 if ((sd = socket(AF_INET6, SOCK_STREAM, 0)) == -1)
505 return -1;
506
507 my_addr.sin6_family = AF_INET6;
508 my_addr.sin6_port = htons(port);
509
510 if (address == NULL)
511 my_addr.sin6_addr = in6addr_any;
512 else
513 inet_pton(AF_INET6, address, &my_addr.sin6_addr);
514 #endif
515
516 setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &tp, sizeof(tp));
517 if (bind(sd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1)
518 {
519 close(sd);
520 return -1;
521 }
522
523 listen(sd, BACKLOG);
524 return sd;
525 }
526
527 /* Wrapper function for strncpy() that guarantees
528 a null-terminated string. This is to avoid any possible
529 issues due to strncpy()'s behaviour.
530 */
my_strncpy(char * dest,const char * src,size_t n)531 char *my_strncpy(char *dest, const char *src, size_t n)
532 {
533 strncpy(dest, src, n);
534 dest[n-1] = '\0';
535 return dest;
536 }
537
isdir(const char * tpbuf)538 int isdir(const char *tpbuf)
539 {
540 struct stat st;
541 char path[MAXREQUESTLENGTH];
542 strcpy(path, tpbuf);
543
544 #ifdef WIN32 /* win32 stat() can't handle trailing '\' */
545 if (path[strlen(path)-1] == '\\')
546 path[strlen(path)-1] = 0;
547 #endif
548
549 if (stat(path, &st) == -1)
550 return 0;
551
552 if ((st.st_mode & S_IFMT) == S_IFDIR)
553 return 1;
554
555 return 0;
556 }
557
addconnection(int sd,char * ip,int is_ssl)558 static void addconnection(int sd, char *ip, int is_ssl)
559 {
560 struct connstruct *tp;
561
562 /* Get ourselves a connstruct */
563 if (freeconns == NULL)
564 tp = (struct connstruct *)calloc(1, sizeof(struct connstruct));
565 else
566 {
567 tp = freeconns;
568 freeconns = tp->next;
569 }
570
571 /* Attach it to the used list */
572 tp->next = usedconns;
573 usedconns = tp;
574 tp->networkdesc = sd;
575
576 if (is_ssl)
577 tp->ssl = ssl_server_new(servers->ssl_ctx, sd);
578
579 tp->is_ssl = is_ssl;
580 tp->filedesc = -1;
581 #if defined(CONFIG_HTTP_HAS_DIRECTORIES)
582 tp->dirp = NULL;
583 #endif
584 *tp->actualfile = '\0';
585 *tp->filereq = '\0';
586 tp->state = STATE_WANT_TO_READ_HEAD;
587 tp->reqtype = TYPE_GET;
588 tp->close_when_done = 0;
589 tp->timeout = time(NULL) + CONFIG_HTTP_TIMEOUT;
590 #if defined(CONFIG_HTTP_HAS_CGI)
591 strcpy(tp->remote_addr, ip);
592 #endif
593 }
594
removeconnection(struct connstruct * cn)595 void removeconnection(struct connstruct *cn)
596 {
597 struct connstruct *tp;
598 int shouldret = 0;
599
600 tp = usedconns;
601
602 if (tp == NULL || cn == NULL)
603 shouldret = 1;
604 else if (tp == cn)
605 usedconns = tp->next;
606 else
607 {
608 while (tp != NULL)
609 {
610 if (tp->next == cn)
611 {
612 tp->next = (tp->next)->next;
613 shouldret = 0;
614 break;
615 }
616
617 tp = tp->next;
618 shouldret = 1;
619 }
620 }
621
622 if (shouldret)
623 return;
624
625 /* If we did, add it to the free list */
626 cn->next = freeconns;
627 freeconns = cn;
628
629 /* Close it all down */
630 if (cn->networkdesc != -1)
631 {
632 if (cn->is_ssl)
633 {
634 ssl_free(cn->ssl);
635 cn->ssl = NULL;
636 }
637
638 #ifndef WIN32
639 shutdown(cn->networkdesc, SHUT_WR);
640 #endif
641 SOCKET_CLOSE(cn->networkdesc);
642 }
643
644 if (cn->filedesc != -1)
645 close(cn->filedesc);
646
647 #if defined(CONFIG_HTTP_HAS_DIRECTORIES)
648 if (cn->dirp != NULL)
649 #ifdef WIN32
650 FindClose(cn->dirp);
651 #else
652 closedir(cn->dirp);
653 #endif
654 #endif
655 }
656
657 /*
658 * Change directories one way or the other.
659 */
660
ax_chdir(void)661 static void ax_chdir(void)
662 {
663 if (chdir(webroot))
664 {
665 #ifdef CONFIG_HTTP_VERBOSE
666 fprintf(stderr, "'%s' is not a directory\n", webroot);
667 #endif
668 exit(1);
669 }
670 }
671
672