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