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 <stdlib.h>
33 #include <ctype.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <time.h>
38 #include <string.h>
39 #include "axhttp.h"
40 
41 #define HTTP_VERSION        "HTTP/1.1"
42 
43 static const char * index_file = "index.html";
44 static const char * rfc1123_format = "%a, %d %b %Y %H:%M:%S GMT";
45 
46 static int special_read(struct connstruct *cn, void *buf, size_t count);
47 static int special_write(struct connstruct *cn,
48                                         const char *buf, size_t count);
49 static void send_error(struct connstruct *cn, int err);
50 static int hexit(char c);
51 static void urldecode(char *buf);
52 static void buildactualfile(struct connstruct *cn);
53 static int sanitizefile(const char *buf);
54 static int sanitizehost(char *buf);
55 static int htaccess_check(struct connstruct *cn);
56 static const char *getmimetype(const char *name);
57 
58 #if defined(CONFIG_HTTP_DIRECTORIES)
59 static void urlencode(const uint8_t *s, char *t);
60 static void procdirlisting(struct connstruct *cn);
61 #endif
62 #if defined(CONFIG_HTTP_HAS_CGI)
63 static void proccgi(struct connstruct *cn);
64 static void decode_path_info(struct connstruct *cn, char *path_info);
65 static int init_read_post_data(char *buf, char *data, struct connstruct *cn, int old_rv);
66 #endif
67 #ifdef CONFIG_HTTP_HAS_AUTHORIZATION
68 static int auth_check(struct connstruct *cn);
69 #endif
70 
71 #if AXDEBUG
72 #define AXDEBUGSTART \
73 	{ \
74 		FILE *axdout; \
75 		axdout = fopen("/var/log/axdebug", "a"); \
76 
77 #define AXDEBUGEND \
78 		fclose(axdout); \
79 	}
80 #else /* AXDEBUG */
81 #define AXDEBUGSTART
82 #define AXDEBUGEND
83 #endif /* AXDEBUG */
84 
85 /* Returns 1 if elems should continue being read, 0 otherwise */
procheadelem(struct connstruct * cn,char * buf)86 static int procheadelem(struct connstruct *cn, char *buf)
87 {
88     char *delim, *value;
89 
90     if ((delim = strchr(buf, ' ')) == NULL)
91         return 0;
92 
93     *delim = 0;
94     value = delim+1;
95 
96     if (strcmp(buf, "GET") == 0 || strcmp(buf, "HEAD") == 0 ||
97                                             strcmp(buf, "POST") == 0)
98     {
99         if (buf[0] == 'H')
100             cn->reqtype = TYPE_HEAD;
101         else if (buf[0] == 'P')
102             cn->reqtype = TYPE_POST;
103 
104         if ((delim = strchr(value, ' ')) == NULL)       /* expect HTTP type */
105             return 0;
106 
107         *delim++ = 0;
108         urldecode(value);
109 
110         if (sanitizefile(value) == 0)
111         {
112             send_error(cn, 403);
113             return 0;
114         }
115 
116 #if defined(CONFIG_HTTP_HAS_CGI)
117         decode_path_info(cn, value);
118 #else
119         my_strncpy(cn->filereq, value, MAXREQUESTLENGTH);
120 #endif
121         cn->if_modified_since = -1;
122         if (strcmp(delim, "HTTP/1.0") == 0) /* v1.0 HTTP? */
123             cn->is_v1_0 = 1;
124     }
125     else if (strcasecmp(buf, "Host:") == 0)
126     {
127         if (sanitizehost(value) == 0)
128         {
129             removeconnection(cn);
130             return 0;
131         }
132 
133         my_strncpy(cn->server_name, value, MAXREQUESTLENGTH);
134     }
135     else if (strcasecmp(buf, "Connection:") == 0 && strcmp(value, "close") == 0)
136     {
137         cn->close_when_done = 1;
138     }
139     else if (strcasecmp(buf, "If-Modified-Since:") == 0)
140     {
141         cn->if_modified_since = tdate_parse(value);
142     }
143     else if (strcasecmp(buf, "Expect:") == 0)
144     {
145 		/* supposed to be safe to ignore 100-continue */
146 		if (strcasecmp(value, "100-continue") != 0) {
147 			send_error(cn, 417); /* expectation failed */
148 			return 0;
149 		}
150     }
151 #ifdef CONFIG_HTTP_HAS_AUTHORIZATION
152     else if (strcasecmp(buf, "Authorization:") == 0 &&
153                                     strncmp(value, "Basic ", 6) == 0)
154     {
155         int size = sizeof(cn->authorization);
156         if (base64_decode(&value[6], strlen(&value[6]),
157                                         (uint8_t *)cn->authorization, &size))
158             cn->authorization[0] = 0;   /* error */
159         else
160             cn->authorization[size] = 0;
161     }
162 #endif
163 #if defined(CONFIG_HTTP_HAS_CGI)
164     else if (strcasecmp(buf, "Content-Length:") == 0)
165     {
166         sscanf(value, "%d", &cn->content_length);
167     }
168     else if (strcasecmp(buf, "Content-Type:") == 0)
169     {
170         my_strncpy(cn->cgicontenttype, value, MAXREQUESTLENGTH);
171     }
172     else if (strcasecmp(buf, "Cookie:") == 0)
173     {
174         my_strncpy(cn->cookie, value, MAXREQUESTLENGTH);
175     }
176 #endif
177 
178     return 1;
179 }
180 
181 #if defined(CONFIG_HTTP_DIRECTORIES)
procdirlisting(struct connstruct * cn)182 static void procdirlisting(struct connstruct *cn)
183 {
184     char buf[MAXREQUESTLENGTH];
185     char actualfile[1024];
186 
187     if (cn->reqtype == TYPE_HEAD)
188     {
189         snprintf(buf, sizeof(buf), HTTP_VERSION
190                 " 200 OK\nContent-Type: text/html\n\n");
191         if (write(cn->networkdesc, buf, strlen(buf)) < 0)
192         {
193             printf("procdirlisting: could not write");
194             TTY_FLUSH();
195         }
196 
197         removeconnection(cn);
198         return;
199     }
200 
201     strcpy(actualfile, cn->actualfile);
202 
203 #ifdef WIN32
204     strcat(actualfile, "*");
205     cn->dirp = FindFirstFile(actualfile, &cn->file_data);
206 
207     if (cn->dirp == INVALID_HANDLE_VALUE)
208     {
209         send_error(cn, 404);
210         return;
211     }
212 #else
213     if ((cn->dirp = opendir(actualfile)) == NULL)
214     {
215         send_error(cn, 404);
216         return;
217     }
218 #endif
219 
220     snprintf(buf, sizeof(buf), HTTP_VERSION
221             " 200 OK\nContent-Type: text/html\n\n"
222             "<html><body>\n<title>Directory Listing</title>\n"
223             "<h3>Directory listing of %s://%s%s</h3><br />\n",
224             cn->is_ssl ? "https" : "http", cn->server_name, cn->filereq);
225     special_write(cn, buf, strlen(buf));
226     cn->state = STATE_DOING_DIR;
227 }
228 
procdodir(struct connstruct * cn)229 void procdodir(struct connstruct *cn)
230 {
231 #ifndef WIN32
232     struct dirent *dp;
233 #endif
234     char buf[MAXREQUESTLENGTH];
235     char encbuf[1024];
236     char *file;
237 
238     do
239     {
240        buf[0] = 0;
241 
242 #ifdef WIN32
243         if (!FindNextFile(cn->dirp, &cn->file_data))
244 #else
245         if ((dp = readdir(cn->dirp)) == NULL)
246 #endif
247         {
248             snprintf(buf, sizeof(buf), "</body></html>\n");
249             special_write(cn, buf, strlen(buf));
250             removeconnection(cn);
251 #ifndef WIN32
252             closedir(cn->dirp);
253 #endif
254             return;
255         }
256 
257 #ifdef WIN32
258         file = cn->file_data.cFileName;
259 #else
260         file = dp->d_name;
261 #endif
262 
263         /* if no index file, don't display the ".." directory */
264         if (cn->filereq[0] == '/' && cn->filereq[1] == '\0' &&
265                 strcmp(file, "..") == 0)
266             continue;
267 
268         /* don't display files beginning with "." */
269         if (file[0] == '.' && file[1] != '.')
270             continue;
271 
272         /* make sure a '/' is at the end of a directory */
273         if (cn->filereq[strlen(cn->filereq)-1] != '/')
274             strcat(cn->filereq, "/");
275 
276         /* see if the dir + file is another directory */
277         snprintf(buf, sizeof(buf), "%s%s", cn->actualfile, file);
278         if (isdir(buf))
279             strcat(file, "/");
280 
281         urlencode((uint8_t *)file, encbuf);
282         snprintf(buf, sizeof(buf), "<a href=\"%s%s\">%s</a><br />\n",
283                 cn->filereq, encbuf, file);
284     } while (special_write(cn, buf, strlen(buf)));
285 }
286 
287 /* Encode funny chars -> %xx in newly allocated storage */
288 /* (preserves '/' !) */
urlencode(const uint8_t * s,char * t)289 static void urlencode(const uint8_t *s, char *t)
290 {
291     const uint8_t *p = s;
292     char *tp = t;
293 
294     for (; *p; p++)
295     {
296         if ((*p > 0x00 && *p < ',') ||
297                 (*p > '9' && *p < 'A') ||
298                 (*p > 'Z' && *p < '_') ||
299                 (*p > '_' && *p < 'a') ||
300                 (*p > 'z' && *p < 0xA1))
301         {
302             sprintf((char *)tp, "%%%02X", *p);
303             tp += 3;
304         }
305         else
306         {
307             *tp = *p;
308             tp++;
309         }
310     }
311 
312     *tp='\0';
313 }
314 
315 #endif
316 
procreadhead(struct connstruct * cn)317 void procreadhead(struct connstruct *cn)
318 {
319     char buf[MAXREADLENGTH], *tp, *next;
320     int rv;
321 
322     memset(buf, 0, sizeof(buf));
323     rv = special_read(cn, buf, sizeof(buf)-1);
324     if (rv <= 0)
325     {
326         if (rv < 0 || !cn->is_ssl) /* really dead? */
327             removeconnection(cn);
328         return;
329     }
330 
331     buf[rv] = '\0';
332     next = tp = buf;
333 
334 #ifdef CONFIG_HTTP_HAS_AUTHORIZATION
335     cn->authorization[0] = 0;
336 #endif
337 
338     /* Split up lines and send to procheadelem() */
339     while (*next != '\0')
340     {
341         /* If we have a blank line, advance to next stage */
342         if (*next == '\r' || *next == '\n')
343         {
344 #if defined(CONFIG_HTTP_HAS_CGI)
345             if (cn->reqtype == TYPE_POST && cn->content_length > 0)
346             {
347                 if (init_read_post_data(buf, next, cn, rv) == 0)
348                     return;
349             }
350 #endif
351 
352             buildactualfile(cn);
353             cn->state = STATE_WANT_TO_SEND_HEAD;
354             return;
355         }
356 
357         while (*next != '\r' && *next != '\n' && *next != '\0')
358             next++;
359 
360         if (*next == '\r')
361         {
362             *next = '\0';
363             next += 2;
364         }
365         else if (*next == '\n')
366             *next++ = '\0';
367 
368         if (procheadelem(cn, tp) == 0)
369             return;
370 
371         tp = next;
372     }
373 }
374 
375 /* In this function we assume that the file has been checked for
376  * maliciousness (".."s, etc) and has been decoded
377  */
procsendhead(struct connstruct * cn)378 void procsendhead(struct connstruct *cn)
379 {
380     char buf[MAXREQUESTLENGTH];
381     struct stat stbuf;
382     time_t t_time;
383     struct tm *ptm;
384     char date[32];
385     char last_modified[32];
386     char expires[32];
387     int file_exists;
388 
389     /* are we trying to access a file over the HTTP connection instead of a
390      * HTTPS connection? Or is this directory disabled? */
391     if (htaccess_check(cn))
392     {
393         send_error(cn, 403);
394         return;
395     }
396 
397 #ifdef CONFIG_HTTP_HAS_AUTHORIZATION
398     if (auth_check(cn))     /* see if there is a '.htpasswd' file */
399     {
400 #ifdef CONFIG_HTTP_VERBOSE
401         printf("axhttpd: access to %s denied\n", cn->filereq); TTY_FLUSH();
402 #endif
403         removeconnection(cn);
404         return;
405     }
406 #endif
407 
408     file_exists = stat(cn->actualfile, &stbuf);
409 
410 #if defined(CONFIG_HTTP_HAS_CGI)
411     if (file_exists != -1 && cn->is_cgi)
412     {
413         proccgi(cn);
414         return;
415     }
416 #endif
417 
418     /* look for "index.html"? */
419     if (isdir(cn->actualfile))
420     {
421         char tbuf[MAXREQUESTLENGTH];
422         snprintf(tbuf, MAXREQUESTLENGTH, "%s%s", cn->actualfile, index_file);
423 
424         if ((file_exists = stat(tbuf, &stbuf)) != -1)
425             my_strncpy(cn->actualfile, tbuf, MAXREQUESTLENGTH);
426         else
427         {
428 #if defined(CONFIG_HTTP_DIRECTORIES)
429             /* If not, we do a directory listing of it */
430             procdirlisting(cn);
431 #else
432             send_error(cn, 404);
433 #endif
434             return;
435         }
436     }
437 
438     if (file_exists == -1)
439     {
440         send_error(cn, 404);
441         return;
442     }
443 
444 
445     time(&t_time);
446     ptm = gmtime(&t_time);
447     strftime(date, sizeof(date), rfc1123_format, ptm);
448 
449     /* has the file been read before? */
450     if (cn->if_modified_since != -1)
451 
452     {
453         ptm = gmtime(&stbuf.st_mtime);
454         t_time = mktime(ptm);
455 
456         if (cn->if_modified_since >= t_time)
457         {
458             snprintf(buf, sizeof(buf), HTTP_VERSION" 304 Not Modified\nServer: "
459                 "%s\nDate: %s\n\n", server_version, date);
460             special_write(cn, buf, strlen(buf));
461             cn->state = STATE_WANT_TO_READ_HEAD;
462             return;
463         }
464     }
465 
466     if (cn->reqtype == TYPE_HEAD)
467     {
468         removeconnection(cn);
469         return;
470     }
471     else
472     {
473         int flags = O_RDONLY;
474 #if defined(WIN32) || defined(CONFIG_PLATFORM_CYGWIN)
475         flags |= O_BINARY;
476 #endif
477         cn->filedesc = open(cn->actualfile, flags);
478 
479         if (cn->filedesc < 0)
480         {
481             send_error(cn, 404);
482             return;
483         }
484 
485         ptm = gmtime(&stbuf.st_mtime);
486         strftime(last_modified, sizeof(last_modified), rfc1123_format, ptm);
487         t_time += CONFIG_HTTP_TIMEOUT;
488         ptm = gmtime(&t_time);
489         strftime(expires, sizeof(expires), rfc1123_format, ptm);
490 
491         snprintf(buf, sizeof(buf), HTTP_VERSION" 200 OK\nServer: %s\n"
492             "Content-Type: %s\nContent-Length: %ld\n"
493             "Date: %s\nLast-Modified: %s\nExpires: %s\n\n", server_version,
494             getmimetype(cn->actualfile), (long) stbuf.st_size,
495             date, last_modified, expires);
496 
497         special_write(cn, buf, strlen(buf));
498 
499 #ifdef CONFIG_HTTP_VERBOSE
500         printf("axhttpd: %s:/%s\n", cn->is_ssl ? "https" : "http", cn->filereq);
501         TTY_FLUSH();
502 #endif
503 
504 #ifdef WIN32
505         for (;;)
506         {
507             procreadfile(cn);
508             if (cn->filedesc == -1)
509                 break;
510 
511             do
512             {
513                 procsendfile(cn);
514             } while (cn->state != STATE_WANT_TO_READ_FILE);
515         }
516 #else
517         cn->state = STATE_WANT_TO_READ_FILE;
518 #endif
519     }
520 }
521 
procreadfile(struct connstruct * cn)522 void procreadfile(struct connstruct *cn)
523 {
524     int rv = read(cn->filedesc, cn->databuf, BLOCKSIZE);
525 
526     if (rv <= 0)
527     {
528         close(cn->filedesc);
529         cn->filedesc = -1;
530 
531         if (cn->close_when_done)        /* close immediately */
532             removeconnection(cn);
533         else
534         {
535             if (cn->is_v1_0)    /* die now */
536                 removeconnection(cn);
537             else                /* keep socket open - HTTP 1.1 */
538             {
539                 cn->state = STATE_WANT_TO_READ_HEAD;
540                 cn->numbytes = 0;
541             }
542         }
543 
544         return;
545     }
546 
547     cn->numbytes = rv;
548     cn->state = STATE_WANT_TO_SEND_FILE;
549 }
550 
procsendfile(struct connstruct * cn)551 void procsendfile(struct connstruct *cn)
552 {
553     int rv = special_write(cn, cn->databuf, cn->numbytes);
554 
555     if (rv < 0)
556         removeconnection(cn);
557     else if (rv == cn->numbytes)
558     {
559         cn->state = STATE_WANT_TO_READ_FILE;
560     }
561     else if (rv == 0)
562     {
563         /* Do nothing */
564     }
565     else
566     {
567         memmove(cn->databuf, cn->databuf + rv, cn->numbytes - rv);
568         cn->numbytes -= rv;
569     }
570 }
571 
572 #if defined(CONFIG_HTTP_HAS_CGI)
573 /* Should this be a bit more dynamic? It would mean more calls to malloc etc */
574 #define CGI_ARG_SIZE        17
575 
proccgi(struct connstruct * cn)576 static void proccgi(struct connstruct *cn)
577 {
578     int tpipe[2], spipe[2];
579     char *myargs[3];
580     char cgienv[CGI_ARG_SIZE][MAXREQUESTLENGTH];
581     char * cgiptr[CGI_ARG_SIZE+4];
582     const char *type = "HEAD";
583     int cgi_index = 0, i;
584     pid_t pid;
585 #ifdef WIN32
586     int tmp_stdout;
587 #endif
588 
589     snprintf(cgienv[0], MAXREQUESTLENGTH,
590             HTTP_VERSION" 200 OK\nServer: %s\n%s",
591             server_version, (cn->reqtype == TYPE_HEAD) ? "\n" : "");
592     special_write(cn, cgienv[0], strlen(cgienv[0]));
593 
594     if (cn->reqtype == TYPE_HEAD)
595     {
596         removeconnection(cn);
597         return;
598     }
599 
600 #ifdef CONFIG_HTTP_VERBOSE
601     printf("[CGI]: %s:/%s\n", cn->is_ssl ? "https" : "http", cn->filereq);
602     TTY_FLUSH();
603 #endif
604 
605     /* win32 cgi is a bit too painful */
606 #ifndef WIN32
607 	/* set up pipe that is used for sending POST query data to CGI script*/
608     if (cn->reqtype == TYPE_POST)
609     {
610         if (pipe(spipe) == -1)
611         {
612             printf("[CGI]: could not create pipe");
613             TTY_FLUSH();
614             return;
615         }
616     }
617 
618 	if (pipe(tpipe) == -1)
619     {
620         printf("[CGI]: could not create pipe");
621         TTY_FLUSH();
622         return;
623     }
624 
625     /*
626      * use vfork() instead of fork() for performance
627      */
628     if ((pid = vfork()) > 0)  /* parent */
629     {
630         /* Send POST query data to CGI script */
631         if ((cn->reqtype == TYPE_POST) && (cn->content_length > 0))
632         {
633             if (write(spipe[1], cn->post_data, cn->content_length) == -1)
634             {
635                 printf("[CGI]: could write to pipe");
636                 TTY_FLUSH();
637                 return;
638             }
639 
640             close(spipe[0]);
641             close(spipe[1]);
642 
643             /* free the memory that is allocated in read_post_data() */
644             free(cn->post_data);
645             cn->post_data = NULL;
646         }
647 
648         /* Close the write descriptor */
649         close(tpipe[1]);
650         cn->filedesc = tpipe[0];
651         cn->state = STATE_WANT_TO_READ_FILE;
652         cn->close_when_done = 1;
653         return;
654     }
655 
656     if (pid < 0) /* vfork failed */
657         exit(1);
658 
659     /* The problem child... */
660 
661     /* Our stdout/stderr goes to the socket */
662     dup2(tpipe[1], 1);
663     dup2(tpipe[1], 2);
664     close(tpipe[0]);
665     close(tpipe[1]);
666 
667     /* If it was a POST request, send the socket data to our stdin */
668     if (cn->reqtype == TYPE_POST)  {
669         dup2(spipe[0], 0);
670         close(spipe[0]);
671         close(spipe[1]);
672     } else    /* Otherwise we can shutdown the read side of the sock */
673         shutdown(cn->networkdesc, 0);
674 
675     myargs[0] = CONFIG_HTTP_CGI_LAUNCHER;
676     myargs[1] = cn->actualfile;
677     myargs[2] = NULL;
678 
679     /*
680      * set the cgi args. A url is defined by:
681      * http://$SERVER_NAME:$SERVER_PORT$SCRIPT_NAME$PATH_INFO?$QUERY_STRING
682      * TODO: other CGI parameters?
683      */
684     sprintf(cgienv[cgi_index++], "SERVER_SOFTWARE=%s", server_version);
685     strcpy(cgienv[cgi_index++], "DOCUMENT_ROOT=" CONFIG_HTTP_WEBROOT);
686     snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
687             "SERVER_NAME=%s", cn->server_name);
688     sprintf(cgienv[cgi_index++], "SERVER_PORT=%d",
689             cn->is_ssl ? CONFIG_HTTP_HTTPS_PORT : CONFIG_HTTP_PORT);
690     snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
691             "REQUEST_URI=%s", cn->uri_request);
692     snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
693             "SCRIPT_NAME=%s", cn->filereq);
694     snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
695             "PATH_INFO=%s", cn->uri_path_info);
696     snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
697             "QUERY_STRING=%s", cn->uri_query);
698     snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
699             "REMOTE_ADDR=%s", cn->remote_addr);
700     snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
701             "HTTP_COOKIE=%s", cn->cookie);  /* note: small size */
702 #if defined(CONFIG_HTTP_HAS_AUTHORIZATION)
703     snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
704             "REMOTE_USER=%s", cn->authorization);
705 #endif
706 
707     switch (cn->reqtype)
708     {
709         case TYPE_GET:
710             type = "GET";
711             break;
712 
713 #if defined(CONFIG_HTTP_HAS_CGI)
714         case TYPE_POST:
715             type = "POST";
716             sprintf(cgienv[cgi_index++],
717                         "CONTENT_LENGTH=%d", cn->content_length);
718             snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
719                         "CONTENT_TYPE=%s", cn->cgicontenttype);
720             break;
721 #endif
722     }
723 
724     sprintf(cgienv[cgi_index++], "REQUEST_METHOD=%s", type);
725 
726     if (cn->is_ssl)
727         strcpy(cgienv[cgi_index++], "HTTPS=on");
728 
729     if (cgi_index >= CGI_ARG_SIZE)
730     {
731         printf("Content-type: text/plain\n\nToo many CGI args (%d, %d)\n",
732                 cgi_index, CGI_ARG_SIZE);
733         _exit(1);
734     }
735 
736     /* copy across the pointer indexes */
737     for (i = 0; i < cgi_index; i++)
738         cgiptr[i] = cgienv[i];
739 
740     cgiptr[i++] = "AUTH_TYPE=Basic";
741     cgiptr[i++] = "GATEWAY_INTERFACE=CGI/1.1";
742     cgiptr[i++] = "SERVER_PROTOCOL="HTTP_VERSION;
743     cgiptr[i] = NULL;
744 
745     execve(myargs[0], myargs, cgiptr);
746     printf("Content-type: text/plain\n\nshouldn't get here\n");
747     _exit(1);
748 #endif
749 }
750 
cgi_filetype_match(struct connstruct * cn,const char * fn)751 static char * cgi_filetype_match(struct connstruct *cn, const char *fn)
752 {
753     struct cgiextstruct *tp = cgiexts;
754 
755     while (tp != NULL)
756     {
757         char *t;
758 
759         if ((t = strstr(fn, tp->ext)) != NULL)
760         {
761             t += strlen(tp->ext);
762 
763             if (*t == '/' || *t == '\0')
764                 return t;
765             else
766                 return NULL;
767 
768         }
769 
770         tp = tp->next;
771     }
772 
773     return NULL;
774 }
775 
decode_path_info(struct connstruct * cn,char * path_info)776 static void decode_path_info(struct connstruct *cn, char *path_info)
777 {
778     char *cgi_delim;
779 
780 #if defined(CONFIG_HTTP_HAS_CGI)
781     cn->is_cgi = 0;
782 #endif
783     *cn->uri_request = '\0';
784     *cn->uri_path_info = '\0';
785     *cn->uri_query = '\0';
786 
787     my_strncpy(cn->uri_request, path_info, MAXREQUESTLENGTH);
788 
789     /* query info? */
790     if ((cgi_delim = strchr(path_info, '?')))
791     {
792         *cgi_delim = '\0';
793         my_strncpy(cn->uri_query, cgi_delim+1, MAXREQUESTLENGTH);
794     }
795 
796 #if defined(CONFIG_HTTP_HAS_CGI)
797     if ((cgi_delim = cgi_filetype_match(cn, path_info)) != NULL)
798     {
799         cn->is_cgi = 1;     /* definitely a CGI script */
800 
801         /* path info? */
802         if (*cgi_delim != '\0')
803         {
804             my_strncpy(cn->uri_path_info, cgi_delim, MAXREQUESTLENGTH);
805             *cgi_delim = '\0';
806         }
807     }
808 #endif
809 
810     /* the bit at the start must be the script name */
811     my_strncpy(cn->filereq, path_info, MAXREQUESTLENGTH);
812 }
813 
init_read_post_data(char * buf,char * data,struct connstruct * cn,int old_rv)814 static int init_read_post_data(char *buf, char *data,
815                                 struct connstruct *cn, int old_rv)
816 {
817    char *next = data;
818    int rv = old_rv;
819    char *post_data;
820 
821     /* Too much Post data to send. MAXPOSTDATASIZE should be
822        configured (now it can be changed in the header file) */
823    if (cn->content_length > MAXPOSTDATASIZE)
824    {
825        send_error(cn, 418);
826        return 0;
827    }
828 
829    /* remove CRLF */
830    while ((*next == '\r' || *next == '\n') && (next < &buf[rv]))
831        next++;
832 
833    if (cn->post_data == NULL)
834    {
835        /* Allocate buffer for the POST data that will be used by proccgi
836           to send POST data to the CGI script */
837        cn->post_data = (char *)calloc(1, (cn->content_length + 1));
838    }
839 
840    cn->post_state = 0;
841    cn->post_read = 0;
842    post_data = cn->post_data;
843 
844    while (next < &buf[rv])
845    {
846        /* copy POST data to buffer */
847        *post_data++ = *next++;
848        cn->post_read++;
849        if (cn->post_read == cn->content_length)
850        {
851            /* No more POST data to be copied */
852            *post_data = '\0';
853            return 1;
854        }
855    }
856 
857    /* More POST data has to be read. read_post_data will continue with that */
858    cn->post_state = 1;
859    return 0;
860 }
861 
read_post_data(struct connstruct * cn)862 void read_post_data(struct connstruct *cn)
863 {
864     char buf[MAXREADLENGTH], *next;
865     char *post_data;
866     int rv;
867 
868     memset(buf, 0, sizeof(buf));
869     rv = special_read(cn, buf, sizeof(buf)-1);
870     if (rv <= 0)
871     {
872         if (rv < 0 || !cn->is_ssl) /* really dead? */
873             removeconnection(cn);
874         return;
875     }
876 
877     buf[rv] = '\0';
878     next = buf;
879     post_data = &cn->post_data[cn->post_read];
880 
881     while (next < &buf[rv])
882     {
883         *post_data++ = *next++;
884         cn->post_read++;
885 
886         if (cn->post_read == cn->content_length)
887         {
888             /* No more POST data to be copied */
889             *post_data='\0';
890             cn->post_state = 0;
891             buildactualfile(cn);
892             cn->state = STATE_WANT_TO_SEND_HEAD;
893             return;
894         }
895     }
896 
897     /* More POST data to read */
898 }
899 
900 #endif  /* CONFIG_HTTP_HAS_CGI */
901 
902 /* Decode string %xx -> char (in place) */
urldecode(char * buf)903 static void urldecode(char *buf)
904 {
905     int v;
906     char *p, *s, *w;
907 
908     w = p = buf;
909 
910     while (*p)
911     {
912         v = 0;
913 
914         if (*p == '%')
915         {
916             s = p;
917             s++;
918 
919             if (isxdigit((int) s[0]) && isxdigit((int) s[1]))
920             {
921                 v = hexit(s[0])*16 + hexit(s[1]);
922 
923                 if (v)
924                 {
925                     /* do not decode %00 to null char */
926                     *w = (char)v;
927                     p = &s[1];
928                 }
929             }
930 
931         }
932 
933         if (!v) *w=*p;
934         p++;
935         w++;
936     }
937 
938     *w='\0';
939 }
940 
hexit(char c)941 static int hexit(char c)
942 {
943     if (c >= '0' && c <= '9')
944         return c - '0';
945     else if (c >= 'a' && c <= 'f')
946         return c - 'a' + 10;
947     else if (c >= 'A' && c <= 'F')
948         return c - 'A' + 10;
949     else
950         return 0;
951 }
952 
buildactualfile(struct connstruct * cn)953 static void buildactualfile(struct connstruct *cn)
954 {
955     char *cp;
956     snprintf(cn->actualfile, MAXREQUESTLENGTH, ".%s", cn->filereq);
957 
958 #ifndef WIN32
959     /* Add directory slash if not there */
960     if (isdir(cn->actualfile) &&
961             cn->actualfile[strlen(cn->actualfile)-1] != '/')
962         strcat(cn->actualfile, "/");
963 
964     /* work out the directory name */
965     strncpy(cn->dirname, cn->actualfile, MAXREQUESTLENGTH);
966     if ((cp = strrchr(cn->dirname, '/')) == NULL)
967         cn->dirname[0] = 0;
968     else
969         *cp = 0;
970 #else
971     {
972         char curr_dir[MAXREQUESTLENGTH];
973         char path[MAXREQUESTLENGTH];
974         char *t = cn->actualfile;
975 
976         GetCurrentDirectory(MAXREQUESTLENGTH, curr_dir);
977 
978         /* convert all the forward slashes to back slashes */
979         while ((t = strchr(t, '/')))
980             *t++ = '\\';
981 
982         snprintf(path, MAXREQUESTLENGTH, "%s%s", curr_dir, cn->actualfile);
983         memcpy(cn->actualfile, path, MAXREQUESTLENGTH);
984 
985         /* Add directory slash if not there */
986         if (isdir(cn->actualfile) &&
987                     cn->actualfile[strlen(cn->actualfile)-1] != '\\')
988             strcat(cn->actualfile, "\\");
989 
990         /* work out the directory name */
991         strncpy(cn->dirname, cn->actualfile, MAXREQUESTLENGTH);
992         if ((cp = strrchr(cn->dirname, '\\')) == NULL)
993             cn->dirname[0] = 0;
994         else
995             *cp = 0;
996     }
997 #endif
998 }
999 
sanitizefile(const char * buf)1000 static int sanitizefile(const char *buf)
1001 {
1002     int len, i;
1003 
1004     /* Don't accept anything not starting with a / */
1005     if (*buf != '/')
1006         return 0;
1007 
1008     len = strlen(buf);
1009     for (i = 0; i < len; i++)
1010     {
1011         /* Check for "/." i.e. don't send files starting with a . */
1012         if (buf[i] == '/' && buf[i+1] == '.')
1013             return 0;
1014     }
1015 
1016     return 1;
1017 }
1018 
sanitizehost(char * buf)1019 static int sanitizehost(char *buf)
1020 {
1021     while (*buf != '\0')
1022     {
1023         /* Handle the port */
1024         if (*buf == ':')
1025         {
1026             *buf = '\0';
1027             return 1;
1028         }
1029 
1030         /* Enforce some basic URL rules... */
1031         if ((isalnum((int)(*buf)) == 0 && *buf != '-' && *buf != '.') ||
1032                 (*buf == '.' && *(buf+1) == '.') ||
1033                 (*buf == '.' && *(buf+1) == '-') ||
1034                 (*buf == '-' && *(buf+1) == '.'))
1035             return 0;
1036 
1037         buf++;
1038     }
1039 
1040     return 1;
1041 }
1042 
exist_check(struct connstruct * cn,const char * check_file)1043 static FILE * exist_check(struct connstruct *cn, const char *check_file)
1044 {
1045     char pathname[MAXREQUESTLENGTH];
1046     snprintf(pathname, MAXREQUESTLENGTH, "%s/%s", cn->dirname, check_file);
1047     return fopen(pathname, "r");
1048 }
1049 
1050 #ifdef CONFIG_HTTP_HAS_AUTHORIZATION
send_authenticate(struct connstruct * cn,const char * realm)1051 static void send_authenticate(struct connstruct *cn, const char *realm)
1052 {
1053     char buf[1024];
1054 
1055     snprintf(buf, sizeof(buf), HTTP_VERSION" 401 Unauthorized\n"
1056          "WWW-Authenticate: Basic\n"
1057                  "realm=\"%s\"\n", realm);
1058     special_write(cn, buf, strlen(buf));
1059 }
1060 
check_digest(char * salt,const char * msg_passwd)1061 static int check_digest(char *salt, const char *msg_passwd)
1062 {
1063     uint8_t b256_salt[MAXREQUESTLENGTH];
1064     uint8_t real_passwd[MD5_SIZE];
1065     int salt_size = sizeof(b256_salt);
1066     int password_size = sizeof(real_passwd);
1067     char *b64_passwd;
1068     uint8_t md5_result[MD5_SIZE];
1069     MD5_CTX ctx;
1070 
1071     /* retrieve the salt */
1072     if ((b64_passwd = strchr(salt, '$')) == NULL)
1073         return -1;
1074 
1075     *b64_passwd++ = 0;
1076     if (base64_decode(salt, strlen(salt), b256_salt, &salt_size))
1077         return -1;
1078 
1079     if (base64_decode(b64_passwd, strlen(b64_passwd), real_passwd,
1080                 &password_size))
1081         return -1;
1082 
1083     /* very simple MD5 crypt algorithm, but then the salt we use is large */
1084     MD5_Init(&ctx);
1085     MD5_Update(&ctx, b256_salt, salt_size);           /* process the salt */
1086     MD5_Update(&ctx, (uint8_t *)msg_passwd, strlen(msg_passwd));
1087     MD5_Final(md5_result, &ctx);
1088     return memcmp(md5_result, real_passwd, MD5_SIZE);/* 0 = ok */
1089 }
1090 
auth_check(struct connstruct * cn)1091 static int auth_check(struct connstruct *cn)
1092 {
1093     char line[MAXREQUESTLENGTH];
1094     FILE *fp;
1095     char *cp;
1096 
1097     if ((fp = exist_check(cn, ".htpasswd")) == NULL)
1098         return 0;               /* no .htpasswd file, so let though */
1099 
1100     if (cn->authorization[0] == 0)
1101         goto error;
1102 
1103     /* cn->authorization is in form "username:password" */
1104     if ((cp = strchr(cn->authorization, ':')) == NULL)
1105         goto error;
1106     else
1107         *cp++ = 0;  /* cp becomes the password */
1108 
1109     while (fgets(line, sizeof(line), fp) != NULL)
1110     {
1111         char *b64_file_passwd;
1112         int l = strlen(line);
1113 
1114         /* nuke newline */
1115         if (line[l-1] == '\n')
1116             line[l-1] = 0;
1117 
1118         /* line is form "username:salt(b64)$password(b64)" */
1119         if ((b64_file_passwd = strchr(line, ':')) == NULL)
1120             continue;
1121 
1122         *b64_file_passwd++ = 0;
1123 
1124         if (strcmp(line, cn->authorization)) /* our user? */
1125             continue;
1126 
1127         if (check_digest(b64_file_passwd, cp) == 0)
1128         {
1129             fclose(fp);
1130             return 0;
1131         }
1132     }
1133 
1134 error:
1135     fclose(fp);
1136     send_authenticate(cn, cn->server_name);
1137     return -1;
1138 }
1139 #endif
1140 
htaccess_check(struct connstruct * cn)1141 static int htaccess_check(struct connstruct *cn)
1142 {
1143     char line[MAXREQUESTLENGTH];
1144     FILE *fp;
1145     int ret = 0;
1146 
1147     if ((fp = exist_check(cn, ".htaccess")) == NULL)
1148         return 0;               /* no .htaccess file, so let though */
1149 
1150     while (fgets(line, sizeof(line), fp) != NULL)
1151     {
1152         if (strstr(line, "Deny all") || /* access to this dir denied */
1153                     /* Access will be denied unless SSL is active */
1154                     (!cn->is_ssl && strstr(line, "SSLRequireSSL")) ||
1155                     /* Access will be denied if SSL is active */
1156                     (cn->is_ssl && strstr(line, "SSLDenySSL")))
1157         {
1158             ret = -1;
1159             break;
1160         }
1161     }
1162 
1163     fclose(fp);
1164     return ret;
1165 }
1166 
send_error(struct connstruct * cn,int err)1167 static void send_error(struct connstruct *cn, int err)
1168 {
1169     char buf[MAXREQUESTLENGTH];
1170     char *title;
1171     char *text;
1172 
1173     switch (err)
1174     {
1175         case 403:
1176             title = "Forbidden";
1177             text = "File is protected";
1178 #ifdef CONFIG_HTTP_VERBOSE
1179             printf("axhttpd: access to %s denied\n", cn->filereq); TTY_FLUSH();
1180 #endif
1181             break;
1182 
1183         case 404:
1184             title = "Not Found";
1185             text = title;
1186             break;
1187 
1188         case 418:
1189             title = "POST data size is too large";
1190             text = title;
1191             break;
1192 
1193         default:
1194             title = "Unknown";
1195             text = "Unknown";
1196             break;
1197     }
1198 
1199     snprintf(buf, sizeof(buf), HTTP_VERSION" 200 OK\n"
1200             "Content-Type: text/html\n\n"
1201             "<html><body>\n<title>%s</title>\n"
1202             "<h1>Error %d - %s</h1>\n</body></html>\n",
1203             title, err, text);
1204     special_write(cn, buf, strlen(buf));
1205 
1206 #ifdef CONFIG_HTTP_VERBOSE
1207     printf("axhttpd: http error: %s [%d]\n", title, err); TTY_FLUSH();
1208 #endif
1209     removeconnection(cn);
1210 }
1211 
getmimetype(const char * name)1212 static const char *getmimetype(const char *name)
1213 {
1214     /* only bother with a few mime types - let the browser figure the rest out */
1215     if (strstr(name, ".htm"))
1216         return "text/html";
1217     else if (strstr(name, ".css"))
1218         return "text/css";
1219     else if (strstr(name, ".php"))
1220         return "application/x-http-php";
1221     else
1222         return "application/octet-stream";
1223 }
1224 
special_write(struct connstruct * cn,const char * buf,size_t count)1225 static int special_write(struct connstruct *cn,
1226                                         const char *buf, size_t count)
1227 {
1228     if (cn->is_ssl)
1229     {
1230         SSL *ssl = cn->ssl;
1231         return ssl ? ssl_write(ssl, (uint8_t *)buf, count) : -1;
1232     }
1233     else
1234         return SOCKET_WRITE(cn->networkdesc, buf, count);
1235 }
1236 
special_read(struct connstruct * cn,void * buf,size_t count)1237 static int special_read(struct connstruct *cn, void *buf, size_t count)
1238 {
1239     int res;
1240 
1241     if (cn->is_ssl)
1242     {
1243         uint8_t *read_buf;
1244         if ((res = ssl_read(cn->ssl, &read_buf)) > SSL_OK)
1245         {
1246             memcpy(buf, read_buf, res > (int)count ? count : res);
1247         }
1248     }
1249     else
1250         res = SOCKET_READ(cn->networkdesc, buf, count);
1251 
1252     return res;
1253 }
1254 
1255