1254 lines
32 KiB
C
1254 lines
32 KiB
C
/*
|
|
* Copyright (c) Cameron Rich
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* * Neither the name of the axTLS project nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include "axhttp.h"
|
|
|
|
#define HTTP_VERSION "HTTP/1.1"
|
|
|
|
static const char * index_file = "index.html";
|
|
static const char * rfc1123_format = "%a, %d %b %Y %H:%M:%S GMT";
|
|
|
|
static int special_read(struct connstruct *cn, void *buf, size_t count);
|
|
static int special_write(struct connstruct *cn,
|
|
const char *buf, size_t count);
|
|
static void send_error(struct connstruct *cn, int err);
|
|
static int hexit(char c);
|
|
static void urldecode(char *buf);
|
|
static void buildactualfile(struct connstruct *cn);
|
|
static int sanitizefile(const char *buf);
|
|
static int sanitizehost(char *buf);
|
|
static int htaccess_check(struct connstruct *cn);
|
|
static const char *getmimetype(const char *name);
|
|
|
|
#if defined(CONFIG_HTTP_DIRECTORIES)
|
|
static void urlencode(const uint8_t *s, char *t);
|
|
static void procdirlisting(struct connstruct *cn);
|
|
#endif
|
|
#if defined(CONFIG_HTTP_HAS_CGI)
|
|
static void proccgi(struct connstruct *cn);
|
|
static void decode_path_info(struct connstruct *cn, char *path_info);
|
|
static int init_read_post_data(char *buf, char *data, struct connstruct *cn, int old_rv);
|
|
#endif
|
|
#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
|
|
static int auth_check(struct connstruct *cn);
|
|
#endif
|
|
|
|
#if AXDEBUG
|
|
#define AXDEBUGSTART \
|
|
{ \
|
|
FILE *axdout; \
|
|
axdout = fopen("/var/log/axdebug", "a"); \
|
|
|
|
#define AXDEBUGEND \
|
|
fclose(axdout); \
|
|
}
|
|
#else /* AXDEBUG */
|
|
#define AXDEBUGSTART
|
|
#define AXDEBUGEND
|
|
#endif /* AXDEBUG */
|
|
|
|
/* Returns 1 if elems should continue being read, 0 otherwise */
|
|
static int procheadelem(struct connstruct *cn, char *buf)
|
|
{
|
|
char *delim, *value;
|
|
|
|
if ((delim = strchr(buf, ' ')) == NULL)
|
|
return 0;
|
|
|
|
*delim = 0;
|
|
value = delim+1;
|
|
|
|
if (strcmp(buf, "GET") == 0 || strcmp(buf, "HEAD") == 0 ||
|
|
strcmp(buf, "POST") == 0)
|
|
{
|
|
if (buf[0] == 'H')
|
|
cn->reqtype = TYPE_HEAD;
|
|
else if (buf[0] == 'P')
|
|
cn->reqtype = TYPE_POST;
|
|
|
|
if ((delim = strchr(value, ' ')) == NULL) /* expect HTTP type */
|
|
return 0;
|
|
|
|
*delim++ = 0;
|
|
urldecode(value);
|
|
|
|
if (sanitizefile(value) == 0)
|
|
{
|
|
send_error(cn, 403);
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_HTTP_HAS_CGI)
|
|
decode_path_info(cn, value);
|
|
#else
|
|
my_strncpy(cn->filereq, value, MAXREQUESTLENGTH);
|
|
#endif
|
|
cn->if_modified_since = -1;
|
|
if (strcmp(delim, "HTTP/1.0") == 0) /* v1.0 HTTP? */
|
|
cn->is_v1_0 = 1;
|
|
}
|
|
else if (strcasecmp(buf, "Host:") == 0)
|
|
{
|
|
if (sanitizehost(value) == 0)
|
|
{
|
|
removeconnection(cn);
|
|
return 0;
|
|
}
|
|
|
|
my_strncpy(cn->server_name, value, MAXREQUESTLENGTH);
|
|
}
|
|
else if (strcasecmp(buf, "Connection:") == 0 && strcmp(value, "close") == 0)
|
|
{
|
|
cn->close_when_done = 1;
|
|
}
|
|
else if (strcasecmp(buf, "If-Modified-Since:") == 0)
|
|
{
|
|
cn->if_modified_since = tdate_parse(value);
|
|
}
|
|
else if (strcasecmp(buf, "Expect:") == 0)
|
|
{
|
|
/* supposed to be safe to ignore 100-continue */
|
|
if (strcasecmp(value, "100-continue") != 0) {
|
|
send_error(cn, 417); /* expectation failed */
|
|
return 0;
|
|
}
|
|
}
|
|
#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
|
|
else if (strcasecmp(buf, "Authorization:") == 0 &&
|
|
strncmp(value, "Basic ", 6) == 0)
|
|
{
|
|
int size = sizeof(cn->authorization);
|
|
if (base64_decode(&value[6], strlen(&value[6]),
|
|
(uint8_t *)cn->authorization, &size))
|
|
cn->authorization[0] = 0; /* error */
|
|
else
|
|
cn->authorization[size] = 0;
|
|
}
|
|
#endif
|
|
#if defined(CONFIG_HTTP_HAS_CGI)
|
|
else if (strcasecmp(buf, "Content-Length:") == 0)
|
|
{
|
|
sscanf(value, "%d", &cn->content_length);
|
|
}
|
|
else if (strcasecmp(buf, "Content-Type:") == 0)
|
|
{
|
|
my_strncpy(cn->cgicontenttype, value, MAXREQUESTLENGTH);
|
|
}
|
|
else if (strcasecmp(buf, "Cookie:") == 0)
|
|
{
|
|
my_strncpy(cn->cookie, value, MAXREQUESTLENGTH);
|
|
}
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
#if defined(CONFIG_HTTP_DIRECTORIES)
|
|
static void procdirlisting(struct connstruct *cn)
|
|
{
|
|
char buf[MAXREQUESTLENGTH];
|
|
char actualfile[1024];
|
|
|
|
if (cn->reqtype == TYPE_HEAD)
|
|
{
|
|
snprintf(buf, sizeof(buf), HTTP_VERSION
|
|
" 200 OK\nContent-Type: text/html\n\n");
|
|
if (write(cn->networkdesc, buf, strlen(buf)) < 0)
|
|
{
|
|
printf("procdirlisting: could not write");
|
|
TTY_FLUSH();
|
|
}
|
|
|
|
removeconnection(cn);
|
|
return;
|
|
}
|
|
|
|
strcpy(actualfile, cn->actualfile);
|
|
|
|
#ifdef WIN32
|
|
strcat(actualfile, "*");
|
|
cn->dirp = FindFirstFile(actualfile, &cn->file_data);
|
|
|
|
if (cn->dirp == INVALID_HANDLE_VALUE)
|
|
{
|
|
send_error(cn, 404);
|
|
return;
|
|
}
|
|
#else
|
|
if ((cn->dirp = opendir(actualfile)) == NULL)
|
|
{
|
|
send_error(cn, 404);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
snprintf(buf, sizeof(buf), HTTP_VERSION
|
|
" 200 OK\nContent-Type: text/html\n\n"
|
|
"<html><body>\n<title>Directory Listing</title>\n"
|
|
"<h3>Directory listing of %s://%s%s</h3><br />\n",
|
|
cn->is_ssl ? "https" : "http", cn->server_name, cn->filereq);
|
|
special_write(cn, buf, strlen(buf));
|
|
cn->state = STATE_DOING_DIR;
|
|
}
|
|
|
|
void procdodir(struct connstruct *cn)
|
|
{
|
|
#ifndef WIN32
|
|
struct dirent *dp;
|
|
#endif
|
|
char buf[MAXREQUESTLENGTH];
|
|
char encbuf[1024];
|
|
char *file;
|
|
|
|
do
|
|
{
|
|
buf[0] = 0;
|
|
|
|
#ifdef WIN32
|
|
if (!FindNextFile(cn->dirp, &cn->file_data))
|
|
#else
|
|
if ((dp = readdir(cn->dirp)) == NULL)
|
|
#endif
|
|
{
|
|
snprintf(buf, sizeof(buf), "</body></html>\n");
|
|
special_write(cn, buf, strlen(buf));
|
|
removeconnection(cn);
|
|
#ifndef WIN32
|
|
closedir(cn->dirp);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#ifdef WIN32
|
|
file = cn->file_data.cFileName;
|
|
#else
|
|
file = dp->d_name;
|
|
#endif
|
|
|
|
/* if no index file, don't display the ".." directory */
|
|
if (cn->filereq[0] == '/' && cn->filereq[1] == '\0' &&
|
|
strcmp(file, "..") == 0)
|
|
continue;
|
|
|
|
/* don't display files beginning with "." */
|
|
if (file[0] == '.' && file[1] != '.')
|
|
continue;
|
|
|
|
/* make sure a '/' is at the end of a directory */
|
|
if (cn->filereq[strlen(cn->filereq)-1] != '/')
|
|
strcat(cn->filereq, "/");
|
|
|
|
/* see if the dir + file is another directory */
|
|
snprintf(buf, sizeof(buf), "%s%s", cn->actualfile, file);
|
|
if (isdir(buf))
|
|
strcat(file, "/");
|
|
|
|
urlencode((uint8_t *)file, encbuf);
|
|
snprintf(buf, sizeof(buf), "<a href=\"%s%s\">%s</a><br />\n",
|
|
cn->filereq, encbuf, file);
|
|
} while (special_write(cn, buf, strlen(buf)));
|
|
}
|
|
|
|
/* Encode funny chars -> %xx in newly allocated storage */
|
|
/* (preserves '/' !) */
|
|
static void urlencode(const uint8_t *s, char *t)
|
|
{
|
|
const uint8_t *p = s;
|
|
char *tp = t;
|
|
|
|
for (; *p; p++)
|
|
{
|
|
if ((*p > 0x00 && *p < ',') ||
|
|
(*p > '9' && *p < 'A') ||
|
|
(*p > 'Z' && *p < '_') ||
|
|
(*p > '_' && *p < 'a') ||
|
|
(*p > 'z' && *p < 0xA1))
|
|
{
|
|
sprintf((char *)tp, "%%%02X", *p);
|
|
tp += 3;
|
|
}
|
|
else
|
|
{
|
|
*tp = *p;
|
|
tp++;
|
|
}
|
|
}
|
|
|
|
*tp='\0';
|
|
}
|
|
|
|
#endif
|
|
|
|
void procreadhead(struct connstruct *cn)
|
|
{
|
|
char buf[MAXREADLENGTH], *tp, *next;
|
|
int rv;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
rv = special_read(cn, buf, sizeof(buf)-1);
|
|
if (rv <= 0)
|
|
{
|
|
if (rv < 0 || !cn->is_ssl) /* really dead? */
|
|
removeconnection(cn);
|
|
return;
|
|
}
|
|
|
|
buf[rv] = '\0';
|
|
next = tp = buf;
|
|
|
|
#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
|
|
cn->authorization[0] = 0;
|
|
#endif
|
|
|
|
/* Split up lines and send to procheadelem() */
|
|
while (*next != '\0')
|
|
{
|
|
/* If we have a blank line, advance to next stage */
|
|
if (*next == '\r' || *next == '\n')
|
|
{
|
|
#if defined(CONFIG_HTTP_HAS_CGI)
|
|
if (cn->reqtype == TYPE_POST && cn->content_length > 0)
|
|
{
|
|
if (init_read_post_data(buf, next, cn, rv) == 0)
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
buildactualfile(cn);
|
|
cn->state = STATE_WANT_TO_SEND_HEAD;
|
|
return;
|
|
}
|
|
|
|
while (*next != '\r' && *next != '\n' && *next != '\0')
|
|
next++;
|
|
|
|
if (*next == '\r')
|
|
{
|
|
*next = '\0';
|
|
next += 2;
|
|
}
|
|
else if (*next == '\n')
|
|
*next++ = '\0';
|
|
|
|
if (procheadelem(cn, tp) == 0)
|
|
return;
|
|
|
|
tp = next;
|
|
}
|
|
}
|
|
|
|
/* In this function we assume that the file has been checked for
|
|
* maliciousness (".."s, etc) and has been decoded
|
|
*/
|
|
void procsendhead(struct connstruct *cn)
|
|
{
|
|
char buf[MAXREQUESTLENGTH];
|
|
struct stat stbuf;
|
|
time_t t_time;
|
|
struct tm *ptm;
|
|
char date[32];
|
|
char last_modified[32];
|
|
char expires[32];
|
|
int file_exists;
|
|
|
|
/* are we trying to access a file over the HTTP connection instead of a
|
|
* HTTPS connection? Or is this directory disabled? */
|
|
if (htaccess_check(cn))
|
|
{
|
|
send_error(cn, 403);
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
|
|
if (auth_check(cn)) /* see if there is a '.htpasswd' file */
|
|
{
|
|
#ifdef CONFIG_HTTP_VERBOSE
|
|
printf("axhttpd: access to %s denied\n", cn->filereq); TTY_FLUSH();
|
|
#endif
|
|
removeconnection(cn);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
file_exists = stat(cn->actualfile, &stbuf);
|
|
|
|
#if defined(CONFIG_HTTP_HAS_CGI)
|
|
if (file_exists != -1 && cn->is_cgi)
|
|
{
|
|
proccgi(cn);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* look for "index.html"? */
|
|
if (isdir(cn->actualfile))
|
|
{
|
|
char tbuf[MAXREQUESTLENGTH];
|
|
snprintf(tbuf, MAXREQUESTLENGTH, "%s%s", cn->actualfile, index_file);
|
|
|
|
if ((file_exists = stat(tbuf, &stbuf)) != -1)
|
|
my_strncpy(cn->actualfile, tbuf, MAXREQUESTLENGTH);
|
|
else
|
|
{
|
|
#if defined(CONFIG_HTTP_DIRECTORIES)
|
|
/* If not, we do a directory listing of it */
|
|
procdirlisting(cn);
|
|
#else
|
|
send_error(cn, 404);
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (file_exists == -1)
|
|
{
|
|
send_error(cn, 404);
|
|
return;
|
|
}
|
|
|
|
|
|
time(&t_time);
|
|
ptm = gmtime(&t_time);
|
|
strftime(date, sizeof(date), rfc1123_format, ptm);
|
|
|
|
/* has the file been read before? */
|
|
if (cn->if_modified_since != -1)
|
|
|
|
{
|
|
ptm = gmtime(&stbuf.st_mtime);
|
|
t_time = mktime(ptm);
|
|
|
|
if (cn->if_modified_since >= t_time)
|
|
{
|
|
snprintf(buf, sizeof(buf), HTTP_VERSION" 304 Not Modified\nServer: "
|
|
"%s\nDate: %s\n\n", server_version, date);
|
|
special_write(cn, buf, strlen(buf));
|
|
cn->state = STATE_WANT_TO_READ_HEAD;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (cn->reqtype == TYPE_HEAD)
|
|
{
|
|
removeconnection(cn);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int flags = O_RDONLY;
|
|
#if defined(WIN32) || defined(CONFIG_PLATFORM_CYGWIN)
|
|
flags |= O_BINARY;
|
|
#endif
|
|
cn->filedesc = open(cn->actualfile, flags);
|
|
|
|
if (cn->filedesc < 0)
|
|
{
|
|
send_error(cn, 404);
|
|
return;
|
|
}
|
|
|
|
ptm = gmtime(&stbuf.st_mtime);
|
|
strftime(last_modified, sizeof(last_modified), rfc1123_format, ptm);
|
|
t_time += CONFIG_HTTP_TIMEOUT;
|
|
ptm = gmtime(&t_time);
|
|
strftime(expires, sizeof(expires), rfc1123_format, ptm);
|
|
|
|
snprintf(buf, sizeof(buf), HTTP_VERSION" 200 OK\nServer: %s\n"
|
|
"Content-Type: %s\nContent-Length: %ld\n"
|
|
"Date: %s\nLast-Modified: %s\nExpires: %s\n\n", server_version,
|
|
getmimetype(cn->actualfile), (long) stbuf.st_size,
|
|
date, last_modified, expires);
|
|
|
|
special_write(cn, buf, strlen(buf));
|
|
|
|
#ifdef CONFIG_HTTP_VERBOSE
|
|
printf("axhttpd: %s:/%s\n", cn->is_ssl ? "https" : "http", cn->filereq);
|
|
TTY_FLUSH();
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
for (;;)
|
|
{
|
|
procreadfile(cn);
|
|
if (cn->filedesc == -1)
|
|
break;
|
|
|
|
do
|
|
{
|
|
procsendfile(cn);
|
|
} while (cn->state != STATE_WANT_TO_READ_FILE);
|
|
}
|
|
#else
|
|
cn->state = STATE_WANT_TO_READ_FILE;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void procreadfile(struct connstruct *cn)
|
|
{
|
|
int rv = read(cn->filedesc, cn->databuf, BLOCKSIZE);
|
|
|
|
if (rv <= 0)
|
|
{
|
|
close(cn->filedesc);
|
|
cn->filedesc = -1;
|
|
|
|
if (cn->close_when_done) /* close immediately */
|
|
removeconnection(cn);
|
|
else
|
|
{
|
|
if (cn->is_v1_0) /* die now */
|
|
removeconnection(cn);
|
|
else /* keep socket open - HTTP 1.1 */
|
|
{
|
|
cn->state = STATE_WANT_TO_READ_HEAD;
|
|
cn->numbytes = 0;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
cn->numbytes = rv;
|
|
cn->state = STATE_WANT_TO_SEND_FILE;
|
|
}
|
|
|
|
void procsendfile(struct connstruct *cn)
|
|
{
|
|
int rv = special_write(cn, cn->databuf, cn->numbytes);
|
|
|
|
if (rv < 0)
|
|
removeconnection(cn);
|
|
else if (rv == cn->numbytes)
|
|
{
|
|
cn->state = STATE_WANT_TO_READ_FILE;
|
|
}
|
|
else if (rv == 0)
|
|
{
|
|
/* Do nothing */
|
|
}
|
|
else
|
|
{
|
|
memmove(cn->databuf, cn->databuf + rv, cn->numbytes - rv);
|
|
cn->numbytes -= rv;
|
|
}
|
|
}
|
|
|
|
#if defined(CONFIG_HTTP_HAS_CGI)
|
|
/* Should this be a bit more dynamic? It would mean more calls to malloc etc */
|
|
#define CGI_ARG_SIZE 17
|
|
|
|
static void proccgi(struct connstruct *cn)
|
|
{
|
|
int tpipe[2], spipe[2];
|
|
char *myargs[3];
|
|
char cgienv[CGI_ARG_SIZE][MAXREQUESTLENGTH];
|
|
char * cgiptr[CGI_ARG_SIZE+4];
|
|
const char *type = "HEAD";
|
|
int cgi_index = 0, i;
|
|
pid_t pid;
|
|
#ifdef WIN32
|
|
int tmp_stdout;
|
|
#endif
|
|
|
|
snprintf(cgienv[0], MAXREQUESTLENGTH,
|
|
HTTP_VERSION" 200 OK\nServer: %s\n%s",
|
|
server_version, (cn->reqtype == TYPE_HEAD) ? "\n" : "");
|
|
special_write(cn, cgienv[0], strlen(cgienv[0]));
|
|
|
|
if (cn->reqtype == TYPE_HEAD)
|
|
{
|
|
removeconnection(cn);
|
|
return;
|
|
}
|
|
|
|
#ifdef CONFIG_HTTP_VERBOSE
|
|
printf("[CGI]: %s:/%s\n", cn->is_ssl ? "https" : "http", cn->filereq);
|
|
TTY_FLUSH();
|
|
#endif
|
|
|
|
/* win32 cgi is a bit too painful */
|
|
#ifndef WIN32
|
|
/* set up pipe that is used for sending POST query data to CGI script*/
|
|
if (cn->reqtype == TYPE_POST)
|
|
{
|
|
if (pipe(spipe) == -1)
|
|
{
|
|
printf("[CGI]: could not create pipe");
|
|
TTY_FLUSH();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (pipe(tpipe) == -1)
|
|
{
|
|
printf("[CGI]: could not create pipe");
|
|
TTY_FLUSH();
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* use vfork() instead of fork() for performance
|
|
*/
|
|
if ((pid = vfork()) > 0) /* parent */
|
|
{
|
|
/* Send POST query data to CGI script */
|
|
if ((cn->reqtype == TYPE_POST) && (cn->content_length > 0))
|
|
{
|
|
if (write(spipe[1], cn->post_data, cn->content_length) == -1)
|
|
{
|
|
printf("[CGI]: could write to pipe");
|
|
TTY_FLUSH();
|
|
return;
|
|
}
|
|
|
|
close(spipe[0]);
|
|
close(spipe[1]);
|
|
|
|
/* free the memory that is allocated in read_post_data() */
|
|
free(cn->post_data);
|
|
cn->post_data = NULL;
|
|
}
|
|
|
|
/* Close the write descriptor */
|
|
close(tpipe[1]);
|
|
cn->filedesc = tpipe[0];
|
|
cn->state = STATE_WANT_TO_READ_FILE;
|
|
cn->close_when_done = 1;
|
|
return;
|
|
}
|
|
|
|
if (pid < 0) /* vfork failed */
|
|
exit(1);
|
|
|
|
/* The problem child... */
|
|
|
|
/* Our stdout/stderr goes to the socket */
|
|
dup2(tpipe[1], 1);
|
|
dup2(tpipe[1], 2);
|
|
close(tpipe[0]);
|
|
close(tpipe[1]);
|
|
|
|
/* If it was a POST request, send the socket data to our stdin */
|
|
if (cn->reqtype == TYPE_POST) {
|
|
dup2(spipe[0], 0);
|
|
close(spipe[0]);
|
|
close(spipe[1]);
|
|
} else /* Otherwise we can shutdown the read side of the sock */
|
|
shutdown(cn->networkdesc, 0);
|
|
|
|
myargs[0] = CONFIG_HTTP_CGI_LAUNCHER;
|
|
myargs[1] = cn->actualfile;
|
|
myargs[2] = NULL;
|
|
|
|
/*
|
|
* set the cgi args. A url is defined by:
|
|
* http://$SERVER_NAME:$SERVER_PORT$SCRIPT_NAME$PATH_INFO?$QUERY_STRING
|
|
* TODO: other CGI parameters?
|
|
*/
|
|
sprintf(cgienv[cgi_index++], "SERVER_SOFTWARE=%s", server_version);
|
|
strcpy(cgienv[cgi_index++], "DOCUMENT_ROOT=" CONFIG_HTTP_WEBROOT);
|
|
snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
|
|
"SERVER_NAME=%s", cn->server_name);
|
|
sprintf(cgienv[cgi_index++], "SERVER_PORT=%d",
|
|
cn->is_ssl ? CONFIG_HTTP_HTTPS_PORT : CONFIG_HTTP_PORT);
|
|
snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
|
|
"REQUEST_URI=%s", cn->uri_request);
|
|
snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
|
|
"SCRIPT_NAME=%s", cn->filereq);
|
|
snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
|
|
"PATH_INFO=%s", cn->uri_path_info);
|
|
snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
|
|
"QUERY_STRING=%s", cn->uri_query);
|
|
snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
|
|
"REMOTE_ADDR=%s", cn->remote_addr);
|
|
snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
|
|
"HTTP_COOKIE=%s", cn->cookie); /* note: small size */
|
|
#if defined(CONFIG_HTTP_HAS_AUTHORIZATION)
|
|
snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
|
|
"REMOTE_USER=%s", cn->authorization);
|
|
#endif
|
|
|
|
switch (cn->reqtype)
|
|
{
|
|
case TYPE_GET:
|
|
type = "GET";
|
|
break;
|
|
|
|
#if defined(CONFIG_HTTP_HAS_CGI)
|
|
case TYPE_POST:
|
|
type = "POST";
|
|
sprintf(cgienv[cgi_index++],
|
|
"CONTENT_LENGTH=%d", cn->content_length);
|
|
snprintf(cgienv[cgi_index++], MAXREQUESTLENGTH,
|
|
"CONTENT_TYPE=%s", cn->cgicontenttype);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
sprintf(cgienv[cgi_index++], "REQUEST_METHOD=%s", type);
|
|
|
|
if (cn->is_ssl)
|
|
strcpy(cgienv[cgi_index++], "HTTPS=on");
|
|
|
|
if (cgi_index >= CGI_ARG_SIZE)
|
|
{
|
|
printf("Content-type: text/plain\n\nToo many CGI args (%d, %d)\n",
|
|
cgi_index, CGI_ARG_SIZE);
|
|
_exit(1);
|
|
}
|
|
|
|
/* copy across the pointer indexes */
|
|
for (i = 0; i < cgi_index; i++)
|
|
cgiptr[i] = cgienv[i];
|
|
|
|
cgiptr[i++] = "AUTH_TYPE=Basic";
|
|
cgiptr[i++] = "GATEWAY_INTERFACE=CGI/1.1";
|
|
cgiptr[i++] = "SERVER_PROTOCOL="HTTP_VERSION;
|
|
cgiptr[i] = NULL;
|
|
|
|
execve(myargs[0], myargs, cgiptr);
|
|
printf("Content-type: text/plain\n\nshouldn't get here\n");
|
|
_exit(1);
|
|
#endif
|
|
}
|
|
|
|
static char * cgi_filetype_match(struct connstruct *cn, const char *fn)
|
|
{
|
|
struct cgiextstruct *tp = cgiexts;
|
|
|
|
while (tp != NULL)
|
|
{
|
|
char *t;
|
|
|
|
if ((t = strstr(fn, tp->ext)) != NULL)
|
|
{
|
|
t += strlen(tp->ext);
|
|
|
|
if (*t == '/' || *t == '\0')
|
|
return t;
|
|
else
|
|
return NULL;
|
|
|
|
}
|
|
|
|
tp = tp->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void decode_path_info(struct connstruct *cn, char *path_info)
|
|
{
|
|
char *cgi_delim;
|
|
|
|
#if defined(CONFIG_HTTP_HAS_CGI)
|
|
cn->is_cgi = 0;
|
|
#endif
|
|
*cn->uri_request = '\0';
|
|
*cn->uri_path_info = '\0';
|
|
*cn->uri_query = '\0';
|
|
|
|
my_strncpy(cn->uri_request, path_info, MAXREQUESTLENGTH);
|
|
|
|
/* query info? */
|
|
if ((cgi_delim = strchr(path_info, '?')))
|
|
{
|
|
*cgi_delim = '\0';
|
|
my_strncpy(cn->uri_query, cgi_delim+1, MAXREQUESTLENGTH);
|
|
}
|
|
|
|
#if defined(CONFIG_HTTP_HAS_CGI)
|
|
if ((cgi_delim = cgi_filetype_match(cn, path_info)) != NULL)
|
|
{
|
|
cn->is_cgi = 1; /* definitely a CGI script */
|
|
|
|
/* path info? */
|
|
if (*cgi_delim != '\0')
|
|
{
|
|
my_strncpy(cn->uri_path_info, cgi_delim, MAXREQUESTLENGTH);
|
|
*cgi_delim = '\0';
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* the bit at the start must be the script name */
|
|
my_strncpy(cn->filereq, path_info, MAXREQUESTLENGTH);
|
|
}
|
|
|
|
static int init_read_post_data(char *buf, char *data,
|
|
struct connstruct *cn, int old_rv)
|
|
{
|
|
char *next = data;
|
|
int rv = old_rv;
|
|
char *post_data;
|
|
|
|
/* Too much Post data to send. MAXPOSTDATASIZE should be
|
|
configured (now it can be changed in the header file) */
|
|
if (cn->content_length > MAXPOSTDATASIZE)
|
|
{
|
|
send_error(cn, 418);
|
|
return 0;
|
|
}
|
|
|
|
/* remove CRLF */
|
|
while ((*next == '\r' || *next == '\n') && (next < &buf[rv]))
|
|
next++;
|
|
|
|
if (cn->post_data == NULL)
|
|
{
|
|
/* Allocate buffer for the POST data that will be used by proccgi
|
|
to send POST data to the CGI script */
|
|
cn->post_data = (char *)calloc(1, (cn->content_length + 1));
|
|
}
|
|
|
|
cn->post_state = 0;
|
|
cn->post_read = 0;
|
|
post_data = cn->post_data;
|
|
|
|
while (next < &buf[rv])
|
|
{
|
|
/* copy POST data to buffer */
|
|
*post_data++ = *next++;
|
|
cn->post_read++;
|
|
if (cn->post_read == cn->content_length)
|
|
{
|
|
/* No more POST data to be copied */
|
|
*post_data = '\0';
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* More POST data has to be read. read_post_data will continue with that */
|
|
cn->post_state = 1;
|
|
return 0;
|
|
}
|
|
|
|
void read_post_data(struct connstruct *cn)
|
|
{
|
|
char buf[MAXREADLENGTH], *next;
|
|
char *post_data;
|
|
int rv;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
rv = special_read(cn, buf, sizeof(buf)-1);
|
|
if (rv <= 0)
|
|
{
|
|
if (rv < 0 || !cn->is_ssl) /* really dead? */
|
|
removeconnection(cn);
|
|
return;
|
|
}
|
|
|
|
buf[rv] = '\0';
|
|
next = buf;
|
|
post_data = &cn->post_data[cn->post_read];
|
|
|
|
while (next < &buf[rv])
|
|
{
|
|
*post_data++ = *next++;
|
|
cn->post_read++;
|
|
|
|
if (cn->post_read == cn->content_length)
|
|
{
|
|
/* No more POST data to be copied */
|
|
*post_data='\0';
|
|
cn->post_state = 0;
|
|
buildactualfile(cn);
|
|
cn->state = STATE_WANT_TO_SEND_HEAD;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* More POST data to read */
|
|
}
|
|
|
|
#endif /* CONFIG_HTTP_HAS_CGI */
|
|
|
|
/* Decode string %xx -> char (in place) */
|
|
static void urldecode(char *buf)
|
|
{
|
|
int v;
|
|
char *p, *s, *w;
|
|
|
|
w = p = buf;
|
|
|
|
while (*p)
|
|
{
|
|
v = 0;
|
|
|
|
if (*p == '%')
|
|
{
|
|
s = p;
|
|
s++;
|
|
|
|
if (isxdigit((int) s[0]) && isxdigit((int) s[1]))
|
|
{
|
|
v = hexit(s[0])*16 + hexit(s[1]);
|
|
|
|
if (v)
|
|
{
|
|
/* do not decode %00 to null char */
|
|
*w = (char)v;
|
|
p = &s[1];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (!v) *w=*p;
|
|
p++;
|
|
w++;
|
|
}
|
|
|
|
*w='\0';
|
|
}
|
|
|
|
static int hexit(char c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
else if (c >= 'a' && c <= 'f')
|
|
return c - 'a' + 10;
|
|
else if (c >= 'A' && c <= 'F')
|
|
return c - 'A' + 10;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static void buildactualfile(struct connstruct *cn)
|
|
{
|
|
char *cp;
|
|
snprintf(cn->actualfile, MAXREQUESTLENGTH, ".%s", cn->filereq);
|
|
|
|
#ifndef WIN32
|
|
/* Add directory slash if not there */
|
|
if (isdir(cn->actualfile) &&
|
|
cn->actualfile[strlen(cn->actualfile)-1] != '/')
|
|
strcat(cn->actualfile, "/");
|
|
|
|
/* work out the directory name */
|
|
strncpy(cn->dirname, cn->actualfile, MAXREQUESTLENGTH);
|
|
if ((cp = strrchr(cn->dirname, '/')) == NULL)
|
|
cn->dirname[0] = 0;
|
|
else
|
|
*cp = 0;
|
|
#else
|
|
{
|
|
char curr_dir[MAXREQUESTLENGTH];
|
|
char path[MAXREQUESTLENGTH];
|
|
char *t = cn->actualfile;
|
|
|
|
GetCurrentDirectory(MAXREQUESTLENGTH, curr_dir);
|
|
|
|
/* convert all the forward slashes to back slashes */
|
|
while ((t = strchr(t, '/')))
|
|
*t++ = '\\';
|
|
|
|
snprintf(path, MAXREQUESTLENGTH, "%s%s", curr_dir, cn->actualfile);
|
|
memcpy(cn->actualfile, path, MAXREQUESTLENGTH);
|
|
|
|
/* Add directory slash if not there */
|
|
if (isdir(cn->actualfile) &&
|
|
cn->actualfile[strlen(cn->actualfile)-1] != '\\')
|
|
strcat(cn->actualfile, "\\");
|
|
|
|
/* work out the directory name */
|
|
strncpy(cn->dirname, cn->actualfile, MAXREQUESTLENGTH);
|
|
if ((cp = strrchr(cn->dirname, '\\')) == NULL)
|
|
cn->dirname[0] = 0;
|
|
else
|
|
*cp = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int sanitizefile(const char *buf)
|
|
{
|
|
int len, i;
|
|
|
|
/* Don't accept anything not starting with a / */
|
|
if (*buf != '/')
|
|
return 0;
|
|
|
|
len = strlen(buf);
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
/* Check for "/." i.e. don't send files starting with a . */
|
|
if (buf[i] == '/' && buf[i+1] == '.')
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sanitizehost(char *buf)
|
|
{
|
|
while (*buf != '\0')
|
|
{
|
|
/* Handle the port */
|
|
if (*buf == ':')
|
|
{
|
|
*buf = '\0';
|
|
return 1;
|
|
}
|
|
|
|
/* Enforce some basic URL rules... */
|
|
if ((isalnum((int)(*buf)) == 0 && *buf != '-' && *buf != '.') ||
|
|
(*buf == '.' && *(buf+1) == '.') ||
|
|
(*buf == '.' && *(buf+1) == '-') ||
|
|
(*buf == '-' && *(buf+1) == '.'))
|
|
return 0;
|
|
|
|
buf++;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static FILE * exist_check(struct connstruct *cn, const char *check_file)
|
|
{
|
|
char pathname[MAXREQUESTLENGTH];
|
|
snprintf(pathname, MAXREQUESTLENGTH, "%s/%s", cn->dirname, check_file);
|
|
return fopen(pathname, "r");
|
|
}
|
|
|
|
#ifdef CONFIG_HTTP_HAS_AUTHORIZATION
|
|
static void send_authenticate(struct connstruct *cn, const char *realm)
|
|
{
|
|
char buf[1024];
|
|
|
|
snprintf(buf, sizeof(buf), HTTP_VERSION" 401 Unauthorized\n"
|
|
"WWW-Authenticate: Basic\n"
|
|
"realm=\"%s\"\n", realm);
|
|
special_write(cn, buf, strlen(buf));
|
|
}
|
|
|
|
static int check_digest(char *salt, const char *msg_passwd)
|
|
{
|
|
uint8_t b256_salt[MAXREQUESTLENGTH];
|
|
uint8_t real_passwd[MD5_SIZE];
|
|
int salt_size = sizeof(b256_salt);
|
|
int password_size = sizeof(real_passwd);
|
|
char *b64_passwd;
|
|
uint8_t md5_result[MD5_SIZE];
|
|
MD5_CTX ctx;
|
|
|
|
/* retrieve the salt */
|
|
if ((b64_passwd = strchr(salt, '$')) == NULL)
|
|
return -1;
|
|
|
|
*b64_passwd++ = 0;
|
|
if (base64_decode(salt, strlen(salt), b256_salt, &salt_size))
|
|
return -1;
|
|
|
|
if (base64_decode(b64_passwd, strlen(b64_passwd), real_passwd,
|
|
&password_size))
|
|
return -1;
|
|
|
|
/* very simple MD5 crypt algorithm, but then the salt we use is large */
|
|
MD5_Init(&ctx);
|
|
MD5_Update(&ctx, b256_salt, salt_size); /* process the salt */
|
|
MD5_Update(&ctx, (uint8_t *)msg_passwd, strlen(msg_passwd));
|
|
MD5_Final(md5_result, &ctx);
|
|
return memcmp(md5_result, real_passwd, MD5_SIZE);/* 0 = ok */
|
|
}
|
|
|
|
static int auth_check(struct connstruct *cn)
|
|
{
|
|
char line[MAXREQUESTLENGTH];
|
|
FILE *fp;
|
|
char *cp;
|
|
|
|
if ((fp = exist_check(cn, ".htpasswd")) == NULL)
|
|
return 0; /* no .htpasswd file, so let though */
|
|
|
|
if (cn->authorization[0] == 0)
|
|
goto error;
|
|
|
|
/* cn->authorization is in form "username:password" */
|
|
if ((cp = strchr(cn->authorization, ':')) == NULL)
|
|
goto error;
|
|
else
|
|
*cp++ = 0; /* cp becomes the password */
|
|
|
|
while (fgets(line, sizeof(line), fp) != NULL)
|
|
{
|
|
char *b64_file_passwd;
|
|
int l = strlen(line);
|
|
|
|
/* nuke newline */
|
|
if (line[l-1] == '\n')
|
|
line[l-1] = 0;
|
|
|
|
/* line is form "username:salt(b64)$password(b64)" */
|
|
if ((b64_file_passwd = strchr(line, ':')) == NULL)
|
|
continue;
|
|
|
|
*b64_file_passwd++ = 0;
|
|
|
|
if (strcmp(line, cn->authorization)) /* our user? */
|
|
continue;
|
|
|
|
if (check_digest(b64_file_passwd, cp) == 0)
|
|
{
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
error:
|
|
fclose(fp);
|
|
send_authenticate(cn, cn->server_name);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
static int htaccess_check(struct connstruct *cn)
|
|
{
|
|
char line[MAXREQUESTLENGTH];
|
|
FILE *fp;
|
|
int ret = 0;
|
|
|
|
if ((fp = exist_check(cn, ".htaccess")) == NULL)
|
|
return 0; /* no .htaccess file, so let though */
|
|
|
|
while (fgets(line, sizeof(line), fp) != NULL)
|
|
{
|
|
if (strstr(line, "Deny all") || /* access to this dir denied */
|
|
/* Access will be denied unless SSL is active */
|
|
(!cn->is_ssl && strstr(line, "SSLRequireSSL")) ||
|
|
/* Access will be denied if SSL is active */
|
|
(cn->is_ssl && strstr(line, "SSLDenySSL")))
|
|
{
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
fclose(fp);
|
|
return ret;
|
|
}
|
|
|
|
static void send_error(struct connstruct *cn, int err)
|
|
{
|
|
char buf[MAXREQUESTLENGTH];
|
|
char *title;
|
|
char *text;
|
|
|
|
switch (err)
|
|
{
|
|
case 403:
|
|
title = "Forbidden";
|
|
text = "File is protected";
|
|
#ifdef CONFIG_HTTP_VERBOSE
|
|
printf("axhttpd: access to %s denied\n", cn->filereq); TTY_FLUSH();
|
|
#endif
|
|
break;
|
|
|
|
case 404:
|
|
title = "Not Found";
|
|
text = title;
|
|
break;
|
|
|
|
case 418:
|
|
title = "POST data size is too large";
|
|
text = title;
|
|
break;
|
|
|
|
default:
|
|
title = "Unknown";
|
|
text = "Unknown";
|
|
break;
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf), HTTP_VERSION" 200 OK\n"
|
|
"Content-Type: text/html\n\n"
|
|
"<html><body>\n<title>%s</title>\n"
|
|
"<h1>Error %d - %s</h1>\n</body></html>\n",
|
|
title, err, text);
|
|
special_write(cn, buf, strlen(buf));
|
|
|
|
#ifdef CONFIG_HTTP_VERBOSE
|
|
printf("axhttpd: http error: %s [%d]\n", title, err); TTY_FLUSH();
|
|
#endif
|
|
removeconnection(cn);
|
|
}
|
|
|
|
static const char *getmimetype(const char *name)
|
|
{
|
|
/* only bother with a few mime types - let the browser figure the rest out */
|
|
if (strstr(name, ".htm"))
|
|
return "text/html";
|
|
else if (strstr(name, ".css"))
|
|
return "text/css";
|
|
else if (strstr(name, ".php"))
|
|
return "application/x-http-php";
|
|
else
|
|
return "application/octet-stream";
|
|
}
|
|
|
|
static int special_write(struct connstruct *cn,
|
|
const char *buf, size_t count)
|
|
{
|
|
if (cn->is_ssl)
|
|
{
|
|
SSL *ssl = cn->ssl;
|
|
return ssl ? ssl_write(ssl, (uint8_t *)buf, count) : -1;
|
|
}
|
|
else
|
|
return SOCKET_WRITE(cn->networkdesc, buf, count);
|
|
}
|
|
|
|
static int special_read(struct connstruct *cn, void *buf, size_t count)
|
|
{
|
|
int res;
|
|
|
|
if (cn->is_ssl)
|
|
{
|
|
uint8_t *read_buf;
|
|
if ((res = ssl_read(cn->ssl, &read_buf)) > SSL_OK)
|
|
{
|
|
memcpy(buf, read_buf, res > (int)count ? count : res);
|
|
}
|
|
}
|
|
else
|
|
res = SOCKET_READ(cn->networkdesc, buf, count);
|
|
|
|
return res;
|
|
}
|
|
|