55 Commits
v4.0 ... v4.2

Author SHA1 Message Date
7b562c4b78 Reverse proxy printing debug message always 2021-01-09 11:50:44 +01:00
413ab2aa5b Reverse proxy bugfix 2021-01-08 21:43:39 +01:00
68315b9765 Update version string to 4.2 2021-01-08 20:16:25 +01:00
820a232a96 Sending X-Forwarded-For 2021-01-08 19:59:18 +01:00
b676388018 Bugfix for proxy 2021-01-08 19:45:41 +01:00
debac11f90 Trying proxy more often 2021-01-08 17:52:35 +01:00
9297788cdf Added TODOs 2021-01-07 22:51:02 +01:00
e61d16fb41 Hopefully bugfix 2021-01-07 22:35:49 +01:00
ebf3258092 Bugfix? 2021-01-07 22:26:01 +01:00
59d0c485fd Add version in welcome string 2021-01-07 22:13:15 +01:00
c90df2397f Removed debug message 2021-01-07 22:12:27 +01:00
c4eb6709cf Changed buffer mode for stdout 2021-01-07 22:11:27 +01:00
a73cbac7a1 Bugfix for sending files 2021-01-07 22:03:11 +01:00
95946b1666 Reverse proxy working 2021-01-07 21:58:45 +01:00
785ab31890 Added custom status 2021-01-07 18:28:51 +01:00
5481d314c9 Error document update 2 2021-01-07 18:00:32 +01:00
5986f39802 Error document update 2021-01-07 17:58:08 +01:00
a972340209 Add custom status todos 2021-01-07 17:53:55 +01:00
e6dd8b84f9 Error document style update 2021-01-07 17:53:38 +01:00
c3942b3382 Bugfix 2 2021-01-06 23:38:28 +01:00
fde2b7aabb Bugfix reverse proxy 2021-01-06 23:34:18 +01:00
d0be587a36 Bugfix 2021-01-06 23:29:45 +01:00
351568004f Add reverse proxy 2021-01-06 23:24:47 +01:00
be757add02 Bugfix URI 2021-01-06 19:19:06 +01:00
037c150868 enhanced reverse proxy message 2021-01-06 19:17:25 +01:00
576d0a0dba Dig error as print 2021-01-06 19:15:20 +01:00
ee6f9115a8 client refactor 2021-01-06 19:10:06 +01:00
6bbf79245b Report status on not local mode 2021-01-06 18:45:38 +01:00
080d729f31 Add config file 2021-01-06 15:49:52 +01:00
2f6ba62715 Small http err doc fix 2021-01-05 23:14:36 +01:00
4d4d94fc81 Add config.c and config.h 2021-01-05 23:01:38 +01:00
55028bd9cd HTTP error document update 2021-01-05 23:01:04 +01:00
8b0eb45854 Handling IPv6 addresses as Host field 2021-01-05 19:45:00 +01:00
df49236130 Preventing double slashes 2021-01-04 20:56:05 +01:00
c7ca62a7e9 Add TODO for double slashes 2021-01-03 22:18:30 +01:00
5d01f7e219 Error document style update 2021-01-03 16:21:42 +01:00
ceb0167742 Small Code Refactor 2021-01-01 22:40:01 +01:00
8fa9f2528f Bugfix dir mode 3 2021-01-01 22:36:31 +01:00
2e16fdee96 Bugfix dir mode 2 2021-01-01 22:32:59 +01:00
f9b7e83ac8 Bugfix dir mode 2021-01-01 22:28:53 +01:00
9b9ddbb913 Add get_dir_mode 2021-01-01 22:17:30 +01:00
566ac0ca93 Update of Default error doc 2021-01-01 21:44:34 +01:00
bcdf36527f Dir mode change to info 2020-12-29 23:32:46 +01:00
75ef4110c8 Using right country code 2020-12-29 17:13:47 +01:00
f1064692a8 GeoIP hardcoded ip bugfix 2020-12-29 16:36:49 +01:00
f0ec64b629 GeoIP Info implemented 2020-12-29 16:26:53 +01:00
0db781e823 Bugifx double free client.buf 2020-12-29 11:50:23 +01:00
798c41f1c8 Bugfix for POST and PUT method 2020-12-29 11:48:56 +01:00
bc4a764bd5 Update .gitignore 2020-12-29 11:23:08 +01:00
105e11d31d Add Todo for Reverse Proxy 2020-12-29 11:17:51 +01:00
15530b642a Non-existent webroot bugfix 2020-12-29 11:05:38 +01:00
e856f3f091 Using strcmp more often 2020-12-29 10:52:48 +01:00
b04c787df4 Method parsing bugfix 2020-12-29 10:46:44 +01:00
ee7d1e086b Get hostnames with reverse DNS 2020-12-28 23:32:37 +01:00
cf8862100a Stdout unbuffered 2020-12-28 22:56:03 +01:00
19 changed files with 1387 additions and 532 deletions

2
.gitignore vendored
View File

@ -4,6 +4,4 @@
!run.sh
!Makefile
!.gitignore
!CppNet
!CppNet/**
!README.md

View File

@ -7,11 +7,11 @@ packages:
compile:
@mkdir -p bin
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz -lmaxminddb
compile-debian:
@mkdir -p bin
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz \
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz -lmaxminddb \
-D MAGIC_FILE="\"/usr/share/file/magic.mgc\"" \
-D PHP_FPM_SOCKET="\"/var/run/php/php7.3-fpm.sock\""

View File

@ -13,12 +13,10 @@ int magic_init() {
magic = magic_open(MAGIC_MIME);
if (magic == NULL) {
fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -1;
}
if (magic_load(magic, MAGIC_FILE) != 0) {
fprintf(stderr, ERR_STR "Unable to load magic cookie: %s" CLR_STR "\n", magic_error(magic));
fflush(stderr);
return -2;
}
return 0;
@ -32,10 +30,9 @@ int cache_process() {
signal(SIGINT, cache_process_term);
signal(SIGTERM, cache_process_term);
int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), 0);
int shm_id = shmget(SHM_KEY_CACHE, FILE_CACHE_SIZE * sizeof(cache_entry), 0);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -1;
}
@ -43,7 +40,6 @@ int cache_process() {
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -2;
}
cache = shm_rw;
@ -51,7 +47,6 @@ int cache_process() {
if (mkdir("/var/necronda-server/", 0755) < 0) {
if (errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda-server/': %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -3;
}
}
@ -106,14 +101,12 @@ int cache_process() {
if (comp_file == NULL) {
compress = 0;
fprintf(stderr, ERR_STR "Unable to open cache file: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
} else {
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
if (deflateInit(&strm, level) != Z_OK) {
fprintf(stderr, ERR_STR "Unable to init deflate: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
compress = 0;
fclose(comp_file);
}
@ -165,17 +158,15 @@ int cache_init() {
return -1;
}
int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), IPC_CREAT | IPC_EXCL | 0600);
int shm_id = shmget(SHM_KEY_CACHE, FILE_CACHE_SIZE * sizeof(cache_entry), IPC_CREAT | IPC_EXCL | 0600);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -2;
}
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
if (shm == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -3;
}
cache = shm;
@ -183,7 +174,6 @@ int cache_init() {
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -4;
}
cache = shm_rw;
@ -202,11 +192,9 @@ int cache_init() {
} else if (pid > 0) {
// parent
fprintf(stderr, "Started child process with PID %i as cache-updater\n", pid);
fflush(stderr);
children[0] = pid;
} else {
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
return -5;
}
@ -214,13 +202,15 @@ int cache_init() {
}
int cache_unload() {
int shm_id = shmget(SHM_KEY, 0, 0);
int shm_id = shmget(SHM_KEY_CACHE, 0, 0);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
shmdt(cache);
return -1;
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
shmdt(cache);
return -1;
}
shmdt(cache);
return 0;
@ -228,7 +218,7 @@ int cache_unload() {
int cache_update_entry(int entry_num, const char *filename, const char *webroot) {
void *cache_ro = cache;
int shm_id = shmget(SHM_KEY, 0, 0);
int shm_id = shmget(SHM_KEY_CACHE, 0, 0);
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
@ -248,7 +238,7 @@ int cache_update_entry(int entry_num, const char *filename, const char *webroot)
char type_new[24];
sprintf(type_new, "%s", type);
if (strcmp(type, "text/plain") == 0) {
if (strncmp(filename + strlen(filename) - 4, ".css", 4) == 0) {
if (strcmp(filename + strlen(filename) - 4, ".css") == 0) {
sprintf(type_new, "text/css");
} else if (strcmp(filename + strlen(filename) - 3, ".js") == 0) {
sprintf(type_new, "text/javascript");
@ -270,7 +260,7 @@ int cache_update_entry(int entry_num, const char *filename, const char *webroot)
int cache_filename_comp_invalid(const char *filename) {
void *cache_ro = cache;
int shm_id = shmget(SHM_KEY, 0, 0);
int shm_id = shmget(SHM_KEY_CACHE, 0, 0);
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));

View File

@ -13,19 +13,17 @@
int server_keep_alive = 1;
char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr,
*log_client_prefix, *log_conn_prefix, *log_req_prefix,
*client_host_str;
char *log_client_prefix, *log_conn_prefix, *log_req_prefix, *client_geoip;
struct timeval client_timeout = {.tv_sec = CLIENT_TIMEOUT, .tv_usec = 0};
char *get_webroot(const char *http_host) {
char *webroot = malloc(strlen(webroot_base) + strlen(http_host) + 1);
unsigned long len = strlen(webroot_base);
while (webroot_base[len - 1] == '/') len--;
long pos = strchr(http_host, ':') - http_host;
sprintf(webroot, "%.*s/%.*s", (int) len, webroot_base, (int) (pos < 0 ? strlen(http_host) : pos), http_host);
return webroot;
host_config *get_host_config(const char *host) {
for (int i = 0; i < MAX_HOST_CONFIG; i++) {
host_config *hc = &config[i];
if (hc->type == CONFIG_TYPE_UNSET) break;
if (strcmp(hc->name, host) == 0) return hc;
}
return NULL;
}
void client_terminate() {
@ -39,18 +37,22 @@ int client_websocket_handler() {
int client_request_handler(sock *client, unsigned long client_num, unsigned int req_num) {
struct timespec begin, end;
int ret, client_keep_alive, dir_mode;
long ret;
int client_keep_alive;
char buf0[1024], buf1[1024];
char msg_buf[4096], msg_pre_buf[4096], err_msg[256];
char buffer[CHUNK_SIZE];
err_msg[0] = 0;
char *host, *hdr_connection, *webroot;
char host[256], *host_ptr, *hdr_connection;
host_config *conf = NULL;
long content_length = 0;
FILE *file = NULL;
msg_buf[0] = 0;
int accept_if_modified_since = 0;
int use_fastcgi = 0;
int use_rev_proxy = 0;
fastcgi_conn php_fpm = {.socket = 0, .req_id = 0};
http_status custom_status;
http_res res;
sprintf(res.version, "1.1");
@ -99,29 +101,43 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
}
hdr_connection = http_get_header_field(&req.hdr, "Connection");
client_keep_alive = hdr_connection != NULL && strncmp(hdr_connection, "keep-alive", 10) == 0;
host = http_get_header_field(&req.hdr, "Host");
if (host == NULL || strchr(host, '/') != NULL) {
client_keep_alive = hdr_connection != NULL &&
(strcmp(hdr_connection, "keep-alive") == 0 || strcmp(hdr_connection, "Keep-Alive") == 0);
host_ptr = http_get_header_field(&req.hdr, "Host");
if (host_ptr != NULL && strlen(host_ptr) > 255) {
host[0] = 0;
res.status = http_get_status(400);
sprintf(err_msg, "Host header field is too long.");
goto respond;
} else if (host_ptr == NULL || strchr(host_ptr, '/') != NULL) {
if (strchr(client_addr_str, ':') == NULL) {
strcpy(host, client_addr_str);
} else {
sprintf(host, "[%s]", client_addr_str);
}
res.status = http_get_status(400);
sprintf(err_msg, "The client provided no or an invalid Host header field.");
goto respond;
} else {
strcpy(host, host_ptr);
}
sprintf(log_req_prefix, "[%s%24s%s]%s ", BLD_STR, host, CLR_STR, log_client_prefix);
log_prefix = log_req_prefix;
print(BLD_STR "%s %s" CLR_STR, req.method, req.uri);
webroot = get_webroot(host);
if (webroot == NULL) {
conf = get_host_config(host);
if (conf == NULL) {
print("Host unknown, redirecting to default");
res.status = http_get_status(307);
sprintf(buf0, "https://%s%s", NECRONDA_DEFAULT, req.uri);
http_add_header_field(&req.hdr, "Location", buf0);
sprintf(buf0, "https://%s%s", DEFAULT_HOST, req.uri);
http_add_header_field(&res.hdr, "Location", buf0);
goto respond;
}
dir_mode = URI_DIR_MODE_FORBIDDEN;
http_uri uri;
ret = uri_init(&uri, webroot, req.uri, dir_mode);
unsigned char dir_mode = conf->type == CONFIG_TYPE_LOCAL ? conf->local.dir_mode : URI_DIR_MODE_NO_VALIDATION;
ret = uri_init(&uri, conf->local.webroot, req.uri, dir_mode);
if (ret != 0) {
if (ret == 1) {
sprintf(err_msg, "Invalid URI: has to start with slash.");
@ -132,218 +148,247 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
goto respond;
}
ssize_t size = sizeof(buf0);
url_decode(req.uri, buf0, &size);
int change_proto = strncmp(uri.uri, "/.well-known/", 13) != 0 && !client->enc;
if (strcmp(uri.uri, buf0) != 0 || change_proto) {
res.status = http_get_status(308);
size = sizeof(buf0);
encode_url(uri.uri, buf0, &size);
if (change_proto) {
sprintf(buf1, "https://%s%s", host, buf0);
http_add_header_field(&res.hdr, "Location", buf1);
} else {
http_add_header_field(&res.hdr, "Location", buf0);
if (dir_mode != URI_DIR_MODE_NO_VALIDATION) {
ssize_t size = sizeof(buf0);
url_decode(req.uri, buf0, &size);
int change_proto = strncmp(uri.uri, "/.well-known/", 13) != 0 && !client->enc;
if (strcmp(uri.uri, buf0) != 0 || change_proto) {
res.status = http_get_status(308);
size = sizeof(buf0);
encode_url(uri.uri, buf0, &size);
if (change_proto) {
sprintf(buf1, "https://%s%s", host, buf0);
http_add_header_field(&res.hdr, "Location", buf1);
} else {
http_add_header_field(&res.hdr, "Location", buf0);
}
goto respond;
}
goto respond;
}
if (uri.filename == NULL && (int) uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
res.status = http_get_status(403);
sprintf(err_msg, "It is not allowed to list the contents of this directory.");
goto respond;
} else if (uri.filename == NULL && (int) !uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
// TODO list directory contents
res.status = http_get_status(501);
sprintf(err_msg, "Listing contents of an directory is currently not implemented.");
goto respond;
} else if (uri.filename == NULL || (strlen(uri.pathinfo) > 0 && (int) uri.is_static)) {
res.status = http_get_status(404);
goto respond;
}
if (uri.is_static) {
res.status = http_get_status(200);
http_add_header_field(&res.hdr, "Allow", "GET, HEAD");
http_add_header_field(&res.hdr, "Accept-Ranges", "bytes");
if (strncmp(req.method, "GET", 3) != 0 && strncmp(req.method, "HEAD", 4) != 0) {
res.status = http_get_status(405);
if (conf->type == CONFIG_TYPE_LOCAL) {
if (uri.filename == NULL && (int) uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
res.status = http_get_status(403);
sprintf(err_msg, "It is not allowed to list the contents of this directory.");
goto respond;
} else if (uri.filename == NULL && (int) !uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
// TODO list directory contents
res.status = http_get_status(501);
sprintf(err_msg, "Listing contents of an directory is currently not implemented.");
goto respond;
} else if (uri.filename == NULL || (strlen(uri.pathinfo) > 0 && (int) uri.is_static)) {
res.status = http_get_status(404);
goto respond;
} else if (strlen(uri.pathinfo) != 0 && conf->local.dir_mode != URI_DIR_MODE_INFO) {
res.status = http_get_status(404);
goto respond;
}
ret = uri_cache_init(&uri);
if (ret != 0) {
res.status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with internal file cache.");
goto respond;
}
char *last_modified = http_format_date(uri.meta->stat.st_mtime, buf0, sizeof(buf0));
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
sprintf(buf1, "%s; charset=%s", uri.meta->type, uri.meta->charset);
http_add_header_field(&res.hdr, "Content-Type", buf1);
if (uri.meta->etag[0] != 0) {
http_add_header_field(&res.hdr, "ETag", uri.meta->etag);
}
if (strncmp(uri.meta->type, "text/", 5) == 0) {
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=3600");
} else {
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=86400");
}
char *if_modified_since = http_get_header_field(&req.hdr, "If-Modified-Since");
char *if_none_match = http_get_header_field(&req.hdr, "If-None-Match");
if ((if_none_match != NULL && strstr(if_none_match, uri.meta->etag) == NULL) || (accept_if_modified_since &&
if_modified_since != NULL && strncmp(if_modified_since, last_modified, strlen(last_modified)) == 0)) {
res.status = http_get_status(304);
goto respond;
}
char *range = http_get_header_field(&req.hdr, "Range");
if (range != NULL) {
if (strlen(range) <= 6 || strncmp(range, "bytes=", 6) != 0) {
res.status = http_get_status(416);
http_remove_header_field(&res.hdr, "Content-Type", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Last-Modified", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "ETag", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
if (uri.is_static) {
res.status = http_get_status(200);
http_add_header_field(&res.hdr, "Allow", "GET, HEAD");
http_add_header_field(&res.hdr, "Accept-Ranges", "bytes");
if (strcmp(req.method, "GET") != 0 && strcmp(req.method, "HEAD") != 0) {
res.status = http_get_status(405);
goto respond;
}
range += 6;
char *ptr = strchr(range, '-');
if (ptr == NULL) {
res.status = http_get_status(416);
ret = uri_cache_init(&uri);
if (ret != 0) {
res.status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with internal file cache.");
goto respond;
}
file = fopen(uri.filename, "rb");
char *last_modified = http_format_date(uri.meta->stat.st_mtime, buf0, sizeof(buf0));
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
sprintf(buf1, "%s; charset=%s", uri.meta->type, uri.meta->charset);
http_add_header_field(&res.hdr, "Content-Type", buf1);
if (uri.meta->etag[0] != 0) {
http_add_header_field(&res.hdr, "ETag", uri.meta->etag);
}
if (strncmp(uri.meta->type, "text/", 5) == 0) {
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=3600");
} else {
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=86400");
}
char *if_modified_since = http_get_header_field(&req.hdr, "If-Modified-Since");
char *if_none_match = http_get_header_field(&req.hdr, "If-None-Match");
if ((if_none_match != NULL && strstr(if_none_match, uri.meta->etag) == NULL) ||
(accept_if_modified_since && if_modified_since != NULL && strcmp(if_modified_since, last_modified) == 0)) {
res.status = http_get_status(304);
goto respond;
}
char *range = http_get_header_field(&req.hdr, "Range");
if (range != NULL) {
if (strlen(range) <= 6 || strncmp(range, "bytes=", 6) != 0) {
res.status = http_get_status(416);
http_remove_header_field(&res.hdr, "Content-Type", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Last-Modified", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "ETag", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
goto respond;
}
range += 6;
char *ptr = strchr(range, '-');
if (ptr == NULL) {
res.status = http_get_status(416);
goto respond;
}
file = fopen(uri.filename, "rb");
fseek(file, 0, SEEK_END);
unsigned long file_len = ftell(file);
fseek(file, 0, SEEK_SET);
if (file_len == 0) {
content_length = 0;
goto respond;
}
long num1 = 0;
long num2 = (long) file_len - 1;
if (ptr != range) num1 = (long) strtoul(range, NULL, 10);
if (ptr[1] != 0) num2 = (long) strtoul(ptr + 1, NULL, 10);
if (num1 >= file_len || num2 >= file_len || num1 > num2) {
res.status = http_get_status(416);
goto respond;
}
sprintf(buf0, "bytes %li-%li/%li", num1, num2, file_len);
http_add_header_field(&res.hdr, "Content-Range", buf0);
res.status = http_get_status(206);
fseek(file, num1, SEEK_SET);
content_length = num2 - num1 + 1;
goto respond;
}
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
if (uri.meta->filename_comp[0] != 0 && accept_encoding != NULL &&
strstr(accept_encoding, "deflate") != NULL) {
file = fopen(uri.meta->filename_comp, "rb");
if (file == NULL) {
cache_filename_comp_invalid(uri.filename);
goto not_compressed;
}
http_add_header_field(&res.hdr, "Content-Encoding", "deflate");
} else {
not_compressed:
file = fopen(uri.filename, "rb");
}
fseek(file, 0, SEEK_END);
unsigned long file_len = ftell(file);
content_length = ftell(file);
fseek(file, 0, SEEK_SET);
if (file_len == 0) {
content_length = 0;
goto respond;
}
long num1 = 0;
long num2 = (long) file_len - 1;
if (ptr != range) num1 = (long) strtoul(range, NULL, 10);
if (ptr[1] != 0) num2 = (long) strtoul(ptr + 1, NULL, 10);
if (num1 >= file_len || num2 >= file_len || num1 > num2) {
res.status = http_get_status(416);
goto respond;
}
sprintf(buf0, "bytes %li-%li/%li", num1, num2, file_len);
http_add_header_field(&res.hdr, "Content-Range", buf0);
res.status = http_get_status(206);
fseek(file, num1, SEEK_SET);
content_length = num2 - num1 + 1;
goto respond;
}
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
if (uri.meta->filename_comp[0] != 0 && accept_encoding != NULL && strstr(accept_encoding, "deflate") != NULL) {
file = fopen(uri.meta->filename_comp, "rb");
if (file == NULL) {
cache_filename_comp_invalid(uri.filename);
goto not_compressed;
}
http_add_header_field(&res.hdr, "Content-Encoding", "deflate");
} else {
not_compressed:
file = fopen(uri.filename, "rb");
}
fseek(file, 0, SEEK_END);
content_length = ftell(file);
fseek(file, 0, SEEK_SET);
} else {
struct stat statbuf;
stat(uri.filename, &statbuf);
char *last_modified = http_format_date(statbuf.st_mtime, buf0, sizeof(buf0));
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
struct stat statbuf;
stat(uri.filename, &statbuf);
char *last_modified = http_format_date(statbuf.st_mtime, buf0, sizeof(buf0));
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
res.status = http_get_status(200);
if (fastcgi_init(&php_fpm, client_num, req_num, client, &req, &uri) != 0) {
res.status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
goto respond;
}
if (strncmp(req.method, "POST", 4) == 0 || strncmp(req.method, "PUT", 3) == 0) {
char *client_content_length = http_get_header_field(&req.hdr, "Content-Length");
unsigned long client_content_len = 0;
if (client_content_length == NULL) {
goto fastcgi_end;
res.status = http_get_status(200);
if (fastcgi_init(&php_fpm, client_num, req_num, client, &req, &uri) != 0) {
res.status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
goto respond;
}
client_content_len = strtoul(client_content_length, NULL, 10);
ret = fastcgi_receive(&php_fpm, client, client_content_len);
if (strcmp(req.method, "POST") == 0 || strcmp(req.method, "PUT") == 0) {
char *client_content_length = http_get_header_field(&req.hdr, "Content-Length");
unsigned long client_content_len;
if (client_content_length == NULL) {
goto fastcgi_end;
}
client_content_len = strtoul(client_content_length, NULL, 10);
ret = fastcgi_receive(&php_fpm, client, client_content_len);
if (ret != 0) {
if (ret < 0) {
goto abort;
} else {
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
}
res.status = http_get_status(502);
goto respond;
}
}
fastcgi_end:
fastcgi_close_stdin(&php_fpm);
ret = fastcgi_header(&php_fpm, &res, err_msg);
if (ret != 0) {
if (ret < 0) {
goto abort;
} else {
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
}
res.status = http_get_status(502);
goto respond;
}
}
fastcgi_end:
fastcgi_close_stdin(&php_fpm);
ret = fastcgi_header(&php_fpm, &res, err_msg);
if (ret != 0) {
if (ret < 0) {
goto abort;
} else {
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
char *status = http_get_header_field(&res.hdr, "Status");
if (status != NULL) {
int status_code = (int) strtoul(status, NULL, 10);
res.status = http_get_status(status_code);
http_remove_header_field(&res.hdr, "Status", HTTP_REMOVE_ALL);
if (res.status == NULL && status_code >= 100 && status_code <= 999) {
custom_status.code = status_code;
strcpy(custom_status.type, "");
strcpy(custom_status.msg, status + 4);
res.status = &custom_status;
} else if (res.status == NULL) {
res.status = http_get_status(500);
sprintf(err_msg, "The status code was set to an invalid or unknown value.");
goto respond;
}
}
res.status = http_get_status(502);
goto respond;
}
char *status = http_get_header_field(&res.hdr, "Status");
if (status != NULL) {
res.status = http_get_status(strtoul(status, NULL, 10));
http_remove_header_field(&res.hdr, "Status", HTTP_REMOVE_ALL);
if (res.status == NULL){
res.status = http_get_status(500);
sprintf(err_msg, "The status code was set to an invalid or unknown value.");
goto respond;
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
if (accept_encoding != NULL && strstr(accept_encoding, "deflate") != NULL) {
http_add_header_field(&res.hdr, "Content-Encoding", "deflate");
}
content_length = -1;
use_fastcgi = 1;
if (http_get_header_field(&res.hdr, "Content-Length") == NULL) {
http_add_header_field(&res.hdr, "Transfer-Encoding", "chunked");
}
}
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
if (accept_encoding != NULL && strstr(accept_encoding, "deflate") != NULL) {
http_add_header_field(&res.hdr, "Content-Encoding", "deflate");
}
content_length = -1;
use_fastcgi = 1;
if (http_get_header_field(&res.hdr, "Content-Length") == NULL) {
http_add_header_field(&res.hdr, "Transfer-Encoding", "chunked");
}
} else if (conf->type != CONFIG_TYPE_LOCAL) {
print("Reverse proxy for " BLD_STR "%s:%i" CLR_STR, conf->rev_proxy.hostname, conf->rev_proxy.port);
ret = rev_proxy_init(&req, &res, conf, client, &custom_status, err_msg);
use_rev_proxy = ret == 0;
} else {
print(ERR_STR "Unknown host type: %i" CLR_STR, conf->type);
res.status = http_get_status(501);
}
respond:
if (http_get_header_field(&res.hdr, "Accept-Ranges") == NULL) {
http_add_header_field(&res.hdr, "Accept-Ranges", "none");
}
if (!use_fastcgi && file == NULL && res.status->code >= 400 && res.status->code < 600) {
http_error_msg *http_msg = http_get_error_msg(res.status->code);
sprintf(msg_pre_buf, http_error_document, res.status->code, res.status->msg,
http_msg != NULL ? http_msg->err_msg : "", err_msg[0] != 0 ? err_msg : "");
content_length = sprintf(msg_buf, http_default_document, res.status->code, res.status->msg,
msg_pre_buf, res.status->code >= 300 && res.status->code < 400 ? "info" : "error",
http_error_icon, "#C00000");
http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8");
}
if (content_length >= 0) {
sprintf(buf0, "%li", content_length);
http_add_header_field(&res.hdr, "Content-Length", buf0);
} else if (http_get_header_field(&res.hdr, "Transfer-Encoding") == NULL) {
server_keep_alive = 0;
if (!use_rev_proxy) {
if (http_get_header_field(&res.hdr, "Accept-Ranges") == NULL) {
http_add_header_field(&res.hdr, "Accept-Ranges", "none");
}
if (!use_fastcgi && !use_rev_proxy && file == NULL &&
res.status->code >= 400 && res.status->code < 600) {
http_error_msg *http_msg = http_get_error_msg(res.status->code);
sprintf(msg_pre_buf, http_error_document, res.status->code, res.status->msg,
http_msg != NULL ? http_msg->err_msg : "", err_msg[0] != 0 ? err_msg : "");
content_length = sprintf(msg_buf, http_default_document, res.status->code, res.status->msg,
msg_pre_buf, res.status->code >= 300 && res.status->code < 400 ? "info" : "error",
http_error_icon, "#C00000", host);
http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8");
}
if (content_length >= 0) {
sprintf(buf0, "%li", content_length);
http_add_header_field(&res.hdr, "Content-Length", buf0);
} else if (http_get_header_field(&res.hdr, "Transfer-Encoding") == NULL) {
server_keep_alive = 0;
}
} else {
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
http_add_header_field(&res.hdr, "Server", SERVER_STR);
}
char *conn = http_get_header_field(&res.hdr, "Connection");
int close_proxy = conn == NULL || (strcmp(conn, "keep-alive") != 0 && strcmp(conn, "Keep-Alive") != 0);
http_remove_header_field(&res.hdr, "Connection", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Keep-Alive", HTTP_REMOVE_ALL);
if (server_keep_alive && client_keep_alive) {
http_add_header_field(&res.hdr, "Connection", "keep-alive");
sprintf(buf0, "timeout=%i, max=%i", CLIENT_TIMEOUT, REQ_PER_CONNECTION);
@ -356,86 +401,162 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
clock_gettime(CLOCK_MONOTONIC, &end);
char *location = http_get_header_field(&res.hdr, "Location");
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
print("%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), res.status->code, res.status->msg,
location != NULL ? " -> " : "", location != NULL ? location : "", format_duration(micros, buf0), CLR_STR);
print("%s%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), use_rev_proxy ? "-> " : "", res.status->code,
res.status->msg, location != NULL ? " -> " : "", location != NULL ? location : "",
format_duration(micros, buf0), CLR_STR);
if (strncmp(req.method, "HEAD", 4) != 0) {
if (strcmp(req.method, "HEAD") != 0) {
unsigned long snd_len = 0;
unsigned long len = 0;
unsigned long len;
if (msg_buf[0] != 0) {
while (snd_len < content_length) {
if (client->enc) {
ret = SSL_write(client->ssl, msg_buf, (int) (content_length - snd_len));
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret));
}
} else {
ret = send(client->socket, msg_buf, content_length - snd_len, 0);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno));
}
}
if (ret <= 0) {
break;
}
snd_len += ret;
ret = sock_send(client, msg_buf, content_length, 0);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
}
snd_len += ret;
} else if (file != NULL) {
while (snd_len < content_length) {
len = fread(&buffer, 1, CHUNK_SIZE, file);
len = fread(buffer, 1, CHUNK_SIZE, file);
if (snd_len + len > content_length) {
len = content_length - snd_len;
}
if (client->enc) {
ret = SSL_write(client->ssl, buffer, (int) len);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, ssl_get_error(client->ssl, ret));
}
} else {
ret = send(client->socket, buffer, len, 0);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, strerror(errno));
}
}
ret = sock_send(client, buffer, len, feof(file) ? 0 : MSG_MORE);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
break;
}
snd_len += ret;
}
} else if (use_fastcgi) {
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
int chunked = transfer_encoding != NULL && strncmp(transfer_encoding, "chunked", 7) == 0;
int chunked = transfer_encoding != NULL && strcmp(transfer_encoding, "chunked") == 0;
char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
int comp = content_encoding != NULL && strncmp(content_encoding, "deflate", 7) == 0;
int comp = content_encoding != NULL && strcmp(content_encoding, "deflate") == 0;
int flags = (chunked ? FASTCGI_CHUNKED : 0) | (comp ? FASTCGI_COMPRESS : 0);
fastcgi_send(&php_fpm, client, flags);
} else if (use_rev_proxy) {
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
int chunked = transfer_encoding != NULL && strcmp(transfer_encoding, "chunked") == 0;
char *content_len = http_get_header_field(&res.hdr, "Content-Length");
unsigned long len_to_send = 0;
if (content_len != NULL) {
len_to_send = strtol(content_len, NULL, 10);
}
rev_proxy_send(client, chunked, len_to_send);
}
}
if (close_proxy && rev_proxy.socket != 0) {
print(BLUE_STR "Closing proxy connection" CLR_STR);
sock_close(&rev_proxy);
}
clock_gettime(CLOCK_MONOTONIC, &end);
micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
print("Transfer complete: %s", format_duration(micros, buf0));
uri_free(&uri);
abort:
if (php_fpm.socket != 0) close(php_fpm.socket);
if (php_fpm.socket != 0) {
shutdown(php_fpm.socket, SHUT_RDWR);
close(php_fpm.socket);
php_fpm.socket = 0;
}
http_free_req(&req);
http_free_res(&res);
if (client->buf != NULL) {
free(client->buf);
client->buf = NULL;
client->buf_off = 0;
client->buf_len = 0;
}
return !client_keep_alive;
}
int client_connection_handler(sock *client, unsigned long client_num) {
struct timespec begin, end;
int ret, req_num;
char buf[16];
char buf[1024];
clock_gettime(CLOCK_MONOTONIC, &begin);
// TODO get geoip data for ip address
// TODO Reverse DNS request
client_host_str = client_addr_str;
if (dns_server[0] != 0) {
sprintf(buf, "dig @%s +short +time=1 -x %s", dns_server, client_addr_str);
FILE *dig = popen(buf, "r");
if (dig == NULL) {
print(ERR_STR "Unable to start dig: %s" CLR_STR "\n", strerror(errno));
goto dig_err;
}
unsigned long read = fread(buf, 1, sizeof(buf), dig);
ret = pclose(dig);
if (ret != 0) {
print(ERR_STR "Dig terminated with exit code %i" CLR_STR "\n", ret);
goto dig_err;
}
char *ptr = memchr(buf, '\n', read);
if (ptr == buf || ptr == NULL) {
goto dig_err;
}
ptr[-1] = 0;
client_host_str = malloc(strlen(buf) + 1);
strcpy(client_host_str, buf);
} else {
dig_err:
client_host_str = NULL;
}
print("Connection accepted from %s (%s) [%s]", client_addr_str, client_host_str, "N/A");
client_geoip = malloc(GEOIP_MAX_SIZE);
long str_off = 0;
for (int i = 0; i < MAX_MMDB && mmdbs[i].filename != NULL; i++) {
int gai_error, mmdb_res;
MMDB_lookup_result_s result = MMDB_lookup_string(&mmdbs[i], client_addr_str, &gai_error, &mmdb_res);
if (mmdb_res != MMDB_SUCCESS) {
print(ERR_STR "Unable to lookup geoip info: %s" CLR_STR "\n", MMDB_strerror(mmdb_res));
continue;
} else if (gai_error != 0) {
print(ERR_STR "Unable to lookup geoip info" CLR_STR "\n");
continue;
} else if (!result.found_entry) {
continue;
}
MMDB_entry_data_list_s *list;
mmdb_res = MMDB_get_entry_data_list(&result.entry, &list);
if (mmdb_res != MMDB_SUCCESS) {
print(ERR_STR "Unable to lookup geoip info: %s" CLR_STR "\n", MMDB_strerror(mmdb_res));
continue;
}
long prev = str_off;
if (str_off != 0) {
str_off--;
}
mmdb_json(list, client_geoip, &str_off, GEOIP_MAX_SIZE);
if (prev != 0) {
client_geoip[prev - 1] = ',';
}
MMDB_free_entry_data_list(list);
}
char client_cc[3];
client_cc[0] = 0;
if (str_off == 0) {
free(client_geoip);
client_geoip = NULL;
} else {
char *pos = client_geoip;
pos = strstr(pos, "\"country\":");
if (pos != NULL) {
pos = strstr(pos, "\"iso_code\":");
pos += 12;
strncpy(client_cc, pos, 2);
}
}
print("Connection accepted from %s %s%s%s[%s]", client_addr_str, client_host_str != NULL ? "(" : "",
client_host_str != NULL ? client_host_str : "", client_host_str != NULL ? ") " : "",
client_cc[0] != 0 ? client_cc : "N/A");
client_timeout.tv_sec = CLIENT_TIMEOUT;
client_timeout.tv_usec = 0;
@ -453,8 +574,11 @@ int client_connection_handler(sock *client, unsigned long client_num) {
SSL_set_accept_state(client->ssl);
ret = SSL_accept(client->ssl);
client->_last_ret = ret;
client->_errno = errno;
client->_ssl_error = ERR_get_error();
if (ret <= 0) {
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, ssl_get_error(client->ssl, ret));
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(client));
goto close;
}
}
@ -467,12 +591,12 @@ int client_connection_handler(sock *client, unsigned long client_num) {
}
close:
if (client->enc) {
SSL_shutdown(client->ssl);
SSL_free(client->ssl);
sock_close(client);
if (rev_proxy.socket != 0) {
print(BLUE_STR "Closing proxy connection" CLR_STR);
sock_close(&rev_proxy);
}
shutdown(client->socket, SHUT_RDWR);
close(client->socket);
clock_gettime(CLOCK_MONOTONIC, &end);
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
@ -524,9 +648,18 @@ int client_handler(sock *client, unsigned long client_num, struct sockaddr_in6 *
ret = client_connection_handler(client, client_num);
free(client_addr_str_ptr);
client_addr_str_ptr = NULL;
free(server_addr_str_ptr);
server_addr_str_ptr = NULL;
if (client_host_str != NULL) {
free(client_host_str);
client_host_str = NULL;
}
free(log_conn_prefix);
log_conn_prefix = NULL;
free(log_req_prefix);
log_req_prefix = NULL;
free(log_client_prefix);
log_client_prefix = NULL;
return ret;
}

200
src/config.c Normal file
View File

@ -0,0 +1,200 @@
/**
* Necronda Web Server
* Configuration file loader
* src/config.c
* Lorenz Stechauner, 2021-01-05
*/
#include "config.h"
#include "uri.h"
#include <sys/ipc.h>
#include <sys/shm.h>
int config_init() {
int shm_id = shmget(SHM_KEY_CONFIG, MAX_HOST_CONFIG * sizeof(host_config), IPC_CREAT | IPC_EXCL | 0640);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
return -1;
}
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
if (shm == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
return -2;
}
config = shm;
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
return -3;
}
config = shm_rw;
memset(config, 0, MAX_HOST_CONFIG * sizeof(host_config));
shmdt(shm_rw);
config = shm;
return 0;
}
int config_unload() {
int shm_id = shmget(SHM_KEY_CONFIG, 0, 0);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
shmdt(config);
return -1;
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
shmdt(config);
return -1;
}
shmdt(config);
return 0;
}
int config_load(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
fprintf(stderr, ERR_STR "Unable to open config file: %s" CLR_STR "\n", strerror(errno));
return -1;
}
fseek(file, 0, SEEK_END);
unsigned long len = ftell(file);
fseek(file, 0, SEEK_SET);
char *conf = malloc(len);
fread(conf, 1, len, file);
fclose(file);
host_config *tmp_config = malloc(MAX_HOST_CONFIG * sizeof(host_config));
memset(tmp_config, 0, MAX_HOST_CONFIG * sizeof(host_config));
int i = 0;
int mode = 0;
char *ptr = NULL;
char host[256], *source, *target;
host[0] = 0;
while ((ptr = strtok(ptr == NULL ? conf : NULL, "\n")) != NULL) {
char *comment = strchr(ptr, '#');
if (comment != NULL) comment[0] = 0;
len = strlen(ptr);
if (ptr[0] == '[') {
if (ptr[len - 1] != ']') goto err;
strncpy(tmp_config[i].name, ptr + 1, len - 2);
i++;
continue;
} else if (i == 0) {
if (len > 12 && strncmp(ptr, "certificate", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
source = ptr + 11;
target = cert_file;
} else if (len > 12 && strncmp(ptr, "private_key", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
source = ptr + 11;
target = key_file;
} else if (len > 10 && strncmp(ptr, "geoip_dir", 9) == 0 && (ptr[9] == ' ' || ptr[9] == '\t')) {
source = ptr + 9;
target = geoip_dir;
} else if (len > 11 && strncmp(ptr, "dns_server", 10) == 0 && (ptr[10] == ' ' || ptr[10] == '\t')) {
source = ptr + 10;
target = dns_server;
}
} else {
host_config *hc = &tmp_config[i - 1];
if (len > 8 && strncmp(ptr, "webroot", 7) == 0 && (ptr[7] == ' ' || ptr[7] == '\t')) {
source = ptr + 7;
target = hc->local.webroot;
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
goto err;
} else {
hc->type = CONFIG_TYPE_LOCAL;
}
} else if (len > 9 && strncmp(ptr, "dir_mode", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) {
source = ptr + 8;
target = NULL;
mode = 1;
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
goto err;
} else {
hc->type = CONFIG_TYPE_LOCAL;
}
} else if (len > 9 && strncmp(ptr, "hostname", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) {
source = ptr + 8;
target = hc->rev_proxy.hostname;
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
}
} else if (len > 5 && strncmp(ptr, "port", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
source = ptr + 4;
target = NULL;
mode = 2;
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
}
} else if (strcmp(ptr, "http") == 0) {
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
hc->rev_proxy.enc = 0;
}
continue;
} else if (strcmp(ptr, "https") == 0) {
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
hc->rev_proxy.enc = 1;
}
continue;
}
}
char *end_ptr = source + strlen(source) - 1;
while (source[0] == ' ' || source[0] == '\t') source++;
while (end_ptr[0] == ' ' || end_ptr[0] == '\t') end_ptr--;
if (end_ptr <= source) {
err:
free(conf);
free(tmp_config);
fprintf(stderr, ERR_STR "Unable to parse config file" CLR_STR "\n");
return -2;
}
end_ptr[1] = 0;
if (target != NULL) {
strcpy(target, source);
} else if (mode == 1) {
if (strcmp(source, "forbidden") == 0) {
tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN;
} else if (strcmp(source, "info") == 0) {
tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_INFO;
} else if (strcmp(source, "list") == 0) {
tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_LIST;
} else {
goto err;
}
} else if (mode == 2) {
tmp_config[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10);
}
}
free(conf);
int shm_id = shmget(SHM_KEY_CONFIG, 0, 0);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
shmdt(config);
return -3;
}
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
free(tmp_config);
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
return -4;
}
memcpy(shm_rw, tmp_config, MAX_HOST_CONFIG * sizeof(host_config));
free(tmp_config);
shmdt(shm_rw);
return 0;
}

44
src/config.h Normal file
View File

@ -0,0 +1,44 @@
/**
* Necronda Web Server
* Configuration file loader (header file)
* src/config.h
* Lorenz Stechauner, 2021-01-05
*/
#ifndef NECRONDA_SERVER_CONFIG_H
#define NECRONDA_SERVER_CONFIG_H
#define CONFIG_TYPE_UNSET 0
#define CONFIG_TYPE_LOCAL 1
#define CONFIG_TYPE_REVERSE_PROXY 2
typedef struct {
int type;
char name[256];
union {
struct {
char hostname[256];
unsigned short port;
unsigned char enc:1;
} rev_proxy;
struct {
char webroot[256];
unsigned char dir_mode:2;
} local;
};
} host_config;
host_config *config;
char cert_file[256], key_file[256], geoip_dir[256], dns_server[256];
int config_init();
int config_load(const char *filename);
int config_unload();
#endif //NECRONDA_SERVER_CONFIG_H

View File

@ -118,17 +118,8 @@ int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_n
addr = (struct sockaddr_in6 *) &addr_storage;
sprintf(buf0, "%i", addr->sin6_port);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_PORT", buf0);
char addr_str[INET6_ADDRSTRLEN];
char *addr_ptr;
inet_ntop(addr->sin6_family, (void *) &addr->sin6_addr, addr_str, INET6_ADDRSTRLEN);
if (strncmp(addr_str, "::ffff:", 7) == 0) {
addr_ptr = addr_str + 7;
} else {
addr_ptr = addr_str;
}
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_ADDR", addr_ptr);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_HOST", addr_ptr);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_ADDR", client_addr_str);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_HOST", client_host_str != NULL ? client_host_str : client_addr_str);
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_IDENT", "");
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_USER", "");
@ -151,6 +142,9 @@ int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_n
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_LENGTH", content_length != NULL ? content_length : "");
char *content_type = http_get_header_field(&req->hdr, "Content-Type");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_TYPE", content_type != NULL ? content_type : "");
if (client_geoip != NULL) {
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", client_geoip);
}
for (int i = 0; i < req->hdr.field_num; i++) {
char *ptr = buf0;
@ -287,12 +281,12 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
return -1;
return 1;
} else if (ret != sizeof(header)) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
return -1;
return 1;
}
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
@ -303,13 +297,13 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
free(content);
return -1;
return 1;
} else if (ret != (content_len + header.paddingLength)) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
free(content);
return -1;
return 1;
}
if (req_id != conn->req_id) {
@ -329,7 +323,7 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
close(conn->socket);
conn->socket = 0;
free(content);
return -2;
return 1;
} else if (header.type == FCGI_STDERR) {
err = err || fastcgi_php_error(content, content_len, err_msg);
} else if (header.type == FCGI_STDOUT) {
@ -342,7 +336,7 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
}
if (err) {
res->status = http_get_status(500);
return -3;
return 2;
}
conn->out_buf = content;
@ -458,11 +452,7 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
}
if (flags & FASTCGI_CHUNKED) {
if (client->enc) {
SSL_write(client->ssl, "0\r\n\r\n", 5);
} else {
send(client->socket, "0\r\n\r\n", 5, 0);
}
sock_send(client, "0\r\n\r\n", 5, 0);
}
return 0;
@ -486,15 +476,9 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
}
if (buf_len != 0) {
len = sprintf(buf0, "%X\r\n", buf_len);
if (client->enc) {
if (flags & FASTCGI_CHUNKED) SSL_write(client->ssl, buf0, len);
SSL_write(client->ssl, ptr, buf_len);
if (flags & FASTCGI_CHUNKED) SSL_write(client->ssl, "\r\n", 2);
} else {
if (flags & FASTCGI_CHUNKED) send(client->socket, buf0, len, 0);
send(client->socket, ptr, buf_len, 0);
if (flags & FASTCGI_CHUNKED) send(client->socket, "\r\n", 2, 0);
}
if (flags & FASTCGI_CHUNKED) sock_send(client, buf0, len, 0);
sock_send(client, ptr, buf_len, 0);
if (flags & FASTCGI_CHUNKED) sock_send(client, "\r\n", 2, 0);
}
} while ((flags & FASTCGI_COMPRESS) && strm.avail_out == 0);
if (finish_comp) goto finish;
@ -508,7 +492,7 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
unsigned long rcv_len = 0;
char *buf[16384];
int ret;
long ret;
FCGI_Header header = {
.version = FCGI_VERSION_1,
.type = FCGI_STDIN,
@ -519,20 +503,21 @@ int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
.paddingLength = 0,
.reserved = 0
};
if (client->buf != NULL && client->buf_len - client->buf_off > 0) {
ret = (int) (client->buf_len - client->buf_off);
memcpy(buf, client->buf + client->buf_off, ret);
goto send;
}
while (rcv_len < len) {
if (client->enc) {
ret = SSL_read(client->ssl, buf, sizeof(buf));
if (ret <= 0) {
print(ERR_STR "Unable to receive: %s" CLR_STR, ssl_get_error(client->ssl, rcv_len));
return -1;
}
} else {
ret = recv(client->socket, buf, sizeof(buf), 0);
if (ret <= 0) {
print(ERR_STR "Unable to receive: %s" CLR_STR, strerror(errno));
return -1;
}
ret = sock_recv(client, buf, sizeof(buf), 0);
if (ret <= 0) {
print(ERR_STR "Unable to receive: %s" CLR_STR, sock_strerror(client));
return -1;
}
send:
rcv_len += ret;
header.contentLengthB1 = (ret >> 8) & 0xFF;
header.contentLengthB0 = ret & 0xFF;

View File

@ -72,7 +72,7 @@ int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr)
}
int http_receive_request(sock *client, http_req *req) {
unsigned long rcv_len, len;
long rcv_len, len;
char *ptr, *pos0, *pos1, *pos2;
char buf[CLIENT_MAX_HEADER_SIZE];
memset(buf, 0, sizeof(buf));
@ -82,22 +82,9 @@ int http_receive_request(sock *client, http_req *req) {
req->hdr.field_num = 0;
while (1) {
if (client->enc) {
rcv_len = SSL_read(client->ssl, buf, CLIENT_MAX_HEADER_SIZE);
if (rcv_len < 0) {
print(ERR_STR "Unable to receive: %s" CLR_STR, ssl_get_error(client->ssl, rcv_len));
return -1;
}
} else {
rcv_len = recv(client->socket, buf, CLIENT_MAX_HEADER_SIZE, 0);
if (rcv_len < 0) {
print(ERR_STR "Unable to receive: %s" CLR_STR, strerror(errno));
return -1;
}
}
if (rcv_len == 0) {
print("Unable to receive: closed");
rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE, 0);
if (rcv_len <= 0) {
print("Unable to receive: %s", sock_strerror(client));
return -1;
}
@ -115,7 +102,7 @@ int http_receive_request(sock *client, http_req *req) {
}
ptr = buf;
while (header_len != (ptr - buf)) {
while (header_len > (ptr - buf + 2)) {
pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
@ -123,29 +110,22 @@ int http_receive_request(sock *client, http_req *req) {
}
if (req->version[0] == 0) {
if (memcmp(ptr, "GET ", 4) == 0) {
strcpy(req->method, "GET");
} else if (memcmp(ptr, "HEAD ", 5) == 0) {
strcpy(req->method, "HEAD");
} else if (memcmp(ptr, "POST ", 5) == 0) {
strcpy(req->method, "POST");
} else if (memcmp(ptr, "PUT ", 4) == 0) {
strcpy(req->method, "PUT");
} else if (memcmp(ptr, "DELETE ", 7) == 0) {
strcpy(req->method, "DELETE");
} else if (memcmp(ptr, "CONNECT ", 7) == 0) {
strcpy(req->method, "CONNECT");
} else if (memcmp(ptr, "OPTIONS ", 7) == 0) {
strcpy(req->method, "OPTIONS");
} else if (memcmp(ptr, "TRACE ", 6) == 0) {
strcpy(req->method, "TRACE");
} else {
print(ERR_STR "Unable to parse header: Invalid method" CLR_STR);
pos1 = memchr(ptr, ' ', rcv_len - (ptr - buf)) + 1;
if (pos1 == NULL) goto err_hdr_fmt;
if (pos1 - ptr - 1 >= sizeof(req->method)) {
print(ERR_STR "Unable to parse header: Method name too long" CLR_STR);
return 2;
}
pos1 = memchr(ptr, ' ', rcv_len - (ptr - buf)) + 1;
if (pos1 == NULL) goto err_hdr_fmt;
for (int i = 0; i < (pos1 - ptr - 1); i++) {
if (ptr[i] < 'A' || ptr[i] > 'Z') {
print(ERR_STR "Unable to parse header: Invalid method" CLR_STR);
return 2;
}
}
strncpy(req->method, ptr, pos1 - ptr - 1);
pos2 = memchr(pos1, ' ', rcv_len - (pos1 - buf)) + 1;
if (pos2 == NULL) {
err_hdr_fmt:
@ -166,12 +146,21 @@ int http_receive_request(sock *client, http_req *req) {
int ret = http_parse_header_field(&req->hdr, ptr, pos0);
if (ret != 0) return ret;
}
if (pos0[2] == '\r' && pos0[3] == '\n') {
return 0;
}
ptr = pos0 + 2;
}
if (pos0[2] == '\r' && pos0[3] == '\n') {
break;
}
}
client->buf_len = rcv_len - (pos0 - buf + 4);
if (client->buf_len > 0) {
client->buf = malloc(client->buf_len);
client->buf_off = 0;
memcpy(client->buf, pos0 + 4, client->buf_len);
}
return 0;
}
char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
@ -205,7 +194,14 @@ void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
char field_name_1[256], field_name_2[256];
strcpy(field_name_1, field_name);
http_to_camel_case(field_name_1, HTTP_LOWER);
for (int i = 0; i < hdr->field_num; i++) {
int i = 0;
int diff = 1;
if (mode == HTTP_REMOVE_LAST) {
i = hdr->field_num - 1;
diff = -1;
}
for (; i < hdr->field_num && i >= 0; i += diff) {
strcpy(field_name_2, hdr->fields[i][0]);
http_to_camel_case(field_name_2, HTTP_LOWER);
if (strcmp(field_name_1, field_name_2) == 0) {
@ -213,10 +209,10 @@ void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
memcpy(hdr->fields[j], hdr->fields[j + 1], sizeof(hdr->fields[0]));
}
hdr->field_num--;
if (mode == HTTP_REMOVE_ONE) {
if (mode == HTTP_REMOVE_ALL) {
i -= diff;
} else {
return;
} else if (mode == HTTP_REMOVE_ALL) {
i--;
}
}
}
@ -224,21 +220,28 @@ void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
int http_send_response(sock *client, http_res *res) {
char buf[CLIENT_MAX_HEADER_SIZE];
int len = 0;
int snd_len = 0;
len += sprintf(buf + len, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
long off = sprintf(buf, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
for (int i = 0; i < res->hdr.field_num; i++) {
len += sprintf(buf + len, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]);
off += sprintf(buf + off, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]);
}
len += sprintf(buf + len, "\r\n");
if (client->enc) {
snd_len = SSL_write(client->ssl, buf, len);
} else {
snd_len = send(client->socket, buf, len, 0);
off += sprintf(buf + off, "\r\n");
if (sock_send(client, buf, off, 0) < 0) {
return -1;
}
return 0;
}
int http_send_request(sock *server, http_req *req) {
char buf[CLIENT_MAX_HEADER_SIZE];
long off = sprintf(buf, "%s %s HTTP/%s\r\n", req->method, req->uri, req->version);
for (int i = 0; i < req->hdr.field_num; i++) {
off += sprintf(buf + off, "%s: %s\r\n", req->hdr.fields[i][0], req->hdr.fields[i][1]);
}
off += sprintf(buf + off, "\r\n");
long ret = sock_send(server, buf, off, 0);
if (ret <= 0) {
return -1;
}
return 0;
}

View File

@ -14,6 +14,7 @@
#define HTTP_REMOVE_ONE 0
#define HTTP_REMOVE_ALL 1
#define HTTP_REMOVE_LAST 2
typedef struct {
unsigned short code;
@ -32,7 +33,7 @@ typedef struct {
} http_hdr;
typedef struct {
char method[8];
char method[16];
char *uri;
char version[3];
http_hdr hdr;
@ -123,37 +124,49 @@ const char *http_default_document =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <title>%1$i %2$s</title>\n"
" <meta charset=\"UTF-8\"/>\n"
" <meta name=\"theme-color\" content=\"%6$s\"/>\n"
" <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"/>\n"
" <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n"
"\t<title>%1$i %2$s - %7$s</title>\n"
"\t<meta charset=\"UTF-8\"/>\n"
"\t<meta name=\"theme-color\" content=\"%6$s\"/>\n"
"\t<meta name=\"color-scheme\" content=\"light dark\"/>\n"
"\t<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"/>\n"
"\t<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n"
"%5$s"
" <style>\n"
" html{font-family:\"Arial\",sans-serif;--error:#C00000;--info:#E0C000;--color:var(--%4$s);}\n"
" body{background-color:#F0F0F0;margin:0.5em;}\n"
" main{max-width:600px;margin:2em auto;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em 2em;}\n"
" h1,h2,h3,h4,h5,h6,h7{text-align:center;color:var(--color);}\n"
" h1{margin:0.5em 0;font-size:1.5em;}\n"
" p{text-align:center;}\n"
" div.footer{color:#808080;font-size:0.75em;text-align:center;margin:0.5em 0;}\n"
" </style>\n"
"\t<style>\n"
"\t\thtml{font-family:\"Arial\",sans-serif;--error:#C00000;--info:#E0C000;--color:var(--%4$s);}\n"
"\t\tbody{background-color:#F0F0F0;margin:0.5em;}\n"
"\t\tmain{max-width:650px;margin:2em auto;}\n"
"\t\tsection{margin:1em;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em 2em;}\n"
"\t\th1,h2,h3,h4,h5,h6,h7{text-align:center;color:var(--color);font-weight:normal;}\n"
"\t\th1{font-size:3em;margin:0.125em 0 0.125em 0;}\n"
"\t\th2{font-size:1.5em;margin:0.25em 0 1em 0;}\n"
"\t\tp{text-align:center;font-size:0.875em;}\n"
"\t\tdiv.footer{color:#808080;font-size:0.75em;text-align:center;margin:2em 0 0.5em 0;}\n"
"\t\tdiv.footer a{color:#808080;}\n"
"\t\t@media(prefers-color-scheme:dark){\n"
"\t\t\thtml{color:#FFFFFF;}\n"
"\t\t\tbody{background-color:#101010;}\n"
"\t\t\tsection{background-color:#181818;}\n"
"\t\t}\n"
"\t</style>\n"
"</head>\n"
"<body>\n"
" <main>\n"
"\t<main>\n"
"\t\t<section>\n"
"%3$s"
" <div class=\"footer\">Necronda web server " NECRONDA_VERSION "</div>\n"
" </main>\n"
"\t\t\t<div class=\"footer\"><a href=\"https://%7$s/\">%7$s</a> - Necronda web server " NECRONDA_VERSION "</div>\n"
"\t\t</section>\n"
"\t</main>\n"
"</body>\n"
"</html>\n";
const char *http_error_document =
" <h1>%1$i %2$s :&#xFEFF;(</h1>\n"
" <p>%3$s</p>\n"
" <p>%4$s</p>\n";
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;(</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char *http_error_icon =
" <link rel=\"shortcut icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"\t<link rel=\"shortcut icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNDMDAwMDAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjooPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
@ -179,6 +192,8 @@ void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode);
int http_send_response(sock *client, http_res *res);
int http_send_request(sock *server, http_req *req);
http_status *http_get_status(unsigned short status_code);
http_error_msg *http_get_error_msg(unsigned short status_code);

View File

@ -9,15 +9,19 @@
#include "necronda-server.h"
#include "config.c"
#include "utils.c"
#include "uri.c"
#include "cache.c"
#include "sock.c"
#include "http.c"
#include "rev_proxy.c"
#include "client.c"
#include "fastcgi.c"
int active = 1;
const char *config_file;
void openssl_init() {
@ -27,42 +31,8 @@ void openssl_init() {
OpenSSL_add_all_algorithms();
}
char *ssl_get_error(SSL *ssl, int ret) {
if (ret > 0) {
return NULL;
}
unsigned long ret2 = ERR_get_error();
char *err2 = strerror(errno);
char *err1 = (char *) ERR_reason_error_string(ret2);
switch (SSL_get_error(ssl, ret)) {
case SSL_ERROR_NONE:
return "none";
case SSL_ERROR_ZERO_RETURN:
return "closed";
case SSL_ERROR_WANT_READ:
return "want read";
case SSL_ERROR_WANT_WRITE:
return "want write";
case SSL_ERROR_WANT_CONNECT:
return "want connect";
case SSL_ERROR_WANT_ACCEPT:
return "want accept";
case SSL_ERROR_WANT_X509_LOOKUP:
return "want x509 lookup";
case SSL_ERROR_SYSCALL:
return ((ret2 == 0) ? ((ret == 0) ? "protocol violation" : err2) : err1);
case SSL_ERROR_SSL:
return err1;
default:
return "unknown error";
}
}
void destroy() {
fprintf(stderr, "\n" ERR_STR "Terminating forcefully!" CLR_STR "\n");
fflush(stderr);
int status = 0;
int ret;
int kills = 0;
@ -72,13 +42,11 @@ void destroy() {
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
fflush(stderr);
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
fflush(stderr);
}
} else {
kill(children[i], SIGKILL);
@ -88,15 +56,14 @@ void destroy() {
}
if (kills > 0) {
fprintf(stderr, ERR_STR "Killed %i child process(es)" CLR_STR "\n", kills);
fflush(stderr);
}
cache_unload();
config_unload();
exit(2);
}
void terminate() {
fprintf(stderr, "\nTerminating gracefully...\n");
fflush(stderr);
active = 0;
signal(SIGINT, destroy);
@ -116,13 +83,11 @@ void terminate() {
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
fflush(stderr);
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
fflush(stderr);
}
} else {
kill(children[i], SIGTERM);
@ -133,7 +98,6 @@ void terminate() {
if (wait_num > 0) {
fprintf(stderr, "Waiting for %i child process(es)...\n", wait_num);
fflush(stderr);
}
for (int i = 0; i < MAX_CHILDREN; i++) {
@ -142,13 +106,11 @@ void terminate() {
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
fflush(stderr);
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
fflush(stderr);
}
}
}
@ -161,12 +123,11 @@ void terminate() {
struct timespec ts = {.tv_sec = 0, .tv_nsec = 50000000};
nanosleep(&ts, &ts);
fprintf(stderr, "\nGoodbye\n");
fflush(stderr);
} else {
fprintf(stderr, "Goodbye\n");
fflush(stderr);
}
cache_unload();
config_unload();
exit(0);
}
@ -184,6 +145,10 @@ int main(int argc, const char *argv[]) {
struct sockaddr_in6 client_addr;
unsigned int client_addr_len = sizeof(client_addr);
memset(sockets, 0, sizeof(sockets));
memset(children, 0, sizeof(children));
memset(mmdbs, 0, sizeof(mmdbs));
struct timeval timeout;
const struct sockaddr_in6 addresses[2] = {
@ -191,70 +156,45 @@ int main(int argc, const char *argv[]) {
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(443)}
};
printf("Necronda Web Server\n");
fflush(stdout);
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
fprintf(stderr, ERR_STR "Unable to set stdout to line-buffered mode: %s" CLR_STR, strerror(errno));
return 1;
}
printf("Necronda Web Server " NECRONDA_VERSION "\n");
ret = config_init();
if (ret != 0) {
return 1;
}
config_file = NULL;
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
unsigned long len = strlen(arg);
if ((len == 2 && strncmp(arg, "-h", 2) == 0) || (len == 6 && strncmp(arg, "--help", 6) == 0)) {
printf("Usage: necronda-server [-h] -w <PATH> -c <CERT-FILE> -p <KEY-FILE> [-g <DB-FILE>]\n"
if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
printf("Usage: necronda-server [-h] [-c <CONFIG-FILE>]\n"
"\n"
"Options:\n"
" -c, --cert <CERT-FILE> path to the full chain certificate file\n"
" -g, --geoip <DB-FILE> path to a Maxmind GeoIP Database file\n"
" -h, --help print this dialogue\n"
" -p, --privkey <KEY-FILE> path to the private key file\n"
" -w, --webroot <PATH> path to the web root directory\n");
" -c, --config <CONFIG-FILE> path to the config file. If not provided, default will be used\n"
" -h, --help print this dialogue\n");
config_unload();
return 0;
} else if ((len == 2 && strncmp(arg, "-w", 2) == 0) || (len == 9 && strncmp(arg, "--webroot", 9) == 0)) {
} else if (strcmp(arg, "-c") == 0 || strcmp(arg, "--config") == 0) {
if (i == argc - 1) {
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --webroot <WEBROOT>" CLR_STR "\n", arg);
fflush(stderr);
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --config <CONFIG-FILE>" CLR_STR "\n", arg);
config_unload();
return 1;
}
webroot_base = argv[++i];
} else if ((len == 2 && strncmp(arg, "-c", 2) == 0) || (len == 6 && strncmp(arg, "--cert", 6) == 0)) {
if (i == argc - 1) {
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --cert <CERT-FILE>" CLR_STR "\n", arg);
fflush(stderr);
return 1;
}
cert_file = argv[++i];
} else if ((len == 2 && strncmp(arg, "-p", 2) == 0) || (len == 9 && strncmp(arg, "--privkey", 9) == 0)) {
if (i == argc - 1) {
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --privkey <KEY-FILE>" CLR_STR "\n", arg);
fflush(stderr);
return 1;
}
key_file = argv[++i];
} else if ((len == 2 && strncmp(arg, "-g", 2) == 0) || (len == 7 && strncmp(arg, "--geoip", 7) == 0)) {
if (i == argc - 1) {
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --geoip <DB-FILE>" CLR_STR "\n", arg);
fflush(stderr);
return 1;
}
geoip_file = argv[++i];
config_file = argv[++i];
} else {
fprintf(stderr, ERR_STR "Unable to parse argument '%s'" CLR_STR "\n", arg);
fflush(stderr);
config_unload();
return 1;
}
}
if (webroot_base == NULL) {
fprintf(stderr, ERR_STR "Error: --webroot is missing" CLR_STR "\n");
fflush(stderr);
return 1;
}
if (cert_file == NULL) {
fprintf(stderr, ERR_STR "Error: --cert is missing" CLR_STR "\n");
fflush(stderr);
return 1;
}
if (key_file == NULL) {
fprintf(stderr, ERR_STR "Error: --privkey is missing" CLR_STR "\n");
fflush(stderr);
ret = config_load(config_file == NULL ? DEFAULT_CONFIG_FILE : config_file);
if (ret != 0) {
config_unload();
return 1;
}
@ -264,14 +204,14 @@ int main(int argc, const char *argv[]) {
if (sockets[1] < 0) {
socket_err:
fprintf(stderr, ERR_STR "Unable to create socket: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
config_unload();
return 1;
}
for (int i = 0; i < NUM_SOCKETS; i++) {
if (setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(YES)) < 0) {
fprintf(stderr, ERR_STR "Unable to set options for socket %i: %s" CLR_STR "\n", i, strerror(errno));
fflush(stderr);
config_unload();
return 1;
}
}
@ -280,24 +220,51 @@ int main(int argc, const char *argv[]) {
if (bind(sockets[1], (struct sockaddr *) &addresses[1], sizeof(addresses[1])) < 0) {
bind_err:
fprintf(stderr, ERR_STR "Unable to bind socket to address: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
config_unload();
return 1;
}
signal(SIGINT, terminate);
signal(SIGTERM, terminate);
ret = cache_init();
if (ret < 0) {
return 1;
} else if (ret != 0) {
return 0;
if (geoip_dir[0] != 0) {
DIR *geoip = opendir(geoip_dir);
if (geoip == NULL) {
fprintf(stderr, ERR_STR "Unable to open GeoIP dir: %s" CLR_STR "\n", strerror(errno));
config_unload();
return 1;
}
struct dirent *dir;
int i = 0;
while ((dir = readdir(geoip)) != NULL) {
if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".mmdb") != 0) continue;
if (i >= MAX_MMDB) {
fprintf(stderr, ERR_STR "Too many .mmdb files" CLR_STR "\n");
config_unload();
return 1;
}
sprintf(buf, "%s/%s", geoip_dir, dir->d_name);
ret = MMDB_open(buf, 0, &mmdbs[i]);
if (ret != MMDB_SUCCESS) {
fprintf(stderr, ERR_STR "Unable to open .mmdb file: %s" CLR_STR "\n", MMDB_strerror(ret));
config_unload();
return 1;
}
i++;
}
if (i == 0) {
fprintf(stderr, ERR_STR "No .mmdb files found in %s" CLR_STR "\n", geoip_dir);
config_unload();
return 1;
}
closedir(geoip);
}
// TODO init geoip database
openssl_init();
client.buf = NULL;
client.buf_len = 0;
client.buf_off = 0;
client.ctx = SSL_CTX_new(TLS_server_method());
SSL_CTX_set_options(client.ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_verify(client.ctx, SSL_VERIFY_NONE, NULL);
@ -306,23 +273,28 @@ int main(int argc, const char *argv[]) {
SSL_CTX_set_cipher_list(client.ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
SSL_CTX_set_ecdh_auto(client.ctx, 1);
rev_proxy.buf = NULL;
rev_proxy.buf_len = 0;
rev_proxy.buf_off = 0;
rev_proxy.ctx = SSL_CTX_new(TLS_client_method());
if (SSL_CTX_use_certificate_chain_file(client.ctx, cert_file) != 1) {
fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), cert_file);
fflush(stderr);
config_unload();
return 1;
}
if (SSL_CTX_use_PrivateKey_file(client.ctx, key_file, SSL_FILETYPE_PEM) != 1) {
fprintf(stderr, ERR_STR "Unable to load private key file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), key_file);
fflush(stderr);
config_unload();
return 1;
}
for (int i = 0; i < NUM_SOCKETS; i++) {
if (listen(sockets[i], LISTEN_BACKLOG) < 0) {
fprintf(stderr, ERR_STR "Unable to listen on socket %i: %s" CLR_STR "\n", i, strerror(errno));
fflush(stderr);
config_unload();
return 1;
}
}
@ -335,8 +307,15 @@ int main(int argc, const char *argv[]) {
}
}
ret = cache_init();
if (ret < 0) {
config_unload();
return 1;
} else if (ret != 0) {
return 0;
}
fprintf(stderr, "Ready to accept connections\n");
fflush(stderr);
while (active) {
timeout.tv_sec = 1;
@ -345,7 +324,7 @@ int main(int argc, const char *argv[]) {
ready_sockets_num = select(max_socket_fd + 1, &read_socket_fds, NULL, NULL, &timeout);
if (ready_sockets_num < 0) {
fprintf(stderr, ERR_STR "Unable to select sockets: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
terminate();
return 1;
}
@ -354,7 +333,6 @@ int main(int argc, const char *argv[]) {
client_fd = accept(sockets[i], (struct sockaddr *) &client_addr, &client_addr_len);
if (client_fd < 0) {
fprintf(stderr, ERR_STR "Unable to accept connection: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
continue;
}
@ -379,7 +357,6 @@ int main(int argc, const char *argv[]) {
}
} else {
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
fflush(stderr);
}
}
}
@ -391,13 +368,11 @@ int main(int argc, const char *argv[]) {
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
fflush(stderr);
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
fflush(stderr);
}
}
}

View File

@ -25,23 +25,32 @@
#include <openssl/conf.h>
#include <openssl/engine.h>
#include <openssl/dh.h>
#include <maxminddb.h>
#include <dirent.h>
#define NUM_SOCKETS 2
#define MAX_CHILDREN 1024
#define MAX_MMDB 3
#define MAX_HOST_CONFIG 64
#define LISTEN_BACKLOG 16
#define REQ_PER_CONNECTION 100
#define CLIENT_TIMEOUT 3600
#define SERVER_TIMEOUT 4
#define CHUNK_SIZE 4096
#define CHUNK_SIZE 8192
#define CLIENT_MAX_HEADER_SIZE 8192
#define FILE_CACHE_SIZE 1024
#define SHM_KEY 255641
#define GEOIP_MAX_SIZE 8192
#define SHM_KEY_CACHE 255641
#define SHM_KEY_CONFIG 255642
#define ERR_STR "\x1B[1;31m"
#define CLR_STR "\x1B[0m"
#define BLD_STR "\x1B[1m"
#define WRN_STR "\x1B[1;33m"
#define BLUE_STR "\x1B[34m"
#define HTTP_STR "\x1B[1;31m"
#define HTTPS_STR "\x1B[1;32m"
@ -51,30 +60,27 @@
#define HTTP_4XX_STR "\x1B[1;31m"
#define HTTP_5XX_STR "\x1B[1;31m"
#define NECRONDA_VERSION "4.0"
#define NECRONDA_VERSION "4.2"
#define SERVER_STR "Necronda/" NECRONDA_VERSION
#define NECRONDA_DEFAULT "www.necronda.net"
#define NECRONDA_ZLIB_LEVEL 9
#ifndef DEFAULT_HOST
#define DEFAULT_HOST "www.necronda.net"
#endif
#ifndef MAGIC_FILE
#define MAGIC_FILE "/usr/share/file/misc/magic.mgc"
#endif
#ifndef PHP_FPM_SOCKET
#define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock"
#endif
#ifndef DEFAULT_CONFIG_FILE
#define DEFAULT_CONFIG_FILE "/etc/necronda-server/necronda-server.conf"
#endif
int sockets[NUM_SOCKETS];
pid_t children[MAX_CHILDREN];
MMDB_s mmdbs[MAX_MMDB];
const char *cert_file, *key_file, *webroot_base, *geoip_file;
typedef struct {
unsigned int enc:1;
int socket;
SSL_CTX *ctx;
SSL *ssl;
} sock;
char *ssl_get_error(SSL *ssl, int ret);
char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr, *client_host_str;
#endif //NECRONDA_SERVER_NECRONDA_SERVER_H

274
src/rev_proxy.c Normal file
View File

@ -0,0 +1,274 @@
/**
* Necronda Web Server
* Reverse proxy
* src/rev_proxy.c
* Lorenz Stechauner, 2021-01-07
*/
#include "rev_proxy.h"
sock rev_proxy;
char *rev_proxy_host = NULL;
struct timeval server_timeout = {.tv_sec = SERVER_TIMEOUT, .tv_usec = 0};
int rev_proxy_init(http_req *req, http_res *res, host_config *conf, sock *client, http_status *custom_status,
char * err_msg) {
char buffer[CHUNK_SIZE];
long ret;
int tries = 0;
int retry = 0;
if (rev_proxy.socket != 0 && strcmp(rev_proxy_host, conf->name) == 0 && sock_check(&rev_proxy) == 0) {
goto rev_proxy;
}
retry:
if (rev_proxy.socket != 0) {
print(BLUE_STR "Closing proxy connection" CLR_STR);
sock_close(&rev_proxy);
}
retry = 0;
tries++;
rev_proxy.socket = socket(AF_INET6, SOCK_STREAM, 0);
if (rev_proxy.socket < 0) {
print(ERR_STR "Unable to create socket: %s" CLR_STR, strerror(errno));
res->status = http_get_status(500);
return -1;
}
server_timeout.tv_sec = SERVER_TIMEOUT;
server_timeout.tv_usec = 0;
if (setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
goto rev_proxy_timeout_err;
if (setsockopt(client->socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0) {
rev_proxy_timeout_err:
res->status = http_get_status(502);
print(ERR_STR "Unable to set timeout for socket: %s" CLR_STR, strerror(errno));
sprintf(err_msg, "Unable to set timeout for socket: %s", strerror(errno));
goto proxy_err;
}
struct hostent *host_ent = gethostbyname(conf->rev_proxy.hostname);
if (host_ent == NULL) {
res->status = http_get_status(502);
print(ERR_STR "Unable to connect to server: Name or service not known" CLR_STR);
sprintf(err_msg, "Unable to connect to server: Name or service not known.");
goto proxy_err;
}
struct sockaddr_in6 address = {.sin6_family = AF_INET6, .sin6_port = htons(conf->rev_proxy.port)};
if (host_ent->h_addrtype == AF_INET6) {
memcpy(&address.sin6_addr, host_ent->h_addr_list[0], host_ent->h_length);
} else if (host_ent->h_addrtype == AF_INET) {
unsigned char addr[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0};
memcpy(addr + 12, host_ent->h_addr_list[0], host_ent->h_length);
memcpy(&address.sin6_addr, addr, 16);
}
if (connect(rev_proxy.socket, (struct sockaddr *) &address, sizeof(address)) < 0) {
res->status = http_get_status(502);
print(ERR_STR "Unable to connect to server: %s" CLR_STR, strerror(errno));
sprintf(err_msg, "Unable to connect to server: %s.", strerror(errno));
goto proxy_err;
}
if (conf->rev_proxy.enc) {
rev_proxy.ssl = SSL_new(rev_proxy.ctx);
SSL_set_fd(rev_proxy.ssl, rev_proxy.socket);
SSL_set_connect_state(rev_proxy.ssl);
ret = SSL_do_handshake(rev_proxy.ssl);
rev_proxy._last_ret = ret;
rev_proxy._errno = errno;
rev_proxy._ssl_error = ERR_get_error();
rev_proxy.enc = 1;
if (ret < 0) {
res->status = http_get_status(502);
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to perform handshake: %s.", sock_strerror(&rev_proxy));
goto proxy_err;
}
}
rev_proxy_host = conf->name;
inet_ntop(address.sin6_family, (void *) &address.sin6_addr, buffer, sizeof(buffer));
print(BLUE_STR "Established new connection with " BLD_STR "[%s]:%i" CLR_STR, buffer, conf->rev_proxy.port);
rev_proxy:
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "Connection", "keep-alive");
http_remove_header_field(&req->hdr, "X-Forwarded-For", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "X-Forwarded-For", client_addr_str);
ret = http_send_request(&rev_proxy, req);
if (ret < 0) {
res->status = http_get_status(502);
print(ERR_STR "Unable to send request to server (1): %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
retry = tries < 4;
goto proxy_err;
}
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
if (content_length != NULL) {
unsigned long content_len = strtoul(content_length, NULL, 10);
if (client->buf_len - client->buf_off > 0) {
unsigned long len = client->buf_len - client->buf_off;
if (len > content_len) {
len = content_len;
}
ret = sock_send(&rev_proxy, client->buf, len, 0);
if (ret <= 0) {
res->status = http_get_status(502);
print(ERR_STR "Unable to send request to server (2): %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
retry = tries < 4;
goto proxy_err;
}
content_len -= len;
}
if (content_len > 0) {
ret = sock_splice(&rev_proxy, client, buffer, sizeof(buffer), content_len);
if (ret <= 0) {
if (ret == -1) {
res->status = http_get_status(502);
print(ERR_STR "Unable to send request to server (3): %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
goto proxy_err;
} else if (ret == -2) {
res->status = http_get_status(400);
print(ERR_STR "Unable to receive request from client: %s" CLR_STR, sock_strerror(client));
sprintf(err_msg, "Unable to receive request from client: %s.", sock_strerror(client));
return -1;
}
res->status = http_get_status(500);
print(ERR_STR "Unknown Error" CLR_STR);
return -1;
}
}
}
ret = sock_recv(&rev_proxy, buffer, sizeof(buffer), MSG_PEEK);
if (ret <= 0) {
res->status = http_get_status(502);
print(ERR_STR "Unable to receive response from server: %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to receive response from server: %s.", sock_strerror(&rev_proxy));
goto proxy_err;
}
char *buf = buffer;
unsigned short header_len = (unsigned short) (strstr(buffer, "\r\n\r\n") - buffer + 4);
if (header_len <= 0) {
res->status = http_get_status(502);
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
sprintf(err_msg, "Unable to parser header: End of header not found.");
goto proxy_err;
}
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
res->status = http_get_status(502);
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
goto proxy_err;
}
}
char *ptr = buf;
while (header_len != (ptr - buf)) {
char *pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
res->status = http_get_status(502);
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
sprintf(err_msg, "Unable to parse header: Invalid header format.");
goto proxy_err;
}
if (ptr == buf) {
if (strncmp(ptr, "HTTP/", 5) != 0) {
res->status = http_get_status(502);
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
sprintf(err_msg, "Unable to parse header: Invalid header format.");
goto proxy_err;
}
int status_code = (int) strtol(ptr + 9, NULL, 10);
res->status = http_get_status(status_code);
if (res->status == NULL && status_code >= 100 && status_code <= 999) {
custom_status->code = status_code;
strcpy(custom_status->type, "");
strncpy(custom_status->msg, ptr + 13, strchr(ptr, '\r') - ptr - 13);
res->status = custom_status;
} else if (res->status == NULL) {
res->status = http_get_status(502);
print(ERR_STR "Unable to parse header: Invalid or unknown status code" CLR_STR);
sprintf(err_msg, "Unable to parse header: Invalid or unknown status code.");
goto proxy_err;
}
} else {
ret = http_parse_header_field(&res->hdr, ptr, pos0);
if (ret != 0) {
res->status = http_get_status(502);
print(ERR_STR "Unable to parse header" CLR_STR);
sprintf(err_msg, "Unable to parse header.");
goto proxy_err;
}
}
if (pos0[2] == '\r' && pos0[3] == '\n') {
break;
}
ptr = pos0 + 2;
}
sock_recv(&rev_proxy, buffer, header_len, 0);
return 0;
proxy_err:
if (retry) goto retry;
return -1;
}
int rev_proxy_send(sock *client, int chunked, unsigned long len_to_send) {
long ret;
char buffer[CHUNK_SIZE];
long len, snd_len;
// TODO handle websockets
do {
if (chunked) {
ret = sock_recv(&rev_proxy, buffer, 16, MSG_PEEK);
if (ret <= 0) {
print("Unable to receive: %s", sock_strerror(&rev_proxy));
break;
}
len_to_send = strtol(buffer, NULL, 16);
char *pos = strstr(buffer, "\r\n");
len = pos - buffer + 2;
ret = sock_send(client, buffer, len, 0);
sock_recv(&rev_proxy, buffer, len, 0);
if (ret <= 0) break;
}
snd_len = 0;
while (snd_len < len_to_send) {
len = sock_recv(&rev_proxy, buffer, CHUNK_SIZE < (len_to_send - snd_len) ? CHUNK_SIZE : len_to_send - snd_len, 0);
ret = sock_send(client, buffer, len, 0);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
break;
}
snd_len += ret;
}
if (ret <= 0) break;
if (chunked) {
sock_recv(&rev_proxy, buffer, 2, 0);
ret = sock_send(client, "\r\n", 2, 0);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
break;
}
}
} while (chunked && len_to_send > 0);
return 0;
}

11
src/rev_proxy.h Normal file
View File

@ -0,0 +1,11 @@
/**
* Necronda Web Server
* Reverse proxy (header file)
* src/rev_proxy.h
* Lorenz Stechauner, 2021-01-07
*/
#ifndef NECRONDA_SERVER_REV_PROXY_H
#define NECRONDA_SERVER_REV_PROXY_H
#endif //NECRONDA_SERVER_REV_PROXY_H

109
src/sock.c Normal file
View File

@ -0,0 +1,109 @@
/**
* Necronda Web Server
* Basic TCP and TLS socket
* src/sock.c
* Lorenz Stechauner, 2021-01-07
*/
#include "sock.h"
const char *sock_strerror(sock *s) {
if (s->_last_ret == 0) {
return "closed";
} else if (s->enc) {
if (s->_last_ret > 0) {
return NULL;
}
const char *err1 = ERR_reason_error_string(s->_ssl_error);
const char *err2 = strerror(errno);
switch (SSL_get_error(s->ssl, (int) s->_last_ret)) {
case SSL_ERROR_NONE:
return NULL;
case SSL_ERROR_ZERO_RETURN:
return "closed";
case SSL_ERROR_WANT_READ:
return "want read";
case SSL_ERROR_WANT_WRITE:
return "want write";
case SSL_ERROR_WANT_CONNECT:
return "want connect";
case SSL_ERROR_WANT_ACCEPT:
return "want accept";
case SSL_ERROR_WANT_X509_LOOKUP:
return "want x509 lookup";
case SSL_ERROR_SYSCALL:
return ((s->_ssl_error == 0) ? ((s->_last_ret == 0) ? "protocol violation" : err2) : err1);
case SSL_ERROR_SSL:
return err1;
default:
return "unknown error";
}
} else {
return strerror(s->_errno);
}
}
long sock_send(sock *s, void *buf, unsigned long len, int flags) {
long ret;
if (s->enc) {
ret = SSL_write(s->ssl, buf, (int) len);
} else {
ret = send(s->socket, buf, len, flags);
}
s->_last_ret = ret;
s->_errno = errno;
s->_ssl_error = ERR_get_error();
return ret >= 0 ? ret : -1;
}
long sock_recv(sock *s, void *buf, unsigned long len, int flags) {
long ret;
if (s->enc) {
if (flags & MSG_PEEK) {
ret = SSL_peek(s->ssl, buf, (int) len);
} else {
ret = SSL_read(s->ssl, buf, (int) len);
}
} else {
ret = recv(s->socket, buf, len, flags);
}
s->_last_ret = ret;
s->_errno = errno;
s->_ssl_error = ERR_get_error();
return ret >= 0 ? ret : -1;
}
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len) {
long ret;
unsigned long send_len = 0;
unsigned long next_len;
while (send_len < len) {
next_len = (buf_len < (len - send_len)) ? buf_len : (len - send_len);
ret = sock_recv(src, buf, next_len, 0);
if (ret < 0) return -2;
if (ret != next_len) return -3;
ret = sock_send(dst, buf, next_len, send_len + next_len < len ? MSG_MORE : 0);
if (ret < 0) return -1;
if (ret != next_len) return -3;
send_len += next_len;
}
return (long) send_len;
}
int sock_close(sock *s) {
if ((int) s->enc && s->ssl != NULL) {
SSL_shutdown(s->ssl);
SSL_free(s->ssl);
}
shutdown(s->socket, SHUT_RDWR);
close(s->socket);
s->socket = 0;
s->enc = 0;
s->ssl = NULL;
return 0;
}
int sock_check(sock *s) {
char buf;
return recv(s->socket, &buf, 1, MSG_PEEK | MSG_DONTWAIT) == 1;
}

36
src/sock.h Normal file
View File

@ -0,0 +1,36 @@
/**
* Necronda Web Server
* Basic TCP and TLS socket (header file)
* src/sock.h
* Lorenz Stechauner, 2021-01-07
*/
#ifndef NECRONDA_SERVER_SOCK_H
#define NECRONDA_SERVER_SOCK_H
typedef struct {
unsigned int enc:1;
int socket;
SSL_CTX *ctx;
SSL *ssl;
char *buf;
unsigned long buf_len;
unsigned long buf_off;
long _last_ret;
int _errno;
unsigned long _ssl_error;
} sock;
const char *sock_strerror(sock *s);
long sock_send(sock *s, void *buf, unsigned long len, int flags);
long sock_recv(sock *s, void *buf, unsigned long len, int flags);
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len);
int sock_close(sock *s);
int sock_check(sock *s);
#endif //NECRONDA_SERVER_SOCK_H

View File

@ -68,7 +68,21 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
size = strlen(uri->req_path) + 1;
uri->path = malloc(size);
uri->pathinfo = malloc(size);
strcpy(uri->path, uri->req_path);
char last = 0;
for (int i = 0, j = 0; i < size - 1; i++) {
char ch = uri->req_path[i];
if (last != '/' || ch != '/') {
uri->path[j++] = ch;
uri->path[j] = 0;
}
last = ch;
}
if (dir_mode == URI_DIR_MODE_NO_VALIDATION) {
return 0;
}
if (uri->path[strlen(uri->path) - 1] == '/') {
uri->path[strlen(uri->path) - 1] = 0;
strcpy(uri->pathinfo, "/");
@ -101,9 +115,9 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
uri->filename = malloc(strlen(buf0) + 1);
strcpy(uri->filename, buf0);
ssize_t len = strlen(uri->path);
if (strncmp(uri->path + len - 5, ".html", 5) == 0) {
if (strcmp(uri->path + len - 5, ".html") == 0) {
uri->path[len - 5] = 0;
} else if (strncmp(uri->path + len - 4, ".php", 4) == 0) {
} else if (strcmp(uri->path + len - 4, ".php") == 0) {
uri->path[len - 4] = 0;
uri->is_static = 0;
}

View File

@ -10,9 +10,10 @@
#include <sys/stat.h>
#define URI_DIR_MODE_FORBIDDEN 0
#define URI_DIR_MODE_LIST 1
#define URI_DIR_MODE_INFO 2
#define URI_DIR_MODE_NO_VALIDATION 0
#define URI_DIR_MODE_FORBIDDEN 1
#define URI_DIR_MODE_LIST 2
#define URI_DIR_MODE_INFO 3
typedef struct {
char etag[64];

View File

@ -94,3 +94,64 @@ int url_decode(const char *str, char *dec, ssize_t *size) {
*size = ptr - dec;
return 0;
}
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len) {
switch (list->entry_data.type) {
case MMDB_DATA_TYPE_MAP:
*str_off += sprintf(str + *str_off, "{");
break;
case MMDB_DATA_TYPE_ARRAY:
*str_off += sprintf(str + *str_off, "[");
break;
case MMDB_DATA_TYPE_UTF8_STRING:
*str_off += sprintf(str + *str_off, "\"%.*s\"", list->entry_data.data_size, list->entry_data.utf8_string);
break;
case MMDB_DATA_TYPE_UINT16:
*str_off += sprintf(str + *str_off, "%u", list->entry_data.uint16);
break;
case MMDB_DATA_TYPE_UINT32:
*str_off += sprintf(str + *str_off, "%u", list->entry_data.uint32);
break;
case MMDB_DATA_TYPE_UINT64:
*str_off += sprintf(str + *str_off, "%lu", list->entry_data.uint64);
break;
case MMDB_DATA_TYPE_UINT128:
*str_off += sprintf(str + *str_off, "%llu", list->entry_data.uint128);
break;
case MMDB_DATA_TYPE_INT32:
*str_off += sprintf(str + *str_off, "%i", list->entry_data.uint32);
break;
case MMDB_DATA_TYPE_BOOLEAN:
*str_off += sprintf(str + *str_off, "%s", list->entry_data.boolean ? "true" : "false");
break;
case MMDB_DATA_TYPE_FLOAT:
*str_off += sprintf(str + *str_off, "%f", list->entry_data.float_value);
break;
case MMDB_DATA_TYPE_DOUBLE:
*str_off += sprintf(str + *str_off, "%f", list->entry_data.double_value);
break;
}
if (list->entry_data.type != MMDB_DATA_TYPE_MAP && list->entry_data.type != MMDB_DATA_TYPE_ARRAY) {
return list->next;
}
MMDB_entry_data_list_s *next = list->next;
int stat = 0;
for (int i = 0; i < list->entry_data.data_size; i++) {
next = mmdb_json(next, str, str_off, str_len);
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
stat = !stat;
if (stat) {
i--;
*str_off += sprintf(str + *str_off, ":");
continue;
}
}
if (i != list->entry_data.data_size - 1) *str_off += sprintf(str + *str_off, ",");
}
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
*str_off += sprintf(str + *str_off, "}");
} else {
*str_off += sprintf(str + *str_off, "]");
}
return next;
}

View File

@ -17,7 +17,7 @@ char *log_prefix;
#define print(...) out_x(, ##__VA_ARGS__, out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
out_2(__VA_ARGS__), out_1(__VA_ARGS__)); fflush(stdout)
out_2(__VA_ARGS__), out_1(__VA_ARGS__))
char *format_duration(unsigned long micros, char *buf);