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