diff --git a/package/uhttpd/Makefile b/package/uhttpd/Makefile
index eb4238c90..8a7b5295d 100644
--- a/package/uhttpd/Makefile
+++ b/package/uhttpd/Makefile
@@ -8,9 +8,10 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=uhttpd
-PKG_RELEASE:=11
+PKG_RELEASE:=13
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+PKG_BUILD_DEPENDS := libcyassl liblua
include $(INCLUDE_DIR)/package.mk
diff --git a/package/uhttpd/files/uhttpd.config b/package/uhttpd/files/uhttpd.config
index acdd62ea4..534e8f8b2 100644
--- a/package/uhttpd/files/uhttpd.config
+++ b/package/uhttpd/files/uhttpd.config
@@ -12,6 +12,11 @@ config uhttpd main
# Server document root
option home /www
+ # Reject requests from RFC1918 IP addresses
+ # directed to the servers public IP(s).
+ # This is a DNS rebinding countermeasure.
+ option rfc1918_filter 1
+
# Certificate and private key for HTTPS.
# If no listen_https addresses are given,
# the key options are ignored.
diff --git a/package/uhttpd/files/uhttpd.init b/package/uhttpd/files/uhttpd.init
index 58f980c42..b00b2e281 100755
--- a/package/uhttpd/files/uhttpd.init
+++ b/package/uhttpd/files/uhttpd.init
@@ -17,6 +17,17 @@ append_arg() {
[ -n "$val" -o -n "$def" ] && append UHTTPD_ARGS "$opt ${val:-$def}"
}
+append_bool() {
+ local cfg="$1"
+ local var="$2"
+ local opt="$3"
+ local def="$4"
+ local val
+
+ config_get_bool val "$cfg" "$var" "$def"
+ [ "$val" = 1 ] && append UHTTPD_ARGS "$opt"
+}
+
generate_keys() {
local cfg="$1"
local key="$2"
@@ -59,6 +70,12 @@ start_instance()
append_arg "$cfg" lua_handler "-L"
append_arg "$cfg" script_timeout "-t"
append_arg "$cfg" network_timeout "-T"
+ append_arg "$cfg" error_page "-E"
+ append_arg "$cfg" index_page "-I"
+
+ append_bool "$cfg" no_symlinks "-S" 0
+ append_bool "$cfg" no_dirlists "-D" 0
+ append_bool "$cfg" rfc1918_filter "-R" 0
config_get http "$cfg" listen_http
for listen in $http; do
diff --git a/package/uhttpd/src/uhttpd-cgi.c b/package/uhttpd/src/uhttpd-cgi.c
index 855a72f56..086124916 100644
--- a/package/uhttpd/src/uhttpd-cgi.c
+++ b/package/uhttpd/src/uhttpd-cgi.c
@@ -234,6 +234,17 @@ void uh_cgi_request(struct client *cl, struct http_request *req, struct path_inf
if( pi->info )
setenv("PATH_INFO", pi->info, 1);
+ /* REDIRECT_STATUS, php-cgi wants it */
+ switch( req->redirect_status )
+ {
+ case 404:
+ setenv("REDIRECT_STATUS", "404", 1);
+ break;
+
+ default:
+ setenv("REDIRECT_STATUS", "200", 1);
+ break;
+ }
/* http version */
if( req->version > 1.0 )
diff --git a/package/uhttpd/src/uhttpd-file.c b/package/uhttpd/src/uhttpd-file.c
index 81f66a34b..850a14175 100644
--- a/package/uhttpd/src/uhttpd-file.c
+++ b/package/uhttpd/src/uhttpd-file.c
@@ -29,7 +29,7 @@
static const char * uh_file_mime_lookup(const char *path)
{
struct mimetype *m = &uh_mime_types[0];
- char *e;
+ const char *e;
while( m->extn )
{
@@ -275,7 +275,9 @@ static void uh_file_dirlist(struct client *cl, struct http_request *req, struct
strncat(filename, files[i]->d_name,
sizeof(filename) - strlen(files[i]->d_name));
- if( !stat(filename, &s) && (s.st_mode & S_IFDIR) )
+ if( !stat(filename, &s) &&
+ (s.st_mode & S_IFDIR) && (s.st_mode & S_IXOTH)
+ )
uh_http_sendf(cl, req,
"
%s/
"
"modified: %s
directory - %.02f kbyte"
@@ -293,7 +295,9 @@ static void uh_file_dirlist(struct client *cl, struct http_request *req, struct
strncat(filename, files[i]->d_name,
sizeof(filename) - strlen(files[i]->d_name));
- if( !stat(filename, &s) && !(s.st_mode & S_IFDIR) )
+ if( !stat(filename, &s) &&
+ !(s.st_mode & S_IFDIR) && (s.st_mode & S_IROTH)
+ )
uh_http_sendf(cl, req,
"%s
"
"modified: %s
%s - %.02f kbyte
"
@@ -369,7 +373,7 @@ void uh_file_request(struct client *cl, struct http_request *req, struct path_in
}
/* directory */
- else if( pi->stat.st_mode & S_IFDIR )
+ else if( (pi->stat.st_mode & S_IFDIR) && !cl->server->conf->no_dirlists )
{
/* write status */
uh_file_response_200(cl, req, NULL);
diff --git a/package/uhttpd/src/uhttpd-utils.c b/package/uhttpd/src/uhttpd-utils.c
index caa6b12bc..4a1423c71 100644
--- a/package/uhttpd/src/uhttpd-utils.c
+++ b/package/uhttpd/src/uhttpd-utils.c
@@ -59,6 +59,21 @@ int sa_port(void *sa)
return ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
}
+int sa_rfc1918(void *sa)
+{
+ struct sockaddr_in *v4 = (struct sockaddr_in *)sa;
+ unsigned long a = htonl(v4->sin_addr.s_addr);
+
+ if( v4->sin_family == AF_INET )
+ {
+ return ((a >= 0x0A000000) && (a <= 0x0AFFFFFF)) ||
+ ((a >= 0xAC100000) && (a <= 0xAC1FFFFF)) ||
+ ((a >= 0xC0A80000) && (a <= 0xC0A8FFFF));
+ }
+
+ return 0;
+}
+
/* Simple strstr() like function that takes len arguments for both haystack and needle. */
char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
{
@@ -464,6 +479,9 @@ struct path_info * uh_path_lookup(struct client *cl, const char *url)
int i = 0;
struct stat s;
+ /* back out early if url is undefined */
+ if ( url == NULL )
+ return NULL;
memset(path_phys, 0, sizeof(path_phys));
memset(path_info, 0, sizeof(path_info));
@@ -550,18 +568,31 @@ struct path_info * uh_path_lookup(struct client *cl, const char *url)
memcpy(buffer, path_phys, sizeof(buffer));
pathptr = &buffer[strlen(buffer)];
- for( i = 0; i < array_size(uh_index_files); i++ )
+ if( cl->server->conf->index_file )
{
- strncat(buffer, uh_index_files[i], sizeof(buffer));
+ strncat(buffer, cl->server->conf->index_file, sizeof(buffer));
if( !stat(buffer, &s) && (s.st_mode & S_IFREG) )
{
memcpy(path_phys, buffer, sizeof(path_phys));
memcpy(&p.stat, &s, sizeof(p.stat));
- break;
}
+ }
+ else
+ {
+ for( i = 0; i < array_size(uh_index_files); i++ )
+ {
+ strncat(buffer, uh_index_files[i], sizeof(buffer));
- *pathptr = 0;
+ if( !stat(buffer, &s) && (s.st_mode & S_IFREG) )
+ {
+ memcpy(path_phys, buffer, sizeof(path_phys));
+ memcpy(&p.stat, &s, sizeof(p.stat));
+ break;
+ }
+
+ *pathptr = 0;
+ }
}
p.root = docroot;
diff --git a/package/uhttpd/src/uhttpd-utils.h b/package/uhttpd/src/uhttpd-utils.h
index a6448b63b..1b1826541 100644
--- a/package/uhttpd/src/uhttpd-utils.h
+++ b/package/uhttpd/src/uhttpd-utils.h
@@ -49,6 +49,7 @@ struct path_info {
const char * sa_straddr(void *sa);
const char * sa_strport(void *sa);
int sa_port(void *sa);
+int sa_rfc1918(void *sa);
char *strfind(char *haystack, int hslen, const char *needle, int ndlen);
diff --git a/package/uhttpd/src/uhttpd.c b/package/uhttpd/src/uhttpd.c
index 2f77a32a9..be882470a 100644
--- a/package/uhttpd/src/uhttpd.c
+++ b/package/uhttpd/src/uhttpd.c
@@ -47,7 +47,7 @@ static void uh_sigchld(int sig)
while( waitpid(-1, NULL, WNOHANG) > 0 ) { }
}
-static void uh_config_parse(const char *path)
+static void uh_config_parse(struct config *conf)
{
FILE *c;
char line[512];
@@ -55,7 +55,10 @@ static void uh_config_parse(const char *path)
char *pass = NULL;
char *eol = NULL;
- if( (c = fopen(path ? path : "/etc/httpd.conf", "r")) != NULL )
+ const char *path = conf->file ? conf->file : "/etc/httpd.conf";
+
+
+ if( (c = fopen(path, "r")) != NULL )
{
memset(line, 0, sizeof(line));
@@ -74,9 +77,23 @@ static void uh_config_parse(const char *path)
"Notice: No password set for user %s, ignoring "
"authentication on %s\n", user, line
);
+ }
+ }
+ else if( !strncmp(line, "I:", 2) )
+ {
+ if( !(user = strchr(line, ':')) || (*user++ = 0) ||
+ !(eol = strchr(user, '\n')) || (*eol++ = 0) )
+ continue;
- break;
- }
+ conf->index_file = strdup(user);
+ }
+ else if( !strncmp(line, "E404:", 5) )
+ {
+ if( !(user = strchr(line, ':')) || (*user++ = 0) ||
+ !(eol = strchr(user, '\n')) || (*eol++ = 0) )
+ continue;
+
+ conf->error_handler = strdup(user);
}
}
@@ -302,6 +319,7 @@ static struct http_request * uh_http_header_parse(struct client *cl, char *buffe
}
/* valid enough */
+ req.redirect_status = 200;
return &req;
}
@@ -505,8 +523,9 @@ int main (int argc, char **argv)
}
#endif
- while( (opt = getopt(argc, argv, "fSC:K:p:s:h:c:l:L:d:r:m:x:t:T:")) > 0 )
- {
+ while( (opt = getopt(argc, argv,
+ "fSDRC:K:E:I:p:s:h:c:l:L:d:r:m:x:t:T:")) > 0
+ ) {
switch(opt)
{
/* [addr:]port */
@@ -597,11 +616,42 @@ int main (int argc, char **argv)
}
break;
+ /* error handler */
+ case 'E':
+ if( (strlen(optarg) == 0) || (optarg[0] != '/') )
+ {
+ fprintf(stderr, "Error: Invalid error handler: %s\n",
+ optarg);
+ exit(1);
+ }
+ conf.error_handler = optarg;
+ break;
+
+ /* index file */
+ case 'I':
+ if( (strlen(optarg) == 0) || (optarg[0] == '/') )
+ {
+ fprintf(stderr, "Error: Invalid index page: %s\n",
+ optarg);
+ exit(1);
+ }
+ conf.index_file = optarg;
+ break;
+
/* don't follow symlinks */
case 'S':
conf.no_symlinks = 1;
break;
+ /* don't list directories */
+ case 'D':
+ conf.no_dirlists = 1;
+ break;
+
+ case 'R':
+ conf.rfc1918_filter = 1;
+ break;
+
#ifdef HAVE_CGI
/* cgi prefix */
case 'x':
@@ -678,7 +728,11 @@ int main (int argc, char **argv)
" -K file ASN.1 server private key file\n"
#endif
" -h directory Specify the document root, default is '.'\n"
+ " -E string Use given virtual URL as 404 error handler\n"
+ " -I string Use given filename as index page for directories\n"
" -S Do not follow symbolic links outside of the docroot\n"
+ " -D Do not allow directory listings, send 403 instead\n"
+ " -R Enable RFC1918 filter\n"
#ifdef HAVE_LUA
" -l string URL prefix for Lua handler, default is '/lua'\n"
" -L file Lua handler script, omit to disable Lua\n"
@@ -727,7 +781,7 @@ int main (int argc, char **argv)
conf.realm = "Protected Area";
/* config file */
- uh_config_parse(conf.file);
+ uh_config_parse(&conf);
/* default network timeout */
if( conf.network_timeout <= 0 )
@@ -883,6 +937,14 @@ int main (int argc, char **argv)
/* parse message header */
if( (req = uh_http_header_recv(cl)) != NULL )
{
+ /* RFC1918 filtering required? */
+ if( conf.rfc1918_filter && sa_rfc1918(&cl->peeraddr) &&
+ !sa_rfc1918(&cl->servaddr) )
+ {
+ uh_http_sendhf(cl, 403, "Forbidden",
+ "Rejected request from RFC1918 IP to public server address");
+ }
+ else
#ifdef HAVE_LUA
/* Lua request? */
if( L && uh_path_match(conf.lua_prefix, req->url) )
@@ -913,8 +975,29 @@ int main (int argc, char **argv)
/* 404 */
else
{
- uh_http_sendhf(cl, 404, "Not Found",
- "No such file or directory");
+ /* Try to invoke an error handler */
+ pin = uh_path_lookup(cl, conf.error_handler);
+
+ if( pin && uh_auth_check(cl, req, pin) )
+ {
+ req->redirect_status = 404;
+
+#ifdef HAVE_CGI
+ if( uh_path_match(conf.cgi_prefix, pin->name) )
+ {
+ uh_cgi_request(cl, req, pin);
+ }
+ else
+#endif
+ {
+ uh_file_request(cl, req, pin);
+ }
+ }
+ else
+ {
+ uh_http_sendhf(cl, 404, "Not Found",
+ "No such file or directory");
+ }
}
}
diff --git a/package/uhttpd/src/uhttpd.h b/package/uhttpd/src/uhttpd.h
index 32e397007..fd2176ebd 100644
--- a/package/uhttpd/src/uhttpd.h
+++ b/package/uhttpd/src/uhttpd.h
@@ -64,8 +64,12 @@ struct config {
char docroot[PATH_MAX];
char *realm;
char *file;
+ char *index_file;
+ char *error_handler;
int no_symlinks;
+ int no_dirlists;
int network_timeout;
+ int rfc1918_filter;
#ifdef HAVE_CGI
char *cgi_prefix;
#endif
@@ -124,6 +128,7 @@ struct auth_realm {
struct http_request {
int method;
float version;
+ int redirect_status;
char *url;
char *headers[UH_LIMIT_HEADERS];
struct auth_realm *realm;