diff --git a/Common/Makefile b/Common/Makefile index 22880a9..21ee0a9 100644 --- a/Common/Makefile +++ b/Common/Makefile @@ -5,7 +5,7 @@ include $(PWD)/Platform/$(PLATFORM).mk .PHONY: all clean .SUFFIXES: .c .o -OBJS = string.o log.o +OBJS = string.o log.o dir.o all: common.a diff --git a/Common/cm_dir.h b/Common/cm_dir.h new file mode 100644 index 0000000..81d72fe --- /dev/null +++ b/Common/cm_dir.h @@ -0,0 +1,8 @@ +/* $Id$ */ + +#ifndef __CM_DIR_H__ +#define __CM_DIR_H__ + +char** cm_scandir(const char* path); + +#endif diff --git a/Common/cm_string.h b/Common/cm_string.h index 9adf0a1..945ba74 100644 --- a/Common/cm_string.h +++ b/Common/cm_string.h @@ -7,6 +7,7 @@ int cm_hex(const char* str, int len); char* cm_html_escape(const char* str); +char* cm_url_escape(const char* str); char* cm_strcat(const char* a, const char* b); char* cm_strcat3(const char* a, const char* b, const char* c); char* cm_strdup(const char* str); diff --git a/Common/dir.c b/Common/dir.c new file mode 100644 index 0000000..eca0e65 --- /dev/null +++ b/Common/dir.c @@ -0,0 +1,48 @@ +/* $Id$ */ + +#include "cm_dir.h" + +#include "cm_string.h" + +#include +#include +#include +#include + +int cm_sort(const void* _a, const void* _b){ + char* a = *(char**)_a; + char* b = *(char**)_b; + return strcmp(a, b); +} + +char** cm_scandir(const char* path){ + DIR* dir = opendir(path); + if(dir != NULL){ + char** r = malloc(sizeof(*r)); + r[0] = NULL; + struct dirent* d; + while((d = readdir(dir)) != NULL){ + if(strcmp(d->d_name, ".") != 0){ + struct stat s; + char* p = cm_strcat3(path, "/", d->d_name); + stat(p, &s); + free(p); + + char** old = r; + int i; + for(i = 0; old[i] != NULL; i++); + r = malloc(sizeof(*r) * (i + 2)); + for(i = 0; old[i] != NULL; i++) r[i] = old[i]; + r[i] = cm_strcat(d->d_name, S_ISDIR(s.st_mode) ? "/" : ""); + r[i + 1] = NULL; + free(old); + } + } + int len; + for(len = 0; r[len] != NULL; len++); + qsort(r, len, sizeof(char*), cm_sort); + return r; + }else{ + return NULL; + } +} diff --git a/Common/string.c b/Common/string.c index c894d3f..6b7af53 100644 --- a/Common/string.c +++ b/Common/string.c @@ -159,3 +159,26 @@ char* cm_html_escape(const char* str) { } return result; } + +char* cm_url_escape(const char* str) { + int i; + char* result = malloc(1); + result[0] = 0; + char cbuf[2]; + cbuf[1] = 0; + for(i = 0; str[i] != 0; i++) { + cbuf[0] = str[i]; + if('!' <= str[i] && str[i] <= '@' && str[i] != '.' && str[i] != '-' && str[i] != '/' && !('0' <= str[i] && str[i] <= '9')) { + char code[4]; + sprintf(code, "%%%02X", str[i]); + char* tmp = result; + result = cm_strcat(tmp, code); + free(tmp); + } else { + char* tmp = result; + result = cm_strcat(tmp, cbuf); + free(tmp); + } + } + return result; +} diff --git a/Server/config.c b/Server/config.c index 4e1904b..26e577f 100644 --- a/Server/config.c +++ b/Server/config.c @@ -30,6 +30,42 @@ struct tw_config_entry* tw_vhost_match(const char* name, int port) { return &config.root; } +bool tw_permission_allowed(const char* path, SOCKADDR addr, struct tw_http_request req, struct tw_config_entry* vhost){ + int i; + bool found = false; + bool pathstart = false; + bool perm = false; +again: + for(i = 0; i < vhost->dir_count; i++){ + struct tw_dir_entry* e = &vhost->dirs[i]; + pathstart = false; + if(strlen(path) >= strlen(e->dir)){ + pathstart = true; + int j; + for(j = 0; path[j] != 0 && e->dir[j] != 0; j++){ + if(path[j] != e->dir[j]){ + pathstart = false; + break; + } + } + } + char* noslash = cm_strdup(e->dir); + noslash[strlen(noslash) - 1] = 0; + if(strcmp(e->dir, path) == 0 || strcmp(noslash, path) == 0 || pathstart){ + found = true; + if(strcmp(e->name, "all") == 0){ + perm = e->type == TW_DIR_ALLOW; + } + } + free(noslash); + } + if(!found && vhost != &config.root){ + vhost = &config.root; + goto again; + } + return perm; +} + void tw_config_init(void) { int i; for(i = 0; i < MAX_PORTS + 1; i++) { @@ -43,6 +79,8 @@ void tw_config_init(void) { config.root.sslkey = NULL; config.root.sslcert = NULL; config.root.root = NULL; + config.root.mime_count = 0; + config.root.dir_count = 0; config.vhost_count = 0; config.module_count = 0; config.extension = NULL; @@ -62,6 +100,7 @@ int tw_config_read(const char* path) { int stop = 0; struct tw_config_entry* current = &config.root; char* vhost = NULL; + char* dir = NULL; while(stop == 0) { int c = fread(cbuf, 1, 1, f); if(cbuf[0] == '\n' || c <= 0) { @@ -77,6 +116,56 @@ int tw_config_read(const char* path) { break; } } + } else if(cm_strcaseequ(r[0], "BeginDirectory")) { + if(dir != NULL) { + cm_log("Config", "Already in directory section at line %d", ln); + stop = 1; + } else { + if(r[1] == NULL) { + cm_log("Config", "Missing directory at line %d", ln); + stop = 1; + } else { + dir = cm_strcat(r[1], r[1][strlen(r[1]) - 1] == '/' ? "" : "/"); + } + } + } else if(cm_strcaseequ(r[0], "EndDirectory")) { + if(dir == NULL) { + cm_log("Config", "Not in directory section at line %d", ln); + stop = 1; + } else { + free(dir); + dir = NULL; + } + } else if(cm_strcaseequ(r[0], "Allow")) { + if(dir == NULL) { + cm_log("Config", "Not in directory section at line %d", ln); + stop = 1; + } else { + if(r[1] == NULL) { + cm_log("Config", "Missing argument at line %d", ln); + stop = 1; + } else { + struct tw_dir_entry* e = ¤t->dirs[current->dir_count++]; + e->name = cm_strdup(r[1]); + e->dir = cm_strdup(dir); + e->type = TW_DIR_ALLOW; + } + } + } else if(cm_strcaseequ(r[0], "Deny")) { + if(dir == NULL) { + cm_log("Config", "Not in directory section at line %d", ln); + stop = 1; + } else { + if(r[1] == NULL) { + cm_log("Config", "Missing argument at line %d", ln); + stop = 1; + } else { + struct tw_dir_entry* e = ¤t->dirs[current->dir_count++]; + e->name = cm_strdup(r[1]); + e->dir = cm_strdup(dir); + e->type = TW_DIR_DENY; + } + } } else if(cm_strcaseequ(r[0], "BeginVirtualHost")) { if(vhost != NULL) { cm_log("Config", "Already in virtual host section at line %d", ln); @@ -88,6 +177,8 @@ int tw_config_read(const char* path) { } else { vhost = cm_strdup(r[1]); current = &config.vhosts[config.vhost_count++]; + current->dir_count = 0; + current->mime_count = 0; int i; current->name = cm_strdup(vhost); current->port = -1; @@ -141,7 +232,7 @@ int tw_config_read(const char* path) { stop = 1; } else { if(current->root != NULL) free(current->root); - current->root = cm_strdup(r[1]); + current->root = cm_strdup(strcmp(r[1], "/") == 0 ? "" : r[1]); } } else if(cm_strcaseequ(r[0], "ServerRoot")) { if(r[1] == NULL) { @@ -151,6 +242,18 @@ int tw_config_read(const char* path) { if(config.server_root != NULL) free(config.server_root); config.server_root = cm_strdup(r[1]); } + } else if(cm_strcaseequ(r[0], "MIMEType")) { + if(r[1] == NULL) { + cm_log("Config", "Missing extension at line %d", ln); + stop = 1; + }else if(r[2] == NULL) { + cm_log("Config", "Missing MIME at line %d", ln); + stop = 1; + } else { + struct tw_mime_entry* e = ¤t->mimes[current->mime_count++]; + e->ext = cm_strdup(r[1]); + e->mime = cm_strdup(r[2]); + } } else if(cm_strcaseequ(r[0], "LoadModule")) { for(i = 1; r[i] != NULL; i++) { void* mod = tw_module_load(r[i]); diff --git a/Server/server.c b/Server/server.c index 229c42b..322e015 100644 --- a/Server/server.c +++ b/Server/server.c @@ -14,14 +14,15 @@ #include #include #include +#include #include #include +#include #ifdef __MINGW32__ #include #include -#define NO_IPV6 #else #include #include @@ -36,11 +37,6 @@ extern char tw_server[]; fd_set fdset; int sockcount = 0; -#ifdef NO_IPV6 -#define SOCKADDR struct sockaddr_in -#else -#define SOCKADDR struct sockaddr_in6 -#endif SOCKADDR addresses[MAX_PORTS]; int sockets[MAX_PORTS]; @@ -151,7 +147,7 @@ size_t tw_write(SSL* ssl, int s, void* data, size_t len) { " \n" \ "\n" -void tw_process_page(SSL* ssl, int sock, const char* status, const char* type, const unsigned char* doc, size_t size) { +void tw_process_page(SSL* ssl, int sock, const char* status, const char* type, FILE* f, const unsigned char* doc, size_t size) { char construct[512]; sprintf(construct, "%llu", (unsigned long long)size); tw_write(ssl, sock, "HTTP/1.1 ", 9); @@ -169,7 +165,13 @@ void tw_process_page(SSL* ssl, int sock, const char* status, const char* type, c tw_write(ssl, sock, "\r\n", 2); size_t incr = 0; while(1) { - tw_write(ssl, sock, (unsigned char*)doc + incr, size < 128 ? size : 128); + if(f != NULL){ + char buffer[128]; + fread(buffer, size < 128 ? size : 128, 1, f); + tw_write(ssl, sock, buffer, size < 128 ? size : 128); + }else{ + tw_write(ssl, sock, (unsigned char*)doc + incr, size < 128 ? size : 128); + } incr += 128; if(size <= 128) break; size -= 128; @@ -215,7 +217,7 @@ char* tw_http_default_error(int code, char* name, int port) { void tw_http_error(SSL* ssl, int sock, int error, char* name, int port) { char* str = tw_http_default_error(error, name, port); - tw_process_page(ssl, sock, tw_http_status(error), "text/html", str, strlen(str)); + tw_process_page(ssl, sock, tw_http_status(error), "text/html", NULL, str, strlen(str)); free(str); } @@ -239,6 +241,12 @@ void addstring(char** str, const char* add, ...) { *str = cm_strcat(tmp, h); free(tmp); free(h); + } else if(add[i] == 'l') { + char* h = cm_url_escape(va_arg(va, const char*)); + char* tmp = *str; + *str = cm_strcat(tmp, h); + free(tmp); + free(h); } else if(add[i] == 'd') { int n = va_arg(va, int); char* h = malloc(512); @@ -265,15 +273,17 @@ struct pass_entry { int sock; int port; bool ssl; + SOCKADDR addr; }; unsigned int WINAPI tw_server_pass(void* ptr) { int sock = ((struct pass_entry*)ptr)->sock; bool ssl = ((struct pass_entry*)ptr)->ssl; int port = ((struct pass_entry*)ptr)->port; + SOCKADDR addr = ((struct pass_entry*)ptr)->addr; free(ptr); #else -void tw_server_pass(int sock, bool ssl, int port) { +void tw_server_pass(int sock, bool ssl, int port, SOCKADDR addr) { #endif char* name = config.hostname; @@ -294,7 +304,27 @@ void tw_server_pass(int sock, bool ssl, int port) { tw_init_tools(&tools); int ret = tw_http_parse(s, sock, &req); if(ret == 0) { + char* vhost = cm_strdup(config.hostname); int i; + for(i = 0; req.headers[i] != NULL; i += 2){ + if(cm_strcaseequ(req.headers[i], "Host")){ + free(vhost); + vhost = req.headers[i + 1]; + break; + } + } + cm_log("Server", "Host is %s", vhost); + int port = s == NULL ? 80 : 443; + char* host = cm_strdup(vhost); + for(i = 0; vhost[i] != 0; i++){ + if(vhost[i] == ':'){ + host[i] = 0; + port = atoi(host + i + 1); + break; + } + } + cm_log("Server", "Hostname is `%s', port is `%d'", host, port); + struct tw_config_entry* vhost_entry = tw_vhost_match(host, port); for(i = 0; i < config.module_count; i++) { tw_mod_request_t mod_req = (tw_mod_request_t)tw_module_symbol(config.modules[i], "mod_request"); if(mod_req != NULL) { @@ -312,30 +342,82 @@ void tw_server_pass(int sock, bool ssl, int port) { } } if(!res._processed) { - char* str = malloc(1); - str[0] = 0; - addstring(&str, "\n"); - addstring(&str, "\n"); - addstring(&str, " \n"); - addstring(&str, " \n"); - addstring(&str, " Index of %h\n", req.path); - addstring(&str, " \n"); - addstring(&str, " \n"); - addstring(&str, "

Index of %h

\n", req.path); - addstring(&str, "
\n"); - addstring(&str, " \n"); - addstring(&str, " \n"); - addstring(&str, " \n"); - addstring(&str, " \n"); - addstring(&str, " \n"); - addstring(&str, "
Filename
\n"); - addstring(&str, "
\n"); - addstring(&str, "
%s Server at %s Port %d
\n", tw_server, name, port); - addstring(&str, " \n"); - addstring(&str, "\n"); - tw_process_page(s, sock, tw_http_status(200), "text/html", str, strlen(str)); - free(str); + cm_log("Server", "Document root is %s", vhost_entry->root == NULL ? "not set" : vhost_entry->root); + char* path = cm_strcat(vhost_entry->root == NULL ? "" : vhost_entry->root, req.path); + cm_log("Server", "Filesystem path is %s", path); + struct stat st; + if(stat(path, &st) == 0){ + if(!tw_permission_allowed(path, addr, req, vhost_entry)){ + tw_http_error(s, sock, 403, name, port); + }else if(S_ISDIR(st.st_mode)){ + char* str = malloc(1); + str[0] = 0; + char** items = cm_scandir(path); + addstring(&str, "\n"); + addstring(&str, "\n"); + addstring(&str, " \n"); + addstring(&str, " \n"); + addstring(&str, " Index of %h\n", req.path); + addstring(&str, " \n"); + addstring(&str, " \n"); + addstring(&str, "

Index of %h

\n", req.path); + addstring(&str, "
\n"); + addstring(&str, " \n"); + addstring(&str, " \n"); + addstring(&str, " \n"); + addstring(&str, " \n"); + addstring(&str, " \n"); + if(items != NULL){ + for(i = 0; items[i] != NULL; i++){ + addstring(&str, "\n"); + addstring(&str, " \n"); + addstring(&str, " \n", items[i], items[i]); + addstring(&str, "\n"); + } + } + addstring(&str, "
Filename
%h
\n"); + addstring(&str, "
\n"); + addstring(&str, "
%s Server at %s Port %d
\n", tw_server, name, port); + addstring(&str, " \n"); + addstring(&str, "\n"); + tw_process_page(s, sock, tw_http_status(200), "text/html", NULL, str, strlen(str)); + free(str); + }else{ + char* mime = "application/octet-stream"; + bool set = false; + char* ext = NULL; + for(i = strlen(req.path) - 1; i >= 0; i--){ + if(req.path[i] == '.'){ + ext = cm_strdup(req.path + i); + break; + } + } + for(i = 0; i < vhost_entry->mime_count; i++){ + if(strcmp(vhost_entry->mimes[i].ext, "all") == 0 || (ext != NULL && strcmp(vhost_entry->mimes[i].ext, ext) == 0)){ + mime = vhost_entry->mimes[i].mime; + set = true; + } + } + if(!set){ + for(i = 0; i < config.root.mime_count; i++){ + if(strcmp(config.root.mimes[i].ext, "all") == 0 || (ext != NULL && strcmp(config.root.mimes[i].ext, ext) == 0)){ + mime = config.root.mimes[i].mime; + set = true; + } + } + } + if(ext != NULL) free(ext); + FILE* f = fopen(path, "rb"); + tw_process_page(s, sock, tw_http_status(200), mime, f, NULL, st.st_size); + fclose(f); + } + }else{ + tw_http_error(s, sock, 404, name, port); + } + free(path); } + free(vhost); + free(host); } else { tw_http_error(s, sock, 400, name, port); } @@ -378,11 +460,12 @@ void tw_server_loop(void) { e->sock = sock; e->ssl = config.ports[i] & (1ULL << 32); e->port = config.ports[i]; + e->addr = claddr; thread = (HANDLE)_beginthreadex(NULL, 0, tw_server_pass, e, 0, NULL); #else pid_t pid = fork(); if(pid == 0) { - tw_server_pass(sock, config.ports[i] & (1ULL << 32), config.ports[i]); + tw_server_pass(sock, config.ports[i] & (1ULL << 32), config.ports[i], claddr); _exit(0); } else { close_socket(sock); diff --git a/Server/tw_config.h b/Server/tw_config.h index 9a17e86..1ec6e4e 100644 --- a/Server/tw_config.h +++ b/Server/tw_config.h @@ -3,11 +3,45 @@ #ifndef __TW_CONFIG_H__ #define __TW_CONFIG_H__ -#include +#include "tw_http.h" -#define MAX_PORTS 1024 -#define MAX_VHOSTS 1024 -#define MAX_MODULES 1024 +#include +#include + +#ifdef __MINGW32__ +#include +#define NO_IPV6 +#else +#include +#endif + +#ifdef NO_IPV6 +#define SOCKADDR struct sockaddr_in +#else +#define SOCKADDR struct sockaddr_in6 +#endif + +#define MAX_PORTS 1024 +#define MAX_VHOSTS 1024 +#define MAX_MODULES 1024 +#define MAX_DIRS 1024 +#define MAX_MIME 1024 + +enum TW_DIR_TYPE { + TW_DIR_ALLOW = 0, + TW_DIR_DENY +}; + +struct tw_dir_entry { + char* name; + char* dir; + int type; +}; + +struct tw_mime_entry { + char* ext; + char* mime; +}; struct tw_config_entry { char* name; @@ -15,6 +49,10 @@ struct tw_config_entry { char* sslkey; char* sslcert; char* root; + struct tw_dir_entry dirs[MAX_DIRS]; + int dir_count; + struct tw_mime_entry mimes[MAX_DIRS]; + int mime_count; }; struct tw_config { @@ -32,5 +70,6 @@ struct tw_config { void tw_config_init(void); int tw_config_read(const char* path); struct tw_config_entry* tw_vhost_match(const char* name, int port); +bool tw_permission_allowed(const char* path, SOCKADDR addr, struct tw_http_request req, struct tw_config_entry* vhost); #endif diff --git a/example.conf b/example.conf index 1159bfc..163bae7 100644 --- a/example.conf +++ b/example.conf @@ -9,7 +9,18 @@ ListenSSL 8443 8444 8445 8446 8447 SSLKey key.pem SSLCertificate cert.pem -DocumentRoot /var/www +MIMEType all application/octet-stream +MIMEType .html text/html + +DocumentRoot / + +BeginDirectory / + Allow all +EndDirectory + +BeginDirectory /var/www + Deny all +EndDirectory BeginVirtualHost nishinbsd-ssd EndVirtualHost