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