Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
43c512dc5a
|
|||
3f5eee236d
|
|||
0b157bcb74
|
|||
30b163c6fa
|
|||
cf2c0de697
|
|||
7aa47cac61
|
|||
456deeae20
|
|||
b08481818c
|
|||
49ad349775
|
|||
2b823cabd6
|
|||
ecd4f16afe
|
|||
45c5f20345
|
|||
c42f27e961
|
|||
de8ab406f6
|
|||
22d50ed4bd
|
|||
de44f4a3fe
|
|||
cc29250d76
|
|||
c2f8f4c962
|
|||
ff708230bd
|
|||
52ea541833
|
|||
f4bd426f3c
|
|||
ffbbcc6490
|
|||
80986325ce
|
|||
10d405e745
|
|||
28f163f97a
|
|||
a8914aa981
|
|||
2ada22481d
|
|||
17b25a3596
|
|||
d130474989
|
|||
8dea0cd3fc
|
|||
0406cad0d8
|
|||
21b7ab585a
|
|||
7587e15749
|
|||
1b44752f91
|
|||
5eeb9ef3c1
|
|||
dd15b9d906
|
|||
12922a0661
|
|||
c0799101b1
|
|||
c1d076db04
|
|||
10464f3f30
|
|||
63781472fa
|
|||
81931d287d
|
|||
531ddb4880
|
|||
e0d8ab31d5
|
|||
3a36d54e9d
|
|||
33d9aa3a5d
|
|||
f4d30206b0
|
|||
5b094ba98d
|
|||
f60cdc8228
|
|||
ab1c4d6fd4
|
|||
26d54e9968
|
|||
b6c7d8f58e
|
|||
53fcceeafb
|
|||
96567909db
|
|||
4b3c067a75
|
|||
77b80ca67b
|
|||
70e76d8783
|
|||
6b1bc54cf3
|
|||
e1edb48a3c
|
|||
dc5d1bebcc
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,7 +1,8 @@
|
||||
*
|
||||
!src
|
||||
!src/**
|
||||
!run.sh
|
||||
!docs
|
||||
!docs/**
|
||||
!Makefile
|
||||
!.gitignore
|
||||
!README.md
|
||||
|
27
Makefile
27
Makefile
@ -1,19 +1,28 @@
|
||||
.DEFAULT_GOAL := install
|
||||
|
||||
CFLAGS=-std=c11 -Wall
|
||||
INCLUDE=-lssl -lcrypto -lmagic -lz -lmaxminddb -lbrotlienc
|
||||
LIBS=src/lib/*.c
|
||||
|
||||
DEBIAN_OPTS=-D CACHE_MAGIC_FILE="\"/usr/share/file/magic.mgc\"" -D PHP_FPM_SOCKET="\"/var/run/php/php7.3-fpm.sock\""
|
||||
|
||||
packages:
|
||||
@echo "Installing packages..."
|
||||
sudo apt-get install gcc libmagic-dev libssl-dev php-fpm
|
||||
sudo apt install gcc php-fpm libmagic-dev libssl-dev libmaxminddb-dev
|
||||
@echo "Finished downloading!"
|
||||
|
||||
permit:
|
||||
sudo setcap 'cap_net_bind_service=+ep' "$(shell pwd)/bin/necronda-server"
|
||||
|
||||
compile:
|
||||
@mkdir -p bin
|
||||
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz -lmaxminddb
|
||||
gcc $(LIBS) -o bin/libnecronda-server.so --shared -fPIC $(CFLAGS) $(INCLUDE)
|
||||
gcc src/necronda-server.c -o bin/necronda-server $(CFLAGS) $(INCLUDE) \
|
||||
-Lbin -lnecronda-server -Wl,-rpath=$(shell pwd)/bin
|
||||
|
||||
compile-debian:
|
||||
@mkdir -p bin
|
||||
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\""
|
||||
|
||||
install: | packages compile
|
||||
@echo "Finished!"
|
||||
gcc $(LIBS) -o bin/libnecronda-server.so --shared -fPIC $(CFLAGS) $(INCLUDE) \
|
||||
$(DEBIAN_OPTS) -O3
|
||||
gcc src/necronda-server.c -o bin/necronda-server $(CFLAGS) $(INCLUDE) \
|
||||
-Lbin -lnecronda-server -Wl,-rpath=$(shell pwd)/bin \
|
||||
$(DEBIAN_OPTS) -O3
|
||||
|
44
README.md
44
README.md
@ -1,2 +1,44 @@
|
||||
|
||||
# Necronda web server
|
||||
Necronda web server
|
||||
===================
|
||||
|
||||
## Features
|
||||
|
||||
* Full IPv4 and IPv6 support
|
||||
* Serving local files via HTTP and HTTPS
|
||||
* File compression ([gzip](https://www.gzip.org/), [Brotli](https://www.brotli.org/)) and disk cache for compressed files
|
||||
* Reverse proxy for other HTTP and HTTPS servers
|
||||
* FastCGI support (e.g. [PHP-FPM](https://php-fpm.org/))
|
||||
* Support for [MaxMind's GeoIP Database](https://www.maxmind.com/en/geoip2-services-and-databases)
|
||||
* Optional DNS reverse lookup for connecting hosts
|
||||
* Automatic URL rewrite (e.g. `/index.html` -> `/`, `/test.php` -> `/test`)
|
||||
* Modern looking and responsive error documents
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
See [docs/example.conf](docs/example.conf) for more details.
|
||||
|
||||
|
||||
### Global directives
|
||||
|
||||
* `certificate` - path to SSL certificate (or certificate chain)
|
||||
* `private_key` - path to SSL private key
|
||||
* `geoip_dir` (optional) - path to a directory containing GeoIP databases
|
||||
* `dns_server` (optional) - address of a DNS server
|
||||
|
||||
|
||||
### Virtual host configuration
|
||||
|
||||
* `[<host>]` - begins section for the virtual host `<host>`
|
||||
* Local
|
||||
* `webroot` - path to the root directory
|
||||
* `dir_mode` - specify the behaviour for directories without an `index.html` or `index.php`
|
||||
* `forbidden` - the server will respond with `403 Forbidden`
|
||||
* `info` - try passing *path info* to an upper `.php` file.
|
||||
* `list` - list contents of directory (**not implemented yet**)
|
||||
* Reverse proxy
|
||||
* `hostname` - hostname of server to be reverse proxy of
|
||||
* `port` - port to be used
|
||||
* `http` - use HTTP to communicate with server
|
||||
* `https` - use HTTPS to communicate with server
|
||||
|
19
docs/example.conf
Normal file
19
docs/example.conf
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
certificate /var/cert/cert.pem
|
||||
private_key /var/cert/cert.key
|
||||
#geoip_dir /var/dir
|
||||
#dns_server 192.168.0.1
|
||||
|
||||
[localhost]
|
||||
webroot /var/www/localhost
|
||||
dir_mode forbidden
|
||||
|
||||
[me.local]
|
||||
hostname www.example.com
|
||||
port 80
|
||||
http
|
||||
|
||||
[secure.local]
|
||||
hostname www.example.com
|
||||
port 443
|
||||
https
|
6
run.sh
6
run.sh
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "-- Building and starting Necronda Server..."
|
||||
make compile && \
|
||||
echo "-- Successfully finished compiling!" && \
|
||||
echo "-- Starting Server..." && \
|
||||
./bin/necronda-server $@
|
273
src/client.c
273
src/client.c
@ -5,20 +5,35 @@
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#include "necronda-server.h"
|
||||
#include "utils.h"
|
||||
#include "uri.h"
|
||||
#include "http.h"
|
||||
#include "fastcgi.h"
|
||||
#include "lib/utils.h"
|
||||
#include "lib/config.h"
|
||||
#include "lib/sock.h"
|
||||
#include "lib/http.h"
|
||||
#include "lib/rev_proxy.h"
|
||||
#include "lib/fastcgi.h"
|
||||
#include "lib/cache.h"
|
||||
#include "lib/geoip.h"
|
||||
#include "lib/compress.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <signal.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
int server_keep_alive = 1;
|
||||
char *log_client_prefix, *log_conn_prefix, *log_req_prefix, *client_geoip;
|
||||
|
||||
struct timeval client_timeout = {.tv_sec = CLIENT_TIMEOUT, .tv_usec = 0};
|
||||
|
||||
int server_keep_alive;
|
||||
char *log_client_prefix, *log_conn_prefix, *log_req_prefix, *client_geoip;
|
||||
char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr, *client_host_str;
|
||||
struct timeval client_timeout;
|
||||
|
||||
host_config *get_host_config(const char *host) {
|
||||
for (int i = 0; i < MAX_HOST_CONFIG; i++) {
|
||||
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
|
||||
host_config *hc = &config[i];
|
||||
if (hc->type == CONFIG_TYPE_UNSET) break;
|
||||
if (strcmp(hc->name, host) == 0) return hc;
|
||||
@ -51,6 +66,7 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
int accept_if_modified_since = 0;
|
||||
int use_fastcgi = 0;
|
||||
int use_rev_proxy = 0;
|
||||
int p_len;
|
||||
fastcgi_conn php_fpm = {.socket = 0, .req_id = 0};
|
||||
http_status custom_status;
|
||||
|
||||
@ -58,8 +74,6 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
sprintf(res.version, "1.1");
|
||||
res.status = http_get_status(501);
|
||||
res.hdr.field_num = 0;
|
||||
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
|
||||
http_add_header_field(&res.hdr, "Server", SERVER_STR);
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &begin);
|
||||
|
||||
@ -69,6 +83,8 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
client_timeout.tv_sec = CLIENT_TIMEOUT;
|
||||
client_timeout.tv_usec = 0;
|
||||
ret = select(client->socket + 1, &socket_fds, NULL, NULL, &client_timeout);
|
||||
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
|
||||
http_add_header_field(&res.hdr, "Server", SERVER_STR);
|
||||
if (ret <= 0) {
|
||||
if (errno != 0) {
|
||||
return 1;
|
||||
@ -86,15 +102,15 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
if (ret < 0) {
|
||||
goto abort;
|
||||
} else if (ret == 1) {
|
||||
sprintf(err_msg, "Unable to parse header: Invalid header format.");
|
||||
sprintf(err_msg, "Unable to parse http header: Invalid header format.");
|
||||
} else if (ret == 2) {
|
||||
sprintf(err_msg, "Unable to parse header: Invalid method.");
|
||||
sprintf(err_msg, "Unable to parse http header: Invalid method.");
|
||||
} else if (ret == 3) {
|
||||
sprintf(err_msg, "Unable to parse header: Invalid version.");
|
||||
sprintf(err_msg, "Unable to parse http header: Invalid version.");
|
||||
} else if (ret == 4) {
|
||||
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
|
||||
sprintf(err_msg, "Unable to parse http header: Header contains illegal characters.");
|
||||
} else if (ret == 5) {
|
||||
sprintf(err_msg, "Unable to parse header: End of header not found.");
|
||||
sprintf(err_msg, "Unable to parse http header: End of header not found.");
|
||||
}
|
||||
res.status = http_get_status(400);
|
||||
goto respond;
|
||||
@ -102,7 +118,7 @@ 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 &&
|
||||
(strcmp(hdr_connection, "keep-alive") == 0 || strcmp(hdr_connection, "Keep-Alive") == 0);
|
||||
(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;
|
||||
@ -122,7 +138,7 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
strcpy(host, host_ptr);
|
||||
}
|
||||
|
||||
sprintf(log_req_prefix, "[%s%24s%s]%s ", BLD_STR, host, CLR_STR, log_client_prefix);
|
||||
sprintf(log_req_prefix, "[%6i][%s%24s%s]%s ", getpid(), BLD_STR, host, CLR_STR, log_client_prefix);
|
||||
log_prefix = log_req_prefix;
|
||||
print(BLD_STR "%s %s" CLR_STR, req.method, req.uri);
|
||||
|
||||
@ -141,10 +157,16 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
if (ret != 0) {
|
||||
if (ret == 1) {
|
||||
sprintf(err_msg, "Invalid URI: has to start with slash.");
|
||||
res.status = http_get_status(400);
|
||||
} else if (ret == 2) {
|
||||
sprintf(err_msg, "Invalid URI: contains relative path change (/../).");
|
||||
res.status = http_get_status(400);
|
||||
} else if (ret == 3) {
|
||||
sprintf(err_msg, "The specified webroot directory does not exist.");
|
||||
res.status = http_get_status(404);
|
||||
} else {
|
||||
res.status = http_get_status(500);
|
||||
}
|
||||
res.status = http_get_status(400);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
@ -155,19 +177,57 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
if (strcmp(uri.uri, buf0) != 0 || change_proto) {
|
||||
res.status = http_get_status(308);
|
||||
size = sizeof(buf0);
|
||||
encode_url(uri.uri, buf0, &size);
|
||||
url_encode(uri.uri, buf0, &size);
|
||||
if (change_proto) {
|
||||
sprintf(buf1, "https://%s%s", host, buf0);
|
||||
p_len = snprintf(buf1, sizeof(buf1), "https://%s%s", host, buf0);
|
||||
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
||||
res.status = http_get_status(500);
|
||||
print(ERR_STR "Header field 'Location' too long" CLR_STR);
|
||||
goto respond;
|
||||
}
|
||||
http_add_header_field(&res.hdr, "Location", buf1);
|
||||
} else {
|
||||
http_add_header_field(&res.hdr, "Location", buf0);
|
||||
}
|
||||
goto respond;
|
||||
}
|
||||
} else if (!client->enc) {
|
||||
res.status = http_get_status(308);
|
||||
sprintf(buf0, "https://%s%s", host, req.uri);
|
||||
http_add_header_field(&res.hdr, "Location", buf0);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
if (http_get_header_field(&req.hdr, "Transfer-Encoding") != NULL) {
|
||||
sprintf(err_msg, "This server is unable to process requests with the Transfer-Encoding header field.");
|
||||
res.status = http_get_status(501);
|
||||
goto respond;
|
||||
}
|
||||
|
||||
if (conf->type == CONFIG_TYPE_LOCAL) {
|
||||
if (uri.filename == NULL && (int) uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
|
||||
if (strcmp(req.method, "TRACE") == 0) {
|
||||
res.status = http_get_status(200);
|
||||
http_add_header_field(&res.hdr, "Content-Type", "message/http");
|
||||
|
||||
content_length = snprintf(msg_buf, sizeof(msg_buf) - content_length, "%s %s HTTP/%s\r\n",
|
||||
req.method, req.uri, req.version);
|
||||
for (int i = 0; i < req.hdr.field_num; i++) {
|
||||
content_length += snprintf(msg_buf + content_length, sizeof(msg_buf) - content_length, "%s: %s\r\n",
|
||||
req.hdr.fields[i][0], req.hdr.fields[i][1]);
|
||||
}
|
||||
|
||||
goto respond;
|
||||
}
|
||||
|
||||
if (strncmp(uri.req_path, "/.well-known/", 13) == 0) {
|
||||
http_add_header_field(&res.hdr, "Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
|
||||
if (strncmp(uri.req_path, "/.well-known/", 13) != 0 && strstr(uri.path, "/.") != NULL) {
|
||||
res.status = http_get_status(403);
|
||||
sprintf(err_msg, "Parts of this URI are hidden.");
|
||||
goto respond;
|
||||
} else 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;
|
||||
@ -186,13 +246,18 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (http_get_header_field(&req.hdr, "Content-Length") != NULL) {
|
||||
res.status = http_get_status(400);
|
||||
sprintf(err_msg, "A GET request must not contain a payload");
|
||||
goto respond;
|
||||
}
|
||||
|
||||
ret = uri_cache_init(&uri);
|
||||
if (ret != 0) {
|
||||
res.status = http_get_status(500);
|
||||
@ -203,9 +268,43 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
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);
|
||||
|
||||
|
||||
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
|
||||
int enc = 0;
|
||||
if (accept_encoding != NULL) {
|
||||
if (uri.meta->filename_comp_br[0] != 0 && strstr(accept_encoding, "br") != NULL) {
|
||||
file = fopen(uri.meta->filename_comp_br, "rb");
|
||||
if (file == NULL) {
|
||||
cache_filename_comp_invalid(uri.filename);
|
||||
} else {
|
||||
http_add_header_field(&res.hdr, "Content-Encoding", "br");
|
||||
enc = COMPRESS_BR;
|
||||
}
|
||||
} else if (uri.meta->filename_comp_gz[0] != 0 && strstr(accept_encoding, "gzip") != NULL) {
|
||||
file = fopen(uri.meta->filename_comp_gz, "rb");
|
||||
if (file == NULL) {
|
||||
cache_filename_comp_invalid(uri.filename);
|
||||
} else {
|
||||
http_add_header_field(&res.hdr, "Content-Encoding", "gzip");
|
||||
enc = COMPRESS_GZ;
|
||||
}
|
||||
}
|
||||
if (enc != 0) {
|
||||
http_add_header_field(&res.hdr, "Vary", "Accept-Encoding");
|
||||
}
|
||||
}
|
||||
|
||||
if (uri.meta->etag[0] != 0) {
|
||||
if (enc) {
|
||||
sprintf(buf0, "%s-%s", uri.meta->etag,
|
||||
(enc & COMPRESS_BR) ? "br" : (enc & COMPRESS_GZ) ? "gzip" : "");
|
||||
http_add_header_field(&res.hdr, "ETag", buf0);
|
||||
} else {
|
||||
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 {
|
||||
@ -215,7 +314,8 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
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)) {
|
||||
(accept_if_modified_since && if_modified_since != NULL &&
|
||||
strcmp(if_modified_since, last_modified) == 0)) {
|
||||
res.status = http_get_status(304);
|
||||
goto respond;
|
||||
}
|
||||
@ -264,19 +364,10 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
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:
|
||||
if (file == NULL) {
|
||||
file = fopen(uri.filename, "rb");
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
content_length = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
@ -293,13 +384,9 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
goto respond;
|
||||
}
|
||||
|
||||
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);
|
||||
char *client_content_length = http_get_header_field(&req.hdr, "Content-Length");
|
||||
if (client_content_length != NULL) {
|
||||
unsigned long client_content_len = strtoul(client_content_length, NULL, 10);
|
||||
ret = fastcgi_receive(&php_fpm, client, client_content_len);
|
||||
if (ret != 0) {
|
||||
if (ret < 0) {
|
||||
@ -311,7 +398,6 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
goto respond;
|
||||
}
|
||||
}
|
||||
fastcgi_end:
|
||||
fastcgi_close_stdin(&php_fpm);
|
||||
|
||||
ret = fastcgi_header(&php_fpm, &res, err_msg);
|
||||
@ -338,21 +424,57 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
int http_comp = http_get_compression(&req, &res);
|
||||
if (http_comp & COMPRESS) {
|
||||
if (http_comp & COMPRESS_BR) {
|
||||
use_fastcgi |= FASTCGI_COMPRESS_BR;
|
||||
sprintf(buf0, "br");
|
||||
} else if (http_comp & COMPRESS_GZ) {
|
||||
use_fastcgi |= FASTCGI_COMPRESS_GZ;
|
||||
sprintf(buf0, "gzip");
|
||||
}
|
||||
http_add_header_field(&res.hdr, "Vary", "Accept-Encoding");
|
||||
http_add_header_field(&res.hdr, "Content-Encoding", buf0);
|
||||
}
|
||||
|
||||
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) {
|
||||
} else if (conf->type == CONFIG_TYPE_REVERSE_PROXY) {
|
||||
print("Reverse proxy for " BLD_STR "%s:%i" CLR_STR, conf->rev_proxy.hostname, conf->rev_proxy.port);
|
||||
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
|
||||
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
|
||||
|
||||
ret = rev_proxy_init(&req, &res, conf, client, &custom_status, err_msg);
|
||||
use_rev_proxy = ret == 0;
|
||||
use_rev_proxy = (ret == 0);
|
||||
|
||||
/*
|
||||
char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
|
||||
if (use_rev_proxy && content_encoding == NULL) {
|
||||
int http_comp = http_get_compression(&req, &res);
|
||||
if (http_comp & COMPRESS_BR) {
|
||||
use_rev_proxy |= REV_PROXY_COMPRESS_BR;
|
||||
} else if (http_comp & COMPRESS_GZ) {
|
||||
use_rev_proxy |= REV_PROXY_COMPRESS_GZ;
|
||||
}
|
||||
}
|
||||
|
||||
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
|
||||
int chunked = transfer_encoding != NULL && strcmp(transfer_encoding, "chunked") == 0;
|
||||
http_remove_header_field(&res.hdr, "Transfer-Encoding", HTTP_REMOVE_ALL);
|
||||
ret = sprintf(buf0, "%s%s%s",
|
||||
(use_rev_proxy & REV_PROXY_COMPRESS_BR) ? "br" :
|
||||
((use_rev_proxy & REV_PROXY_COMPRESS_GZ) ? "gzip" : ""),
|
||||
((use_rev_proxy & REV_PROXY_COMPRESS) && chunked) ? ", " : "",
|
||||
chunked ? "chunked" : "");
|
||||
if (ret > 0) {
|
||||
http_add_header_field(&res.hdr, "Transfer-Encoding", buf0);
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
print(ERR_STR "Unknown host type: %i" CLR_STR, conf->type);
|
||||
res.status = http_get_status(501);
|
||||
@ -360,17 +482,27 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
|
||||
respond:
|
||||
if (!use_rev_proxy) {
|
||||
if (conf != NULL && conf->type == CONFIG_TYPE_LOCAL && uri.is_static && res.status->code == 405) {
|
||||
http_add_header_field(&res.hdr, "Allow", "GET, HEAD, TRACE");
|
||||
}
|
||||
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);
|
||||
((res.status->code >= 400 && res.status->code < 600) || err_msg[0] != 0)) {
|
||||
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
|
||||
http_remove_header_field(&res.hdr, "Server", 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);
|
||||
|
||||
// TODO list Locations on 3xx Redirects
|
||||
const http_doc_info *info = http_get_status_info(res.status);
|
||||
const http_status_msg *http_msg = http_get_error_msg(res.status);
|
||||
|
||||
sprintf(msg_pre_buf, info->doc, res.status->code, res.status->msg,
|
||||
http_msg != NULL ? http_msg->msg : "", err_msg[0] != 0 ? err_msg : "");
|
||||
content_length = snprintf(msg_buf, sizeof(msg_buf), http_default_document, res.status->code,
|
||||
res.status->msg, msg_pre_buf, info->mode, info->icon, info->color, host);
|
||||
http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8");
|
||||
}
|
||||
if (content_length >= 0) {
|
||||
@ -379,12 +511,8 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
} 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);
|
||||
@ -405,6 +533,8 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
res.status->msg, location != NULL ? " -> " : "", location != NULL ? location : "",
|
||||
format_duration(micros, buf0), CLR_STR);
|
||||
|
||||
// TODO access/error log file
|
||||
|
||||
if (strcmp(req.method, "HEAD") != 0) {
|
||||
unsigned long snd_len = 0;
|
||||
unsigned long len;
|
||||
@ -430,19 +560,21 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
|
||||
} else if (use_fastcgi) {
|
||||
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
|
||||
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 && strcmp(content_encoding, "deflate") == 0;
|
||||
int flags = (chunked ? FASTCGI_CHUNKED : 0) | (comp ? FASTCGI_COMPRESS : 0);
|
||||
|
||||
int flags = (chunked ? FASTCGI_CHUNKED : 0) | (use_fastcgi & FASTCGI_COMPRESS);
|
||||
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;
|
||||
int chunked = transfer_encoding != NULL && strstr(transfer_encoding, "chunked") != NULL;
|
||||
|
||||
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);
|
||||
|
||||
int flags = (chunked ? REV_PROXY_CHUNKED : 0) | (use_rev_proxy & REV_PROXY_COMPRESS);
|
||||
rev_proxy_send(client, len_to_send, flags);
|
||||
}
|
||||
}
|
||||
|
||||
@ -579,6 +711,7 @@ int client_connection_handler(sock *client, unsigned long client_num) {
|
||||
client->_ssl_error = ERR_get_error();
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(client));
|
||||
ret = -1;
|
||||
goto close;
|
||||
}
|
||||
}
|
||||
@ -641,12 +774,13 @@ int client_handler(sock *client, unsigned long client_num, struct sockaddr_in6 *
|
||||
ntohs(client_addr->sin6_port), CLR_STR);
|
||||
|
||||
log_conn_prefix = malloc(256);
|
||||
sprintf(log_conn_prefix, "[%24s]%s ", server_addr_str, log_client_prefix);
|
||||
sprintf(log_conn_prefix, "[%6i][%24s]%s ", getpid(), server_addr_str, log_client_prefix);
|
||||
log_prefix = log_conn_prefix;
|
||||
|
||||
print("Started child process with PID %i", getpid());
|
||||
|
||||
ret = client_connection_handler(client, client_num);
|
||||
|
||||
free(client_addr_str_ptr);
|
||||
client_addr_str_ptr = NULL;
|
||||
free(server_addr_str_ptr);
|
||||
@ -661,5 +795,6 @@ int client_handler(sock *client, unsigned long client_num, struct sockaddr_in6 *
|
||||
log_req_prefix = NULL;
|
||||
free(log_client_prefix);
|
||||
log_client_prefix = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1,13 +1,26 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* File cache implementation
|
||||
* src/cache.c
|
||||
* src/lib/cache.c
|
||||
* Lorenz Stechauner, 2020-12-19
|
||||
*/
|
||||
|
||||
#include <zlib.h>
|
||||
#include "cache.h"
|
||||
#include "uri.h"
|
||||
#include "utils.h"
|
||||
#include "compress.h"
|
||||
#include <stdio.h>
|
||||
#include <magic.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <malloc.h>
|
||||
|
||||
int cache_continue = 1;
|
||||
magic_t magic;
|
||||
cache_entry *cache;
|
||||
|
||||
int magic_init() {
|
||||
magic = magic_open(MAGIC_MIME);
|
||||
@ -15,7 +28,7 @@ int magic_init() {
|
||||
fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (magic_load(magic, MAGIC_FILE) != 0) {
|
||||
if (magic_load(magic, CACHE_MAGIC_FILE) != 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to load magic cookie: %s" CLR_STR "\n", magic_error(magic));
|
||||
return -2;
|
||||
}
|
||||
@ -30,7 +43,7 @@ int cache_process() {
|
||||
signal(SIGINT, cache_process_term);
|
||||
signal(SIGTERM, cache_process_term);
|
||||
|
||||
int shm_id = shmget(SHM_KEY_CACHE, FILE_CACHE_SIZE * sizeof(cache_entry), 0);
|
||||
int shm_id = shmget(CACHE_SHM_KEY, CACHE_ENTRIES * sizeof(cache_entry), 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
@ -53,35 +66,38 @@ int cache_process() {
|
||||
|
||||
FILE *cache_file = fopen("/var/necronda-server/cache", "rb");
|
||||
if (cache_file != NULL) {
|
||||
fread(cache, sizeof(cache_entry), FILE_CACHE_SIZE, cache_file);
|
||||
fread(cache, sizeof(cache_entry), CACHE_ENTRIES, cache_file);
|
||||
fclose(cache_file);
|
||||
}
|
||||
|
||||
for (int i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
for (int i = 0; i < CACHE_ENTRIES; i++) {
|
||||
cache[i].is_updating = 0;
|
||||
}
|
||||
|
||||
FILE *file;
|
||||
char buf[16384];
|
||||
char comp_buf[16384];
|
||||
char filename_comp[256];
|
||||
char *buf = malloc(CACHE_BUF_SIZE);
|
||||
char *comp_buf = malloc(CACHE_BUF_SIZE);
|
||||
char filename_comp_gz[256];
|
||||
char filename_comp_br[256];
|
||||
unsigned long read;
|
||||
int compress;
|
||||
SHA_CTX ctx;
|
||||
unsigned char hash[SHA_DIGEST_LENGTH];
|
||||
int cache_changed = 0;
|
||||
int p_len_gz, p_len_br;
|
||||
int ret;
|
||||
while (cache_continue) {
|
||||
for (int i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
for (int i = 0; i < CACHE_ENTRIES; i++) {
|
||||
if (cache[i].filename[0] != 0 && cache[i].meta.etag[0] == 0 && !cache[i].is_updating) {
|
||||
cache[i].is_updating = 1;
|
||||
fprintf(stdout, "[cache] Hashing file %s\n", cache[i].filename);
|
||||
SHA1_Init(&ctx);
|
||||
file = fopen(cache[i].filename, "rb");
|
||||
compress = strncmp(cache[i].meta.type, "text/", 5) == 0 ||
|
||||
(strncmp(cache[i].meta.type, "application/", 12) == 0 &&
|
||||
strstr(cache[i].meta.type, "+xml") != NULL);
|
||||
compress = mime_is_compressible(cache[i].meta.type);
|
||||
|
||||
int level = NECRONDA_ZLIB_LEVEL;
|
||||
z_stream strm;
|
||||
FILE *comp_file = NULL;
|
||||
compress_ctx comp_ctx;
|
||||
FILE *comp_file_gz = NULL;
|
||||
FILE *comp_file_br = NULL;
|
||||
if (compress) {
|
||||
sprintf(buf, "%.*s/.necronda-server", cache[i].webroot_len, cache[i].filename);
|
||||
mkdir(buf, 0755);
|
||||
@ -96,44 +112,70 @@ int cache_process() {
|
||||
buf[j] = ch;
|
||||
}
|
||||
buf[strlen(rel_path)] = 0;
|
||||
sprintf(filename_comp, "%.*s/.necronda-server/cache/%s.z", cache[i].webroot_len, cache[i].filename, buf);
|
||||
comp_file = fopen(filename_comp, "wb");
|
||||
if (comp_file == NULL) {
|
||||
|
||||
p_len_gz = snprintf(filename_comp_gz, sizeof(filename_comp_gz),
|
||||
"%.*s/.necronda-server/cache/%s.gz",
|
||||
cache[i].webroot_len, cache[i].filename, buf);
|
||||
p_len_br = snprintf(filename_comp_br, sizeof(filename_comp_br),
|
||||
"%.*s/.necronda-server/cache/%s.br",
|
||||
cache[i].webroot_len, cache[i].filename, buf);
|
||||
if (p_len_gz < 0 || p_len_gz >= sizeof(filename_comp_gz) ||
|
||||
p_len_br < 0 || p_len_br >= sizeof(filename_comp_br)) {
|
||||
fprintf(stderr, ERR_STR "Unable to open cached file: "
|
||||
"File name for compressed file too long" CLR_STR "\n");
|
||||
goto comp_err;
|
||||
}
|
||||
|
||||
fprintf(stdout, "[cache] Compressing file %s\n", cache[i].filename);
|
||||
|
||||
comp_file_gz = fopen(filename_comp_gz, "wb");
|
||||
comp_file_br = fopen(filename_comp_br, "wb");
|
||||
if (comp_file_gz == NULL || comp_file_br == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open cached file: %s" CLR_STR "\n", strerror(errno));
|
||||
comp_err:
|
||||
compress = 0;
|
||||
fprintf(stderr, ERR_STR "Unable to open cache file: %s" CLR_STR "\n", strerror(errno));
|
||||
} 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));
|
||||
ret = compress_init(&comp_ctx, COMPRESS_GZ | COMPRESS_BR);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to init compression: %s" CLR_STR "\n", strerror(errno));
|
||||
compress = 0;
|
||||
fclose(comp_file);
|
||||
fclose(comp_file_gz);
|
||||
fclose(comp_file_br);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((read = fread(buf, 1, sizeof(buf), file)) > 0) {
|
||||
while ((read = fread(buf, 1, CACHE_BUF_SIZE, file)) > 0) {
|
||||
SHA1_Update(&ctx, buf, read);
|
||||
if (compress) {
|
||||
strm.avail_in = read;
|
||||
strm.next_in = (unsigned char *) buf;
|
||||
unsigned long avail_in, avail_out;
|
||||
avail_in = read;
|
||||
do {
|
||||
strm.avail_out = sizeof(comp_buf);
|
||||
strm.next_out = (unsigned char *) comp_buf;
|
||||
deflate(&strm, feof(file) ? Z_FINISH : Z_NO_FLUSH);
|
||||
fwrite(comp_buf, 1, sizeof(comp_buf) - strm.avail_out, comp_file);
|
||||
strm.avail_in = 0;
|
||||
} while (strm.avail_out == 0);
|
||||
avail_out = CACHE_BUF_SIZE;
|
||||
compress_compress_mode(&comp_ctx, COMPRESS_GZ,buf + read - avail_in, &avail_in,
|
||||
comp_buf, &avail_out, feof(file));
|
||||
fwrite(comp_buf, 1, CACHE_BUF_SIZE - avail_out, comp_file_gz);
|
||||
} while (avail_in != 0 || avail_out != CACHE_BUF_SIZE);
|
||||
avail_in = read;
|
||||
do {
|
||||
avail_out = CACHE_BUF_SIZE;
|
||||
compress_compress_mode(&comp_ctx, COMPRESS_BR, buf + read - avail_in, &avail_in,
|
||||
comp_buf, &avail_out, feof(file));
|
||||
fwrite(comp_buf, 1, CACHE_BUF_SIZE - avail_out, comp_file_br);
|
||||
} while (avail_in != 0 || avail_out != CACHE_BUF_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (compress) {
|
||||
deflateEnd(&strm);
|
||||
fclose(comp_file);
|
||||
strcpy(cache[i].meta.filename_comp, filename_comp);
|
||||
compress_free(&comp_ctx);
|
||||
fclose(comp_file_gz);
|
||||
fclose(comp_file_br);
|
||||
fprintf(stdout, "[cache] Finished compressing file %s\n", cache[i].filename);
|
||||
strcpy(cache[i].meta.filename_comp_gz, filename_comp_gz);
|
||||
strcpy(cache[i].meta.filename_comp_br, filename_comp_br);
|
||||
} else {
|
||||
memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
|
||||
memset(cache[i].meta.filename_comp_gz, 0, sizeof(cache[i].meta.filename_comp_gz));
|
||||
memset(cache[i].meta.filename_comp_br, 0, sizeof(cache[i].meta.filename_comp_br));
|
||||
}
|
||||
SHA1_Final(hash, &ctx);
|
||||
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
|
||||
@ -141,15 +183,29 @@ int cache_process() {
|
||||
sprintf(cache[i].meta.etag + j * 2, "%02x", hash[j]);
|
||||
}
|
||||
fclose(file);
|
||||
fprintf(stdout, "[cache] Finished hashing file %s\n", cache[i].filename);
|
||||
cache[i].is_updating = 0;
|
||||
cache_changed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
cache_file = fopen("/var/necronda-server/cache", "wb");
|
||||
fwrite(cache, sizeof(cache_entry), FILE_CACHE_SIZE , cache_file);
|
||||
fclose(cache_file);
|
||||
sleep(1);
|
||||
if (cache_changed) {
|
||||
cache_changed = 0;
|
||||
cache_file = fopen("/var/necronda-server/cache", "wb");
|
||||
if (cache_file == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open cache file: %s" CLR_STR "\n", strerror(errno));
|
||||
free(buf);
|
||||
free(comp_buf);
|
||||
return -1;
|
||||
}
|
||||
fwrite(cache, sizeof(cache_entry), CACHE_ENTRIES, cache_file);
|
||||
fclose(cache_file);
|
||||
} else {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
free(comp_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -158,7 +214,7 @@ int cache_init() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int shm_id = shmget(SHM_KEY_CACHE, FILE_CACHE_SIZE * sizeof(cache_entry), IPC_CREAT | IPC_EXCL | 0600);
|
||||
int shm_id = shmget(CACHE_SHM_KEY, CACHE_ENTRIES * 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));
|
||||
return -2;
|
||||
@ -177,7 +233,7 @@ int cache_init() {
|
||||
return -4;
|
||||
}
|
||||
cache = shm_rw;
|
||||
memset(cache, 0, FILE_CACHE_SIZE * sizeof(cache_entry));
|
||||
memset(cache, 0, CACHE_ENTRIES * sizeof(cache_entry));
|
||||
shmdt(shm_rw);
|
||||
cache = shm;
|
||||
|
||||
@ -185,24 +241,22 @@ int cache_init() {
|
||||
if (pid == 0) {
|
||||
// child
|
||||
if (cache_process() == 0) {
|
||||
return 1;
|
||||
return 0;
|
||||
} else {
|
||||
return -6;
|
||||
}
|
||||
} else if (pid > 0) {
|
||||
// parent
|
||||
fprintf(stderr, "Started child process with PID %i as cache-updater\n", pid);
|
||||
children[0] = pid;
|
||||
return pid;
|
||||
} else {
|
||||
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
|
||||
return -5;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_unload() {
|
||||
int shm_id = shmget(SHM_KEY_CACHE, 0, 0);
|
||||
int shm_id = shmget(CACHE_SHM_KEY, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(cache);
|
||||
@ -218,7 +272,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_CACHE, 0, 0);
|
||||
int shm_id = shmget(CACHE_SHM_KEY, 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));
|
||||
@ -237,11 +291,11 @@ int cache_update_entry(int entry_num, const char *filename, const char *webroot)
|
||||
const char *type = magic_file(magic, filename);
|
||||
char type_new[24];
|
||||
sprintf(type_new, "%s", type);
|
||||
if (strcmp(type, "text/plain") == 0) {
|
||||
if (strncmp(type, "text/", 5) == 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");
|
||||
sprintf(type_new, "application/javascript");
|
||||
}
|
||||
}
|
||||
strcpy(cache[entry_num].meta.type, type_new);
|
||||
@ -250,7 +304,8 @@ int cache_update_entry(int entry_num, const char *filename, const char *webroot)
|
||||
strcpy(cache[entry_num].meta.charset, magic_file(magic, filename));
|
||||
|
||||
memset(cache[entry_num].meta.etag, 0, sizeof(cache[entry_num].meta.etag));
|
||||
memset(cache[entry_num].meta.filename_comp, 0, sizeof(cache[entry_num].meta.filename_comp));
|
||||
memset(cache[entry_num].meta.filename_comp_gz, 0, sizeof(cache[entry_num].meta.filename_comp_gz));
|
||||
memset(cache[entry_num].meta.filename_comp_br, 0, sizeof(cache[entry_num].meta.filename_comp_br));
|
||||
cache[entry_num].is_updating = 0;
|
||||
|
||||
shmdt(shm_rw);
|
||||
@ -260,7 +315,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_CACHE, 0, 0);
|
||||
int shm_id = shmget(CACHE_SHM_KEY, 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));
|
||||
@ -269,9 +324,9 @@ int cache_filename_comp_invalid(const char *filename) {
|
||||
cache = shm_rw;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
for (i = 0; i < CACHE_ENTRIES; i++) {
|
||||
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(filename) &&
|
||||
strcmp(cache[i].filename, filename) == 0) {
|
||||
strcmp(cache[i].filename, filename) == 0) {
|
||||
if (cache[i].is_updating) {
|
||||
return 0;
|
||||
} else {
|
||||
@ -281,7 +336,8 @@ int cache_filename_comp_invalid(const char *filename) {
|
||||
}
|
||||
|
||||
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
|
||||
memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
|
||||
memset(cache[i].meta.filename_comp_gz, 0, sizeof(cache[i].meta.filename_comp_gz));
|
||||
memset(cache[i].meta.filename_comp_br, 0, sizeof(cache[i].meta.filename_comp_br));
|
||||
cache[i].is_updating = 0;
|
||||
|
||||
shmdt(shm_rw);
|
||||
@ -295,9 +351,9 @@ int uri_cache_init(http_uri *uri) {
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
for (i = 0; i < CACHE_ENTRIES; i++) {
|
||||
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(uri->filename) &&
|
||||
strcmp(cache[i].filename, uri->filename) == 0) {
|
||||
strcmp(cache[i].filename, uri->filename) == 0) {
|
||||
uri->meta = &cache[i].meta;
|
||||
if (cache[i].is_updating) {
|
||||
return 0;
|
||||
@ -308,7 +364,7 @@ int uri_cache_init(http_uri *uri) {
|
||||
}
|
||||
|
||||
if (uri->meta == NULL) {
|
||||
for (i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
for (i = 0; i < CACHE_ENTRIES; i++) {
|
||||
if (cache[i].filename[0] == 0) {
|
||||
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
|
||||
return -1;
|
@ -1,19 +1,27 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* File cache implementation (header file)
|
||||
* src/cache.h
|
||||
* src/lib/cache.h
|
||||
* Lorenz Stechauner, 2020-12-19
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_CACHE_H
|
||||
#define NECRONDA_SERVER_CACHE_H
|
||||
|
||||
#include <magic.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include "uri.h"
|
||||
|
||||
magic_t magic;
|
||||
#define CACHE_SHM_KEY 255641
|
||||
#define CACHE_ENTRIES 1024
|
||||
#define CACHE_BUF_SIZE 16384
|
||||
|
||||
#ifndef CACHE_MAGIC_FILE
|
||||
# define CACHE_MAGIC_FILE "/usr/share/file/misc/magic.mgc"
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_CONFIG_FILE
|
||||
# define DEFAULT_CONFIG_FILE "/etc/necronda-server/necronda-server.conf"
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
char filename[256];
|
||||
@ -22,9 +30,9 @@ typedef struct {
|
||||
meta_data meta;
|
||||
} cache_entry;
|
||||
|
||||
cache_entry *cache;
|
||||
extern cache_entry *cache;
|
||||
|
||||
int cache_continue = 1;
|
||||
extern int cache_continue;
|
||||
|
||||
int magic_init();
|
||||
|
79
src/lib/compress.c
Normal file
79
src/lib/compress.c
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Compression interface
|
||||
* src/lib/compress.c
|
||||
* Lorenz Stechauner, 2021-05-05
|
||||
*/
|
||||
|
||||
#include "compress.h"
|
||||
#include <malloc.h>
|
||||
#include <errno.h>
|
||||
|
||||
int compress_init(compress_ctx *ctx, int mode) {
|
||||
ctx->gzip = NULL;
|
||||
ctx->brotli = NULL;
|
||||
ctx->mode = 0;
|
||||
int ret;
|
||||
if (mode & COMPRESS_GZ) {
|
||||
ctx->mode |= COMPRESS_GZ;
|
||||
ctx->gzip = malloc(sizeof(z_stream));
|
||||
ctx->gzip->zalloc = Z_NULL;
|
||||
ctx->gzip->zfree = Z_NULL;
|
||||
ctx->gzip->opaque = Z_NULL;
|
||||
ret = deflateInit2(ctx->gzip, COMPRESS_LEVEL_GZIP, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY);
|
||||
if (ret != Z_OK) return -1;
|
||||
}
|
||||
if (mode & COMPRESS_BR) {
|
||||
ctx->mode |= COMPRESS_BR;
|
||||
ctx->brotli = BrotliEncoderCreateInstance(NULL, NULL, NULL);
|
||||
if (ctx->brotli == NULL) return -1;
|
||||
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_MODE, BROTLI_MODE_GENERIC);
|
||||
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_QUALITY, COMPRESS_LEVEL_BROTLI);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int compress_compress(compress_ctx *ctx, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish) {
|
||||
if ((ctx->mode & COMPRESS_GZ) && (ctx->mode & COMPRESS_BR)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
return compress_compress_mode(ctx, ctx->mode, in, in_len, out, out_len, finish);
|
||||
}
|
||||
|
||||
int compress_compress_mode(compress_ctx *ctx, int mode, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish) {
|
||||
if ((mode & COMPRESS_GZ) && (mode & COMPRESS_BR)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
} else if (mode & COMPRESS_GZ) {
|
||||
ctx->gzip->next_in = (unsigned char*) in;
|
||||
ctx->gzip->avail_in = *in_len;
|
||||
ctx->gzip->next_out = (unsigned char*) out;
|
||||
ctx->gzip->avail_out = *out_len;
|
||||
int ret = deflate(ctx->gzip, finish ? Z_FINISH : Z_NO_FLUSH);
|
||||
*in_len = ctx->gzip->avail_in;
|
||||
*out_len = ctx->gzip->avail_out;
|
||||
return ret;
|
||||
} else if (mode & COMPRESS_BR) {
|
||||
int ret = BrotliEncoderCompressStream(ctx->brotli, finish ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
|
||||
in_len, (const unsigned char**) &in, out_len, (unsigned char **) &out, NULL);
|
||||
return (ret == BROTLI_TRUE) ? 0 : -1;
|
||||
} else {
|
||||
errno = EINVAL;
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
int compress_free(compress_ctx *ctx) {
|
||||
if (ctx->gzip != NULL) {
|
||||
deflateEnd(ctx->gzip);
|
||||
free(ctx->gzip);
|
||||
ctx->gzip = NULL;
|
||||
}
|
||||
if (ctx->brotli != NULL) {
|
||||
BrotliEncoderDestroyInstance(ctx->brotli);
|
||||
ctx->brotli = NULL;
|
||||
}
|
||||
ctx->mode = 0;
|
||||
return 0;
|
||||
}
|
37
src/lib/compress.h
Normal file
37
src/lib/compress.h
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Compression interface (header file)
|
||||
* src/lib/compress.h
|
||||
* Lorenz Stechauner, 2021-05-05
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_COMPRESS_H
|
||||
#define NECRONDA_SERVER_COMPRESS_H
|
||||
|
||||
#include <zlib.h>
|
||||
#include <brotli/encode.h>
|
||||
|
||||
#define COMPRESS_LEVEL_GZIP 9
|
||||
#define COMPRESS_LEVEL_BROTLI BROTLI_MAX_QUALITY
|
||||
|
||||
#define COMPRESS_GZ 1
|
||||
#define COMPRESS_BR 2
|
||||
#define COMPRESS 3
|
||||
|
||||
typedef struct {
|
||||
int mode;
|
||||
z_stream *gzip;
|
||||
BrotliEncoderState *brotli;
|
||||
} compress_ctx;
|
||||
|
||||
int compress_init(compress_ctx *ctx, int mode);
|
||||
|
||||
int compress_compress(compress_ctx *ctx, const char *in, unsigned long *in_len, char *out, unsigned long *out_len,
|
||||
int finish);
|
||||
|
||||
int compress_compress_mode(compress_ctx *ctx, int mode, const char *in, unsigned long *in_len, char *out,
|
||||
unsigned long *out_len, int finish);
|
||||
|
||||
int compress_free(compress_ctx *ctx);
|
||||
|
||||
#endif //NECRONDA_SERVER_COMPRESS_H
|
@ -1,17 +1,24 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Configuration file loader
|
||||
* src/config.c
|
||||
* src/lib/config.c
|
||||
* Lorenz Stechauner, 2021-01-05
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "uri.h"
|
||||
#include "utils.h"
|
||||
#include <stdio.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
host_config *config;
|
||||
char cert_file[256], key_file[256], geoip_dir[256], dns_server[256];
|
||||
|
||||
int config_init() {
|
||||
int shm_id = shmget(SHM_KEY_CONFIG, MAX_HOST_CONFIG * sizeof(host_config), IPC_CREAT | IPC_EXCL | 0640);
|
||||
int shm_id = shmget(CONFIG_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;
|
||||
@ -30,14 +37,14 @@ int config_init() {
|
||||
return -3;
|
||||
}
|
||||
config = shm_rw;
|
||||
memset(config, 0, MAX_HOST_CONFIG * sizeof(host_config));
|
||||
memset(config, 0, CONFIG_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);
|
||||
int shm_id = shmget(CONFIG_SHM_KEY, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(config);
|
||||
@ -65,14 +72,13 @@ int config_load(const char *filename) {
|
||||
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));
|
||||
host_config *tmp_config = malloc(CONFIG_MAX_HOST_CONFIG * sizeof(host_config));
|
||||
memset(tmp_config, 0, CONFIG_MAX_HOST_CONFIG * sizeof(host_config));
|
||||
|
||||
int i = 0;
|
||||
int mode = 0;
|
||||
char *ptr = NULL;
|
||||
char host[256], *source, *target;
|
||||
host[0] = 0;
|
||||
char *source, *target;
|
||||
while ((ptr = strtok(ptr == NULL ? conf : NULL, "\n")) != NULL) {
|
||||
char *comment = strchr(ptr, '#');
|
||||
if (comment != NULL) comment[0] = 0;
|
||||
@ -177,10 +183,18 @@ int config_load(const char *filename) {
|
||||
tmp_config[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10);
|
||||
}
|
||||
}
|
||||
|
||||
free(conf);
|
||||
|
||||
int shm_id = shmget(SHM_KEY_CONFIG, 0, 0);
|
||||
for (int j = 0; j < i; j++) {
|
||||
if (tmp_config[j].type == CONFIG_TYPE_LOCAL) {
|
||||
char *webroot = tmp_config[j].local.webroot;
|
||||
if (webroot[strlen(webroot) - 1] == '/') {
|
||||
webroot[strlen(webroot) - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int shm_id = shmget(CONFIG_SHM_KEY, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(config);
|
||||
@ -193,7 +207,7 @@ int config_load(const char *filename) {
|
||||
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));
|
||||
memcpy(shm_rw, tmp_config, CONFIG_MAX_HOST_CONFIG * sizeof(host_config));
|
||||
free(tmp_config);
|
||||
shmdt(shm_rw);
|
||||
return 0;
|
@ -1,19 +1,22 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Configuration file loader (header file)
|
||||
* src/config.h
|
||||
* src/lib/config.h
|
||||
* Lorenz Stechauner, 2021-01-05
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NECRONDA_SERVER_CONFIG_H
|
||||
#define NECRONDA_SERVER_CONFIG_H
|
||||
|
||||
#include "uri.h"
|
||||
|
||||
#define CONFIG_SHM_KEY 255642
|
||||
#define CONFIG_MAX_HOST_CONFIG 64
|
||||
|
||||
#define CONFIG_TYPE_UNSET 0
|
||||
#define CONFIG_TYPE_LOCAL 1
|
||||
#define CONFIG_TYPE_REVERSE_PROXY 2
|
||||
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
char name[256];
|
||||
@ -30,10 +33,8 @@ typedef struct {
|
||||
};
|
||||
} host_config;
|
||||
|
||||
|
||||
host_config *config;
|
||||
char cert_file[256], key_file[256], geoip_dir[256], dns_server[256];
|
||||
|
||||
extern host_config *config;
|
||||
extern char cert_file[256], key_file[256], geoip_dir[256], dns_server[256];
|
||||
|
||||
int config_init();
|
||||
|
@ -1,15 +1,18 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* FastCGI interface implementation
|
||||
* src/fastcgi.c
|
||||
* src/lib/fastcgi.c
|
||||
* Lorenz Stechauner, 2020-12-26
|
||||
*/
|
||||
|
||||
#include "fastcgi.h"
|
||||
#include "necronda-server.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include "compress.h"
|
||||
#include "../necronda-server.h"
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
char *fastcgi_add_param(char *buf, const char *key, const char *value) {
|
||||
char *ptr = buf;
|
||||
@ -133,7 +136,7 @@ int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_n
|
||||
if (uri->pathinfo != NULL && strlen(uri->pathinfo) > 0) {
|
||||
sprintf(buf0, "/%s", uri->pathinfo);
|
||||
} else {
|
||||
sprintf(buf0, "");
|
||||
buf0[0] = 0;
|
||||
}
|
||||
param_ptr = fastcgi_add_param(param_ptr, "PATH_INFO", buf0);
|
||||
|
||||
@ -205,17 +208,21 @@ int fastcgi_close_stdin(fastcgi_conn *conn) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fastcgi_php_error(char *msg, int msg_len, char *err_msg) {
|
||||
int fastcgi_php_error(const char *msg, int msg_len, char *err_msg) {
|
||||
char *msg_str = malloc(msg_len + 1);
|
||||
char *ptr0 = msg_str;
|
||||
strncpy(msg_str, msg, msg_len);
|
||||
char *ptr1 = NULL;
|
||||
int len;
|
||||
int err = 0;
|
||||
// FIXME *msg is part of a stream, handle fragmented lines
|
||||
while (1) {
|
||||
int msg_type = 0;
|
||||
int msg_pre_len = 0;
|
||||
ptr1 = strstr(ptr0, "PHP message: ");
|
||||
if (ptr1 == NULL) {
|
||||
len = (int) (msg_len - (ptr0 - msg_str));
|
||||
if (ptr0 == msg_str) msg_type = 2;
|
||||
} else {
|
||||
len = (int) (ptr1 - ptr0);
|
||||
}
|
||||
@ -223,8 +230,6 @@ int fastcgi_php_error(char *msg, int msg_len, char *err_msg) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
int msg_type = 0;
|
||||
int msg_pre_len = 0;
|
||||
if (len >= 14 && strncmp(ptr0, "PHP Warning: ", 14) == 0) {
|
||||
msg_type = 1;
|
||||
msg_pre_len = 14;
|
||||
@ -248,7 +253,7 @@ int fastcgi_php_error(char *msg, int msg_len, char *err_msg) {
|
||||
if (ptr3 != NULL && (ptr3 - ptr2) < len2) {
|
||||
len2 = (int) (ptr3 - ptr2);
|
||||
}
|
||||
print("%s%.*s%s", msg_type == 1 ? WRN_STR : msg_type == 2 ? ERR_STR: "", len2, ptr2, msg_type != 0 ? CLR_STR : "");
|
||||
print("%s%.*s%s", msg_type == 1 ? WRN_STR : msg_type == 2 ? ERR_STR : "", len2, ptr2, msg_type != 0 ? CLR_STR : "");
|
||||
if (msg_type == 2 && ptr2 == ptr0) {
|
||||
sprintf(err_msg, "%.*s", len2, ptr2);
|
||||
err = 1;
|
||||
@ -265,6 +270,7 @@ int fastcgi_php_error(char *msg, int msg_len, char *err_msg) {
|
||||
}
|
||||
ptr0 = ptr1 + 13;
|
||||
}
|
||||
free(msg_str);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -272,7 +278,7 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
|
||||
FCGI_Header header;
|
||||
char *content;
|
||||
unsigned short content_len, req_id;
|
||||
int ret;
|
||||
long ret;
|
||||
int err = 0;
|
||||
|
||||
while (1) {
|
||||
@ -366,7 +372,7 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
|
||||
}
|
||||
|
||||
ret = http_parse_header_field(&res->hdr, ptr, pos0);
|
||||
if (ret != 0) return ret;
|
||||
if (ret != 0) return (int) ret;
|
||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||
return 0;
|
||||
}
|
||||
@ -378,7 +384,7 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
|
||||
|
||||
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
|
||||
FCGI_Header header;
|
||||
int ret;
|
||||
long ret;
|
||||
char buf0[256];
|
||||
int len;
|
||||
char *content, *ptr;
|
||||
@ -386,15 +392,18 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
|
||||
char comp_out[4096];
|
||||
int finish_comp = 0;
|
||||
|
||||
z_stream strm;
|
||||
if (flags & FASTCGI_COMPRESS) {
|
||||
int level = NECRONDA_ZLIB_LEVEL;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
if (deflateInit(&strm, level) != Z_OK) {
|
||||
print(ERR_STR "Unable to init deflate: %s" CLR_STR, strerror(errno));
|
||||
flags &= !FASTCGI_COMPRESS;
|
||||
compress_ctx comp_ctx;
|
||||
if (flags & FASTCGI_COMPRESS_BR) {
|
||||
flags &= ~FASTCGI_COMPRESS_GZ;
|
||||
if (compress_init(&comp_ctx, COMPRESS_BR) != 0) {
|
||||
print(ERR_STR "Unable to init brotli: %s" CLR_STR, strerror(errno));
|
||||
flags &= ~FASTCGI_COMPRESS_BR;
|
||||
}
|
||||
} else if (flags & FASTCGI_COMPRESS_GZ) {
|
||||
flags &= ~FASTCGI_COMPRESS_BR;
|
||||
if (compress_init(&comp_ctx, COMPRESS_GZ) != 0) {
|
||||
print(ERR_STR "Unable to init gzip: %s" CLR_STR, strerror(errno));
|
||||
flags &= ~FASTCGI_COMPRESS_GZ;
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,6 +423,7 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
|
||||
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
|
||||
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
|
||||
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
|
||||
content = malloc(content_len + header.paddingLength);
|
||||
@ -448,7 +458,7 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
|
||||
content_len = 0;
|
||||
goto out;
|
||||
finish:
|
||||
deflateEnd(&strm);
|
||||
compress_free(&comp_ctx);
|
||||
}
|
||||
|
||||
if (flags & FASTCGI_CHUNKED) {
|
||||
@ -457,22 +467,20 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
|
||||
|
||||
return 0;
|
||||
} else if (header.type == FCGI_STDERR) {
|
||||
print(ERR_STR "%.*s" CLR_STR, content_len, content);
|
||||
fastcgi_php_error(content, content_len, buf0);
|
||||
} else if (header.type == FCGI_STDOUT) {
|
||||
unsigned long avail_in, avail_out;
|
||||
out:
|
||||
if (flags & FASTCGI_COMPRESS) {
|
||||
strm.avail_in = content_len;
|
||||
strm.next_in = (unsigned char *) ptr;
|
||||
}
|
||||
avail_in = content_len;
|
||||
void *next_in = ptr;
|
||||
do {
|
||||
int buf_len = content_len;
|
||||
if (flags & FASTCGI_COMPRESS) {
|
||||
strm.avail_out = sizeof(comp_out);
|
||||
strm.next_out = (unsigned char *) comp_out;
|
||||
deflate(&strm, finish_comp ? Z_FINISH : Z_NO_FLUSH);
|
||||
strm.avail_in = 0;
|
||||
avail_out = sizeof(comp_out);
|
||||
compress_compress(&comp_ctx, next_in + content_len - avail_in, &avail_in, comp_out, &avail_out,
|
||||
finish_comp);
|
||||
ptr = comp_out;
|
||||
buf_len = (int) (sizeof(comp_out) - strm.avail_out);
|
||||
buf_len = (int) (sizeof(comp_out) - avail_out);
|
||||
}
|
||||
if (buf_len != 0) {
|
||||
len = sprintf(buf0, "%X\r\n", buf_len);
|
||||
@ -480,7 +488,7 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
|
||||
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);
|
||||
} while ((flags & FASTCGI_COMPRESS) && (avail_in != 0 || avail_out != sizeof(comp_out)));
|
||||
if (finish_comp) goto finish;
|
||||
} else {
|
||||
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
|
47
src/lib/fastcgi.h
Normal file
47
src/lib/fastcgi.h
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* FastCGI interface implementation (header file)
|
||||
* src/lib/fastcgi.h
|
||||
* Lorenz Stechauner, 2020-12-26
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_FASTCGI_H
|
||||
#define NECRONDA_SERVER_FASTCGI_H
|
||||
|
||||
#include "include/fastcgi.h"
|
||||
#include "http.h"
|
||||
#include "uri.h"
|
||||
|
||||
#define FASTCGI_CHUNKED 1
|
||||
#define FASTCGI_COMPRESS_GZ 2
|
||||
#define FASTCGI_COMPRESS_BR 4
|
||||
#define FASTCGI_COMPRESS 6
|
||||
|
||||
#ifndef PHP_FPM_SOCKET
|
||||
# define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
int socket;
|
||||
unsigned short req_id;
|
||||
char *out_buf;
|
||||
unsigned short out_len;
|
||||
unsigned short out_off;
|
||||
} fastcgi_conn;
|
||||
|
||||
char *fastcgi_add_param(char *buf, const char *key, const char *value);
|
||||
|
||||
int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_num, const sock *client,
|
||||
const http_req *req, const http_uri *uri);
|
||||
|
||||
int fastcgi_close_stdin(fastcgi_conn *conn);
|
||||
|
||||
int fastcgi_php_error(const char *msg, int msg_len, char *err_msg);
|
||||
|
||||
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg);
|
||||
|
||||
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags);
|
||||
|
||||
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len);
|
||||
|
||||
#endif //NECRONDA_SERVER_FASTCGI_H
|
69
src/lib/geoip.c
Normal file
69
src/lib/geoip.c
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* MaxMind GeoIP Database interface
|
||||
* src/lib/geoip.c
|
||||
* Lorenz Stechauner, 2021-05-04
|
||||
*/
|
||||
|
||||
#include "geoip.h"
|
||||
|
||||
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", (unsigned long long) 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;
|
||||
}
|
18
src/lib/geoip.h
Normal file
18
src/lib/geoip.h
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* MaxMind GeoIP Database interface (header file)
|
||||
* src/lib/geoip.h
|
||||
* Lorenz Stechauner, 2021-05-04
|
||||
*/
|
||||
|
||||
|
||||
#ifndef NECRONDA_SERVER_GEOIP_H
|
||||
#define NECRONDA_SERVER_GEOIP_H
|
||||
|
||||
#include <maxminddb.h>
|
||||
|
||||
#define GEOIP_MAX_SIZE 8192
|
||||
|
||||
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len);
|
||||
|
||||
#endif //NECRONDA_SERVER_GEOIP_H
|
@ -1,13 +1,14 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* HTTP implementation
|
||||
* src/net/http.c
|
||||
* src/lib/http.c
|
||||
* Lorenz Stechauner, 2020-12-09
|
||||
*/
|
||||
|
||||
#include "http.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "compress.h"
|
||||
#include <string.h>
|
||||
|
||||
void http_to_camel_case(char *str, int mode) {
|
||||
char last = '-';
|
||||
@ -32,7 +33,7 @@ void http_free_hdr(http_hdr *hdr) {
|
||||
}
|
||||
|
||||
void http_free_req(http_req *req) {
|
||||
if (req->uri == NULL) free(req->uri);
|
||||
if (req->uri != NULL) free(req->uri);
|
||||
req->uri = NULL;
|
||||
http_free_hdr(&req->hdr);
|
||||
}
|
||||
@ -50,9 +51,9 @@ int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr)
|
||||
}
|
||||
|
||||
long len = pos1 - buf;
|
||||
hdr->fields[hdr->field_num][0] = malloc(len + 1);
|
||||
sprintf(hdr->fields[hdr->field_num][0], "%.*s", (int) len, buf);
|
||||
http_to_camel_case(hdr->fields[hdr->field_num][0], HTTP_CAMEL);
|
||||
hdr->fields[(int) hdr->field_num][0] = malloc(len + 1);
|
||||
sprintf(hdr->fields[(int) hdr->field_num][0], "%.*s", (int) len, buf);
|
||||
http_to_camel_case(hdr->fields[(int) hdr->field_num][0], HTTP_CAMEL);
|
||||
|
||||
pos1++;
|
||||
pos2 = (char *) end_ptr - 1;
|
||||
@ -61,11 +62,11 @@ int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr)
|
||||
len = pos2 - pos1 + 1;
|
||||
|
||||
if (len <= 0) {
|
||||
hdr->fields[hdr->field_num][1] = malloc(1);
|
||||
hdr->fields[hdr->field_num][1][0] = 0;
|
||||
hdr->fields[(int) hdr->field_num][1] = malloc(1);
|
||||
hdr->fields[(int) hdr->field_num][1][0] = 0;
|
||||
} else {
|
||||
hdr->fields[hdr->field_num][1] = malloc(len + 1);
|
||||
sprintf(hdr->fields[hdr->field_num][1], "%.*s", (int) len, pos1);
|
||||
hdr->fields[(int) hdr->field_num][1] = malloc(len + 1);
|
||||
sprintf(hdr->fields[(int) hdr->field_num][1], "%.*s", (int) len, pos1);
|
||||
}
|
||||
hdr->field_num++;
|
||||
return 0;
|
||||
@ -84,19 +85,19 @@ int http_receive_request(sock *client, http_req *req) {
|
||||
while (1) {
|
||||
rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE, 0);
|
||||
if (rcv_len <= 0) {
|
||||
print("Unable to receive: %s", sock_strerror(client));
|
||||
print("Unable to receive http header: %s", sock_strerror(client));
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned long header_len = strstr(buf, "\r\n\r\n") - buf + 4;
|
||||
if (header_len <= 0) {
|
||||
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
|
||||
print(ERR_STR "Unable to parse http header: End of header not found" CLR_STR);
|
||||
return 5;
|
||||
}
|
||||
|
||||
for (int i = 0; i < header_len; i++) {
|
||||
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
|
||||
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
|
||||
print(ERR_STR "Unable to parse http header: Header contains illegal characters" CLR_STR);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
@ -105,7 +106,7 @@ int http_receive_request(sock *client, http_req *req) {
|
||||
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);
|
||||
print(ERR_STR "Unable to parse http header: Invalid header format" CLR_STR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -114,13 +115,13 @@ int http_receive_request(sock *client, http_req *req) {
|
||||
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);
|
||||
print(ERR_STR "Unable to parse http header: Method name too long" CLR_STR);
|
||||
return 2;
|
||||
}
|
||||
|
||||
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);
|
||||
print(ERR_STR "Unable to parse http header: Invalid method" CLR_STR);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
@ -129,12 +130,12 @@ int http_receive_request(sock *client, http_req *req) {
|
||||
pos2 = memchr(pos1, ' ', rcv_len - (pos1 - buf)) + 1;
|
||||
if (pos2 == NULL) {
|
||||
err_hdr_fmt:
|
||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
||||
print(ERR_STR "Unable to parse http header: Invalid header format" CLR_STR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (memcmp(pos2, "HTTP/", 5) != 0 || memcmp(pos2 + 8, "\r\n", 2) != 0) {
|
||||
print(ERR_STR "Unable to parse header: Invalid version" CLR_STR);
|
||||
print(ERR_STR "Unable to parse http header: Invalid version" CLR_STR);
|
||||
return 3;
|
||||
}
|
||||
|
||||
@ -185,8 +186,8 @@ void http_add_header_field(http_hdr *hdr, const char *field_name, const char *fi
|
||||
strcpy(_field_name, field_name);
|
||||
strcpy(_field_value, field_value);
|
||||
http_to_camel_case(_field_name, HTTP_PRESERVE);
|
||||
hdr->fields[hdr->field_num][0] = _field_name;
|
||||
hdr->fields[hdr->field_num][1] = _field_value;
|
||||
hdr->fields[(int) hdr->field_num][0] = _field_name;
|
||||
hdr->fields[(int) hdr->field_num][1] = _field_value;
|
||||
hdr->field_num++;
|
||||
}
|
||||
|
||||
@ -245,8 +246,8 @@ int http_send_request(sock *server, http_req *req) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
http_status *http_get_status(unsigned short status_code) {
|
||||
for (int i = 0; i < sizeof(http_statuses) / sizeof(http_status); i++) {
|
||||
const http_status *http_get_status(unsigned short status_code) {
|
||||
for (int i = 0; i < http_statuses_size / sizeof(http_status); i++) {
|
||||
if (http_statuses[i].code == status_code) {
|
||||
return &http_statuses[i];
|
||||
}
|
||||
@ -254,20 +255,21 @@ http_status *http_get_status(unsigned short status_code) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
http_error_msg *http_get_error_msg(unsigned short status_code) {
|
||||
for (int i = 0; i < sizeof(http_error_messages) / sizeof(http_error_msg); i++) {
|
||||
if (http_error_messages[i].code == status_code) {
|
||||
return &http_error_messages[i];
|
||||
const http_status_msg *http_get_error_msg(const http_status *status) {
|
||||
unsigned short code = status->code;
|
||||
for (int i = 0; i < http_status_messages_size / sizeof(http_status_msg); i++) {
|
||||
if (http_status_messages[i].code == code) {
|
||||
return &http_status_messages[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *http_get_status_color(http_status *status) {
|
||||
const char *http_get_status_color(const http_status *status) {
|
||||
unsigned short code = status->code;
|
||||
if (code >= 100 && code < 200) {
|
||||
return HTTP_1XX_STR;
|
||||
} else if (code >= 200 && code < 300 || code == 304) {
|
||||
} else if ((code >= 200 && code < 300) || code == 304) {
|
||||
return HTTP_2XX_STR;
|
||||
} else if (code >= 300 && code < 400) {
|
||||
return HTTP_3XX_STR;
|
||||
@ -290,3 +292,37 @@ char *http_get_date(char *buf, size_t size) {
|
||||
time(&rawtime);
|
||||
return http_format_date(rawtime, buf, size);
|
||||
}
|
||||
|
||||
const http_doc_info *http_get_status_info(const http_status *status) {
|
||||
unsigned short code = status->code;
|
||||
static http_doc_info info[] = {
|
||||
{"info", HTTP_COLOR_INFO, http_info_icon, http_info_document},
|
||||
{"success", HTTP_COLOR_SUCCESS, http_success_icon, http_success_document},
|
||||
{"warning", HTTP_COLOR_WARNING, http_warning_icon, http_warning_document},
|
||||
{"error", HTTP_COLOR_ERROR, http_error_icon, http_error_document}
|
||||
};
|
||||
if (code >= 100 && code < 200) {
|
||||
return &info[0];
|
||||
} else if ((code >= 200 && code < 300) || code == 304) {
|
||||
return &info[1];
|
||||
} else if (code >= 300 && code < 400) {
|
||||
return &info[2];
|
||||
} else if (code >= 400 && code < 600) {
|
||||
return &info[3];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int http_get_compression(const http_req *req, const http_res *res) {
|
||||
char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
|
||||
char *content_type = http_get_header_field(&res->hdr, "Content-Type");
|
||||
char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
|
||||
if (mime_is_compressible(content_type) && content_encoding == NULL && accept_encoding != NULL) {
|
||||
if (strstr(accept_encoding, "br") != NULL) {
|
||||
return COMPRESS_BR;
|
||||
} else if (strstr(accept_encoding, "gzip") != NULL) {
|
||||
return COMPRESS_GZ;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
129
src/lib/http.h
Normal file
129
src/lib/http.h
Normal file
@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* HTTP implementation (header file)
|
||||
* src/lib/http.h
|
||||
* Lorenz Stechauner, 2020-12-09
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_HTTP_H
|
||||
#define NECRONDA_SERVER_HTTP_H
|
||||
|
||||
#include "sock.h"
|
||||
|
||||
#define HTTP_PRESERVE 0
|
||||
#define HTTP_LOWER 1
|
||||
#define HTTP_CAMEL 2
|
||||
|
||||
#define HTTP_REMOVE_ONE 0
|
||||
#define HTTP_REMOVE_ALL 1
|
||||
#define HTTP_REMOVE_LAST 2
|
||||
|
||||
#define HTTP_1XX_STR "\x1B[1;32m"
|
||||
#define HTTP_2XX_STR "\x1B[1;32m"
|
||||
#define HTTP_3XX_STR "\x1B[1;33m"
|
||||
#define HTTP_4XX_STR "\x1B[1;31m"
|
||||
#define HTTP_5XX_STR "\x1B[1;31m"
|
||||
|
||||
#define HTTP_COLOR_SUCCESS "#008000"
|
||||
#define HTTP_COLOR_INFO "#606060"
|
||||
#define HTTP_COLOR_WARNING "#E0C000"
|
||||
#define HTTP_COLOR_ERROR "#C00000"
|
||||
|
||||
#define CLIENT_MAX_HEADER_SIZE 8192
|
||||
|
||||
#ifndef SERVER_STR
|
||||
# define SERVER_STR "Necronda"
|
||||
#endif
|
||||
|
||||
#ifndef SERVER_STR_HTML
|
||||
# define SERVER_STR_HTML "Necronda web server"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
unsigned short code;
|
||||
char type[16];
|
||||
char msg[32];
|
||||
} http_status;
|
||||
|
||||
typedef struct {
|
||||
unsigned short code;
|
||||
const char *msg;
|
||||
} http_status_msg;
|
||||
|
||||
typedef struct {
|
||||
char mode[8];
|
||||
char color[8];
|
||||
const char *icon;
|
||||
const char *doc;
|
||||
} http_doc_info;
|
||||
|
||||
typedef struct {
|
||||
char field_num;
|
||||
char *fields[64][2];
|
||||
} http_hdr;
|
||||
|
||||
typedef struct {
|
||||
char method[16];
|
||||
char *uri;
|
||||
char version[4];
|
||||
http_hdr hdr;
|
||||
} http_req;
|
||||
|
||||
typedef struct {
|
||||
const http_status *status;
|
||||
char version[4];
|
||||
http_hdr hdr;
|
||||
} http_res;
|
||||
|
||||
extern const http_status http_statuses[];
|
||||
extern const http_status_msg http_status_messages[];
|
||||
extern const int http_statuses_size;
|
||||
extern const int http_status_messages_size;
|
||||
|
||||
extern const char http_default_document[];
|
||||
extern const char http_error_document[];
|
||||
extern const char http_error_icon[];
|
||||
extern const char http_warning_document[];
|
||||
extern const char http_warning_icon[];
|
||||
extern const char http_success_document[];
|
||||
extern const char http_success_icon[];
|
||||
extern const char http_info_document[];
|
||||
extern const char http_info_icon[];
|
||||
|
||||
void http_to_camel_case(char *str, int mode);
|
||||
|
||||
void http_free_hdr(http_hdr *hdr);
|
||||
|
||||
void http_free_req(http_req *req);
|
||||
|
||||
void http_free_res(http_res *res);
|
||||
|
||||
int http_receive_request(sock *client, http_req *req);
|
||||
|
||||
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) ;
|
||||
|
||||
char *http_get_header_field(const http_hdr *hdr, const char *field_name);
|
||||
|
||||
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
|
||||
|
||||
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);
|
||||
|
||||
const http_status *http_get_status(unsigned short status_code);
|
||||
|
||||
const http_status_msg *http_get_error_msg(const http_status *status);
|
||||
|
||||
const char *http_get_status_color(const http_status *status);
|
||||
|
||||
char *http_format_date(time_t time, char *buf, size_t size);
|
||||
|
||||
char *http_get_date(char *buf, size_t size);
|
||||
|
||||
const http_doc_info *http_get_status_info(const http_status *status);
|
||||
|
||||
int http_get_compression(const http_req *req, const http_res *res);
|
||||
|
||||
#endif //NECRONDA_SERVER_HTTP_H
|
@ -1,51 +1,14 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* HTTP implementation (header file)
|
||||
* src/net/http.h
|
||||
* Lorenz Stechauner, 2020-12-09
|
||||
* HTTP static implementation
|
||||
* src/lib/http_static.c
|
||||
* Lorenz Stechauner, 2021-05-03
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_HTTP_H
|
||||
#define NECRONDA_SERVER_HTTP_H
|
||||
#include "../necronda.h"
|
||||
#include "http.h"
|
||||
|
||||
#define HTTP_PRESERVE 0
|
||||
#define HTTP_LOWER 1
|
||||
#define HTTP_CAMEL 2
|
||||
|
||||
#define HTTP_REMOVE_ONE 0
|
||||
#define HTTP_REMOVE_ALL 1
|
||||
#define HTTP_REMOVE_LAST 2
|
||||
|
||||
typedef struct {
|
||||
unsigned short code;
|
||||
char type[16];
|
||||
char msg[32];
|
||||
} http_status;
|
||||
|
||||
typedef struct {
|
||||
unsigned short code;
|
||||
char *err_msg;
|
||||
} http_error_msg;
|
||||
|
||||
typedef struct {
|
||||
char field_num;
|
||||
char *fields[64][2];
|
||||
} http_hdr;
|
||||
|
||||
typedef struct {
|
||||
char method[16];
|
||||
char *uri;
|
||||
char version[3];
|
||||
http_hdr hdr;
|
||||
} http_req;
|
||||
|
||||
typedef struct {
|
||||
http_status *status;
|
||||
char version[3];
|
||||
http_hdr hdr;
|
||||
} http_res;
|
||||
|
||||
http_status http_statuses[] = {
|
||||
const http_status http_statuses[] = {
|
||||
{100, "Informational", "Continue"},
|
||||
{101, "Informational", "Switching Protocols"},
|
||||
|
||||
@ -61,7 +24,7 @@ http_status http_statuses[] = {
|
||||
{301, "Redirection", "Moved Permanently"},
|
||||
{302, "Redirection", "Found"},
|
||||
{303, "Redirection", "See Other"},
|
||||
{304, "Redirection", "Not Modified"},
|
||||
{304, "Success", "Not Modified"},
|
||||
{305, "Redirection", "Use Proxy"},
|
||||
{307, "Redirection", "Temporary Redirect"},
|
||||
{308, "Redirection", "Permanent Redirect"},
|
||||
@ -93,7 +56,27 @@ http_status http_statuses[] = {
|
||||
{505, "Server Error", "HTTP Version Not Supported"},
|
||||
};
|
||||
|
||||
http_error_msg http_error_messages[] = {
|
||||
const http_status_msg http_status_messages[] = {
|
||||
{100, "The client SHOULD continue with its request."},
|
||||
{101, "The server understands and is willing to comply with the clients request, via the Upgrade message header field, for a change in the application protocol being used on this connection."},
|
||||
|
||||
{200, "The request has succeeded."},
|
||||
{201, "The request has been fulfilled and resulted in a new resource being created."},
|
||||
{202, "The request has been accepted for processing, but the processing has not been completed."},
|
||||
{203, "The returned meta information in the entity-header is not the definitive set as available from the origin server, but is gathered from a local or a third-party copy."},
|
||||
{204, "The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta information."},
|
||||
{205, "The server has fulfilled the request and the user agent SHOULD reset the document view which caused the request to be sent."},
|
||||
{206, "The server has fulfilled the partial GET request for the resource."},
|
||||
|
||||
{300, "The requested resource corresponds to any one of a set of representations, each with its own specific location, and agent-driven negotiation information is being provided so that the user (or user agent) can select a preferred representation and redirect its request to that location."},
|
||||
{301, "The requested resource has been assigned a new permanent URI and any future references to this resource SHOULD use one of the returned URIs."},
|
||||
{302, "The requested resource resides temporarily under a different URI."},
|
||||
{303, "The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource."},
|
||||
{304, "The request has been fulfilled and the requested resource has not been modified."},
|
||||
{305, "The requested resource MUST be accessed through the proxy given by the Location field."},
|
||||
{307, "The requested resource resides temporarily under a different URI."},
|
||||
{308, "The requested resource has been assigned a new permanent URI and any future references to this resource ought to use one of the enclosed URIs."},
|
||||
|
||||
{400, "The request could not be understood by the server due to malformed syntax."},
|
||||
{401, "The request requires user authentication."},
|
||||
{403, "The server understood the request, but is refusing to fulfill it."},
|
||||
@ -109,7 +92,7 @@ http_error_msg http_error_messages[] = {
|
||||
{413, "The server is refusing to process a request because the request entity is larger than the server is willing or able to process."},
|
||||
{414, "The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret."},
|
||||
{415, "The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method."},
|
||||
{416, "None of the ranges in the request's Range header field overlap the current extent of the selected resource or that the set of ranges requested has been rejected due to invalid ranges or an excessive request of small or overlapping ranges."},
|
||||
{416, "None of the ranges in the requests Range header field overlap the current extent of the selected resource or that the set of ranges requested has been rejected due to invalid ranges or an excessive request of small or overlapping ranges."},
|
||||
{417, "The expectation given in an Expect request-header field could not be met by this server, or, if the server is a proxy, the server has unambiguous evidence that the request could not be met by the next-hop server."},
|
||||
|
||||
{500, "The server encountered an unexpected condition which prevented it from fulfilling the request."},
|
||||
@ -120,7 +103,7 @@ http_error_msg http_error_messages[] = {
|
||||
{505, "The server does not support, or refuses to support, the HTTP protocol version that was used in the request message."}
|
||||
};
|
||||
|
||||
const char *http_default_document =
|
||||
const char http_default_document[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang=\"en\">\n"
|
||||
"<head>\n"
|
||||
@ -130,12 +113,13 @@ const char *http_default_document =
|
||||
"\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"
|
||||
"\t<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/favicon.ico\"/>\n"
|
||||
"%5$s"
|
||||
"\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\thtml{font-family:\"Arial\",sans-serif;--error:" HTTP_COLOR_ERROR ";--warning:" HTTP_COLOR_WARNING ";--success:" HTTP_COLOR_SUCCESS ";--info:" HTTP_COLOR_INFO ";--color:var(--%4$s);}\n"
|
||||
"\t\tbody{background-color:#F0F0F0;margin:0;}\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\tsection{margin:1em;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em;}\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"
|
||||
@ -153,55 +137,62 @@ const char *http_default_document =
|
||||
"\t<main>\n"
|
||||
"\t\t<section>\n"
|
||||
"%3$s"
|
||||
"\t\t\t<div class=\"footer\"><a href=\"https://%7$s/\">%7$s</a> - Necronda web server " NECRONDA_VERSION "</div>\n"
|
||||
"\t\t\t<div class=\"footer\"><a href=\"https://%7$s/\">%7$s</a> - " SERVER_STR_HTML "</div>\n"
|
||||
"\t\t</section>\n"
|
||||
"\t</main>\n"
|
||||
"</body>\n"
|
||||
"</html>\n";
|
||||
|
||||
const char *http_error_document =
|
||||
const char http_error_document[] =
|
||||
"\t\t\t<h1>%1$i</h1>\n"
|
||||
"\t\t\t<h2>%2$s :(</h2>\n"
|
||||
"\t\t\t<p>%3$s</p>\n"
|
||||
"\t\t\t<p>%4$s</p>\n";
|
||||
|
||||
const char *http_error_icon =
|
||||
"\t<link rel=\"shortcut icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
const char http_error_icon[] =
|
||||
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
|
||||
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNDMDAwMDAiIHN0eWxlPSJmb250LWZhbWls"
|
||||
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjooPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
|
||||
|
||||
|
||||
void http_to_camel_case(char *str, int mode);
|
||||
const char http_warning_document[] =
|
||||
"\t\t\t<h1>%1$i</h1>\n"
|
||||
"\t\t\t<h2>%2$s :o</h2>\n"
|
||||
"\t\t\t<p>%3$s</p>\n"
|
||||
"\t\t\t<p>%4$s</p>\n";
|
||||
|
||||
void http_free_hdr(http_hdr *hdr);
|
||||
const char http_warning_icon[] =
|
||||
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
|
||||
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNFMEMwMDAiIHN0eWxlPSJmb250LWZhbWls"
|
||||
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjpvPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
|
||||
|
||||
void http_free_req(http_req *req);
|
||||
|
||||
void http_free_res(http_res *res);
|
||||
const char http_success_document[] =
|
||||
"\t\t\t<h1>%1$i</h1>\n"
|
||||
"\t\t\t<h2>%2$s :)</h2>\n"
|
||||
"\t\t\t<p>%3$s</p>\n"
|
||||
"\t\t\t<p>%4$s</p>\n";
|
||||
|
||||
int http_receive_request(sock *client, http_req *req);
|
||||
const char http_success_icon[] =
|
||||
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
|
||||
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiMwMDgwMDAiIHN0eWxlPSJmb250LWZhbWls"
|
||||
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
|
||||
|
||||
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) ;
|
||||
|
||||
char *http_get_header_field(const http_hdr *hdr, const char *field_name);
|
||||
const char http_info_document[] =
|
||||
"\t\t\t<h1>%1$i</h1>\n"
|
||||
"\t\t\t<h2>%2$s :)</h2>\n"
|
||||
"\t\t\t<p>%3$s</p>\n"
|
||||
"\t\t\t<p>%4$s</p>\n";
|
||||
|
||||
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
|
||||
const char http_info_icon[] =
|
||||
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
|
||||
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiM2MDYwNjAiIHN0eWxlPSJmb250LWZhbWls"
|
||||
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
|
||||
|
||||
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);
|
||||
|
||||
const char *http_get_status_color(http_status *status);
|
||||
|
||||
char *http_format_date(time_t time, char *buf, size_t size);
|
||||
|
||||
char *http_get_date(char *buf, size_t size);
|
||||
|
||||
#endif //NECRONDA_SERVER_HTTP_H
|
||||
const int http_statuses_size = sizeof(http_statuses);
|
||||
const int http_status_messages_size = sizeof(http_status_messages);
|
@ -1,37 +1,12 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* FastCGI interface implementation (header file)
|
||||
* src/fastcgi.h
|
||||
* Lorenz Stechauner, 2020-12-26
|
||||
* FastCGI header file
|
||||
* src/lib/include/fastcgi.h
|
||||
* Lorenz Stechauner, 2021-05-03
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_FASTCGI_H
|
||||
#define NECRONDA_SERVER_FASTCGI_H
|
||||
|
||||
#define FASTCGI_CHUNKED 1
|
||||
#define FASTCGI_COMPRESS 2
|
||||
|
||||
typedef struct {
|
||||
int socket;
|
||||
unsigned short req_id;
|
||||
char *out_buf;
|
||||
unsigned short out_len;
|
||||
unsigned short out_off;
|
||||
} fastcgi_conn;
|
||||
|
||||
char *fastcgi_add_param(char *buf, const char *key, const char *value);
|
||||
|
||||
int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_num, const sock *client,
|
||||
const http_req *req, const http_uri *uri);
|
||||
|
||||
int fastcgi_close_stdin(fastcgi_conn *conn);
|
||||
|
||||
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg);
|
||||
|
||||
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags);
|
||||
|
||||
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len);
|
||||
|
||||
#ifndef NECRONDA_SERVER_EXTERN_FASTCGI_H
|
||||
#define NECRONDA_SERVER_EXTERN_FASTCGI_H
|
||||
|
||||
/*
|
||||
* Listening socket file number
|
||||
@ -144,4 +119,4 @@ typedef struct {
|
||||
FCGI_UnknownTypeBody body;
|
||||
} FCGI_UnknownTypeRecord;
|
||||
|
||||
#endif //NECRONDA_SERVER_FASTCGI_H
|
||||
#endif //NECRONDA_SERVER_EXTERN_FASTCGI_H
|
@ -1,19 +1,161 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Reverse proxy
|
||||
* src/rev_proxy.c
|
||||
* src/lib/rev_proxy.c
|
||||
* Lorenz Stechauner, 2021-01-07
|
||||
*/
|
||||
|
||||
#include "rev_proxy.h"
|
||||
#include "utils.h"
|
||||
#include "compress.h"
|
||||
#include "../necronda-server.h"
|
||||
#include <openssl/ssl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <openssl/err.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
sock rev_proxy;
|
||||
char *rev_proxy_host = NULL;
|
||||
struct timeval server_timeout = {.tv_sec = SERVER_TIMEOUT, .tv_usec = 0};
|
||||
|
||||
int rev_proxy_preload() {
|
||||
rev_proxy.buf = NULL;
|
||||
rev_proxy.buf_len = 0;
|
||||
rev_proxy.buf_off = 0;
|
||||
rev_proxy.ctx = SSL_CTX_new(TLS_client_method());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rev_proxy_request_header(http_req *req, int enc) {
|
||||
char buf1[256];
|
||||
char buf2[256];
|
||||
int p_len;
|
||||
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "Connection", "keep-alive");
|
||||
|
||||
char *via = http_get_header_field(&req->hdr, "Via");
|
||||
sprintf(buf1, "HTTP/%s %s", req->version, DEFAULT_HOST);
|
||||
if (via == NULL) {
|
||||
http_add_header_field(&req->hdr, "Via", buf1);
|
||||
} else {
|
||||
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
|
||||
if (p_len < 0 || p_len >= sizeof(buf2)) {
|
||||
print(ERR_STR "Header field 'Via' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
http_remove_header_field(&req->hdr, "Via", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "Via", buf2);
|
||||
}
|
||||
|
||||
char *host = http_get_header_field(&req->hdr, "Host");
|
||||
char *forwarded = http_get_header_field(&req->hdr, "Forwarded");
|
||||
int client_ipv6 = strchr(client_addr_str, ':') != NULL;
|
||||
int server_ipv6 = strchr(server_addr_str, ':') != NULL;
|
||||
|
||||
p_len = snprintf(buf1, sizeof(buf1), "by=%s%s%s;for=%s%s%s;host=%s;proto=%s",
|
||||
server_ipv6 ? "\"[" : "", server_addr_str, server_ipv6 ? "]\"" : "",
|
||||
client_ipv6 ? "\"[" : "", client_addr_str, client_ipv6 ? "]\"" : "",
|
||||
host, enc ? "https" : "http");
|
||||
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
||||
print(ERR_STR "Appended part of header field 'Forwarded' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
if (forwarded == NULL) {
|
||||
http_add_header_field(&req->hdr, "Forwarded", buf1);
|
||||
} else {
|
||||
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", forwarded, buf1);
|
||||
if (p_len < 0 || p_len >= sizeof(buf2)) {
|
||||
print(ERR_STR "Header field 'Forwarded' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
http_remove_header_field(&req->hdr, "Forwarded", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "Forwarded", buf2);
|
||||
}
|
||||
|
||||
char *xff = http_get_header_field(&req->hdr, "X-Forwarded-For");
|
||||
if (xff == NULL) {
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-For", client_addr_str);
|
||||
} else {
|
||||
sprintf(buf1, "%s, %s", xff, client_addr_str);
|
||||
http_remove_header_field(&req->hdr, "X-Forwarded-For", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-For", buf1);
|
||||
}
|
||||
|
||||
char *xfh = http_get_header_field(&req->hdr, "X-Forwarded-Host");
|
||||
if (xfh == NULL) {
|
||||
if (forwarded == NULL) {
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-Host", host);
|
||||
} else {
|
||||
char *ptr = strchr(forwarded, ',');
|
||||
unsigned long len;
|
||||
if (ptr != NULL) len = ptr - forwarded;
|
||||
else len = strlen(forwarded);
|
||||
ptr = strstr(forwarded, "host=");
|
||||
if ((ptr - forwarded) < len) {
|
||||
char *end = strchr(ptr, ';');
|
||||
if (end == NULL) len -= (ptr - forwarded);
|
||||
else len = (end - ptr);
|
||||
len -= 5;
|
||||
sprintf(buf1, "%.*s", (int) len, ptr + 5);
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-Host", buf1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *xfp = http_get_header_field(&req->hdr, "X-Forwarded-Proto");
|
||||
if (xfp == NULL) {
|
||||
if (forwarded == NULL) {
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-Proto", enc ? "https" : "http");
|
||||
} else {
|
||||
char *ptr = strchr(forwarded, ',');
|
||||
unsigned long len;
|
||||
if (ptr != NULL) len = ptr - forwarded;
|
||||
else len = strlen(forwarded);
|
||||
ptr = strstr(forwarded, "proto=");
|
||||
if ((ptr - forwarded) < len) {
|
||||
char *end = strchr(ptr, ';');
|
||||
if (end == NULL) len -= (ptr - forwarded);
|
||||
else len = (end - ptr);
|
||||
len -= 6;
|
||||
sprintf(buf1, "%.*s", (int) len, ptr + 6);
|
||||
http_add_header_field(&req->hdr, "X-Forwarded-Proto", buf1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rev_proxy_response_header(http_req *req, http_res *res) {
|
||||
char buf1[256];
|
||||
char buf2[256];
|
||||
int p_len;
|
||||
|
||||
char *via = http_get_header_field(&res->hdr, "Via");
|
||||
p_len = snprintf(buf1, sizeof(buf1), "HTTP/%s %s", req->version, DEFAULT_HOST);
|
||||
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
||||
print(ERR_STR "Appended part of header field 'Via' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
if (via == NULL) {
|
||||
http_add_header_field(&res->hdr, "Via", buf1);
|
||||
} else {
|
||||
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
|
||||
if (p_len < 0 || p_len >= sizeof(buf2)) {
|
||||
print(ERR_STR "Header field 'Via' too long" CLR_STR);
|
||||
return -1;
|
||||
}
|
||||
http_remove_header_field(&res->hdr, "Via", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&res->hdr, "Via", buf2);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rev_proxy_init(http_req *req, http_res *res, host_config *conf, sock *client, http_status *custom_status,
|
||||
char * err_msg) {
|
||||
char *err_msg) {
|
||||
char buffer[CHUNK_SIZE];
|
||||
long ret;
|
||||
int tries = 0;
|
||||
@ -97,10 +239,11 @@ int rev_proxy_init(http_req *req, http_res *res, host_config *conf, sock *client
|
||||
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 = rev_proxy_request_header(req, (int) client->enc);
|
||||
if (ret != 0) {
|
||||
res->status = http_get_status(500);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = http_send_request(&rev_proxy, req);
|
||||
if (ret < 0) {
|
||||
@ -222,6 +365,12 @@ int rev_proxy_init(http_req *req, http_res *res, host_config *conf, sock *client
|
||||
}
|
||||
sock_recv(&rev_proxy, buffer, header_len, 0);
|
||||
|
||||
ret = rev_proxy_response_header(req, res);
|
||||
if (ret != 0) {
|
||||
res->status = http_get_status(500);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
proxy_err:
|
||||
@ -229,46 +378,110 @@ int rev_proxy_init(http_req *req, http_res *res, host_config *conf, sock *client
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rev_proxy_send(sock *client, int chunked, unsigned long len_to_send) {
|
||||
int rev_proxy_send(sock *client, unsigned long len_to_send, int flags) {
|
||||
// TODO handle websockets
|
||||
long ret;
|
||||
char buffer[CHUNK_SIZE];
|
||||
char comp_out[CHUNK_SIZE];
|
||||
char buf[256];
|
||||
long len, snd_len;
|
||||
// TODO handle websockets
|
||||
int finish_comp = 0;
|
||||
char *ptr;
|
||||
|
||||
compress_ctx comp_ctx;
|
||||
if (flags & REV_PROXY_COMPRESS_BR) {
|
||||
flags &= ~REV_PROXY_COMPRESS_GZ;
|
||||
if (compress_init(&comp_ctx, COMPRESS_BR) != 0) {
|
||||
print(ERR_STR "Unable to init brotli: %s" CLR_STR, strerror(errno));
|
||||
flags &= ~REV_PROXY_COMPRESS_BR;
|
||||
}
|
||||
} else if (flags & REV_PROXY_COMPRESS_GZ) {
|
||||
flags &= ~REV_PROXY_COMPRESS_BR;
|
||||
if (compress_init(&comp_ctx, COMPRESS_GZ) != 0) {
|
||||
print(ERR_STR "Unable to init gzip: %s" CLR_STR, strerror(errno));
|
||||
flags &= ~REV_PROXY_COMPRESS_GZ;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
if (chunked) {
|
||||
snd_len = 0;
|
||||
if (flags & REV_PROXY_CHUNKED) {
|
||||
char *pos;
|
||||
ret = sock_recv(&rev_proxy, buffer, 16, MSG_PEEK);
|
||||
if (ret <= 0) {
|
||||
print("Unable to receive: %s", sock_strerror(&rev_proxy));
|
||||
break;
|
||||
}
|
||||
if (ret <= 0) goto err0;
|
||||
|
||||
len_to_send = strtol(buffer, NULL, 16);
|
||||
char *pos = strstr(buffer, "\r\n");
|
||||
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));
|
||||
err0:
|
||||
print("Unable to receive from server: %s", sock_strerror(&rev_proxy));
|
||||
break;
|
||||
}
|
||||
snd_len += ret;
|
||||
|
||||
if (len_to_send == 0 && (flags & REV_PROXY_COMPRESS)) {
|
||||
finish_comp = 1;
|
||||
len = 0;
|
||||
goto out;
|
||||
finish:
|
||||
compress_free(&comp_ctx);
|
||||
}
|
||||
}
|
||||
while (snd_len < len_to_send) {
|
||||
unsigned long avail_in, avail_out;
|
||||
ret = sock_recv(&rev_proxy, buffer, CHUNK_SIZE < (len_to_send - snd_len) ? CHUNK_SIZE : len_to_send - snd_len, 0);
|
||||
if (ret <= 0) {
|
||||
print("Unable to receive from server: %s", sock_strerror(&rev_proxy));
|
||||
break;
|
||||
}
|
||||
len = ret;
|
||||
ptr = buffer;
|
||||
out:
|
||||
avail_in = len;
|
||||
void *next_in = ptr;
|
||||
do {
|
||||
long buf_len = len;
|
||||
if (flags & REV_PROXY_COMPRESS) {
|
||||
avail_out = sizeof(comp_out);
|
||||
compress_compress(&comp_ctx, next_in + len - avail_in, &avail_in, comp_out, &avail_out,
|
||||
finish_comp);
|
||||
ptr = comp_out;
|
||||
buf_len = (int) (sizeof(comp_out) - avail_out);
|
||||
snd_len += (long) (len - avail_in);
|
||||
}
|
||||
if (buf_len != 0) {
|
||||
len = sprintf(buf, "%lX\r\n", buf_len);
|
||||
ret = 1;
|
||||
if (flags & REV_PROXY_CHUNKED) ret = sock_send(client, buf, len, 0);
|
||||
if (ret <= 0) goto err;
|
||||
ret = sock_send(client, ptr, buf_len, 0);
|
||||
if (ret <= 0) goto err;
|
||||
if (!(flags & REV_PROXY_COMPRESS)) snd_len += ret;
|
||||
if (flags & REV_PROXY_CHUNKED) ret = sock_send(client, "\r\n", 2, 0);
|
||||
if (ret <= 0) {
|
||||
err:
|
||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while ((flags & REV_PROXY_COMPRESS) && (avail_in != 0 || avail_out != sizeof(comp_out)));
|
||||
if (ret <= 0) break;
|
||||
if (finish_comp) goto finish;
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (flags & REV_PROXY_CHUNKED) sock_recv(&rev_proxy, buffer, 2, 0);
|
||||
} while ((flags & REV_PROXY_CHUNKED) && len_to_send > 0);
|
||||
|
||||
if (ret <= 0) return (int) -1;
|
||||
|
||||
if (flags & REV_PROXY_CHUNKED) {
|
||||
ret = sock_send(client, "0\r\n\r\n", 5, 0);
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
||||
return -1;
|
||||
}
|
||||
} while (chunked && len_to_send > 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
32
src/lib/rev_proxy.h
Normal file
32
src/lib/rev_proxy.h
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Reverse proxy (header file)
|
||||
* src/lib/rev_proxy.h
|
||||
* Lorenz Stechauner, 2021-01-07
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_REV_PROXY_H
|
||||
#define NECRONDA_SERVER_REV_PROXY_H
|
||||
|
||||
#define REV_PROXY_CHUNKED 1
|
||||
#define REV_PROXY_COMPRESS_GZ 2
|
||||
#define REV_PROXY_COMPRESS_BR 4
|
||||
#define REV_PROXY_COMPRESS 6
|
||||
|
||||
#include "http.h"
|
||||
#include "config.h"
|
||||
|
||||
extern sock rev_proxy;
|
||||
|
||||
int rev_proxy_preload();
|
||||
|
||||
int rev_proxy_request_header(http_req *req, int enc);
|
||||
|
||||
int rev_proxy_response_header(http_req *req, http_res *res);
|
||||
|
||||
int rev_proxy_init(http_req *req, http_res *res, host_config *conf, sock *client, http_status *custom_status,
|
||||
char *err_msg);
|
||||
|
||||
int rev_proxy_send(sock *client, unsigned long len_to_send, int flags);
|
||||
|
||||
#endif //NECRONDA_SERVER_REV_PROXY_H
|
@ -1,11 +1,16 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Basic TCP and TLS socket
|
||||
* src/sock.c
|
||||
* src/lib/sock.c
|
||||
* Lorenz Stechauner, 2021-01-07
|
||||
*/
|
||||
|
||||
#include "sock.h"
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
const char *sock_strerror(sock *s) {
|
||||
if (s->_last_ret == 0) {
|
||||
@ -47,12 +52,12 @@ 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);
|
||||
s->_ssl_error = ERR_get_error();
|
||||
} 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;
|
||||
}
|
||||
|
||||
@ -64,12 +69,12 @@ long sock_recv(sock *s, void *buf, unsigned long len, int flags) {
|
||||
} else {
|
||||
ret = SSL_read(s->ssl, buf, (int) len);
|
||||
}
|
||||
s->_ssl_error = ERR_get_error();
|
||||
} 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;
|
||||
}
|
||||
|
||||
@ -81,7 +86,7 @@ long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigne
|
||||
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;
|
||||
next_len = ret;
|
||||
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;
|
||||
@ -92,14 +97,14 @@ long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigne
|
||||
|
||||
int sock_close(sock *s) {
|
||||
if ((int) s->enc && s->ssl != NULL) {
|
||||
SSL_shutdown(s->ssl);
|
||||
if (s->_last_ret >= 0) SSL_shutdown(s->ssl);
|
||||
SSL_free(s->ssl);
|
||||
s->ssl = NULL;
|
||||
}
|
||||
shutdown(s->socket, SHUT_RDWR);
|
||||
close(s->socket);
|
||||
s->socket = 0;
|
||||
s->enc = 0;
|
||||
s->ssl = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Basic TCP and TLS socket (header file)
|
||||
* src/sock.h
|
||||
* src/lib/sock.h
|
||||
* Lorenz Stechauner, 2021-01-07
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_SOCK_H
|
||||
#define NECRONDA_SERVER_SOCK_H
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
typedef struct {
|
||||
unsigned int enc:1;
|
||||
int socket;
|
@ -1,12 +1,14 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* URI and path handlers
|
||||
* src/uri.c
|
||||
* src/lib/uri.c
|
||||
* Lorenz Stechauner, 2020-12-13
|
||||
*/
|
||||
|
||||
#include "uri.h"
|
||||
|
||||
#include "utils.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int path_is_directory(const char *path) {
|
||||
struct stat statbuf;
|
||||
@ -28,6 +30,7 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
||||
char buf1[1024];
|
||||
char buf2[1024];
|
||||
char buf3[1024];
|
||||
int p_len;
|
||||
uri->webroot = NULL;
|
||||
uri->req_path = NULL;
|
||||
uri->path = NULL;
|
||||
@ -50,12 +53,12 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
||||
} else {
|
||||
query[0] = 0;
|
||||
query++;
|
||||
ssize_t size = strlen(query) + 1;
|
||||
long size = (long) strlen(query) + 1;
|
||||
uri->query = malloc(size);
|
||||
strcpy(uri->query, query);
|
||||
}
|
||||
|
||||
ssize_t size = strlen(uri_str) + 1;
|
||||
long size = (long) strlen(uri_str) + 1;
|
||||
uri->req_path = malloc(size);
|
||||
url_decode(uri_str, uri->req_path, &size);
|
||||
if (query != NULL) {
|
||||
@ -65,7 +68,7 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
||||
return 2;
|
||||
}
|
||||
|
||||
size = strlen(uri->req_path) + 1;
|
||||
size = (long) strlen(uri->req_path) + 1;
|
||||
uri->path = malloc(size);
|
||||
uri->pathinfo = malloc(size);
|
||||
|
||||
@ -89,10 +92,17 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
||||
} else {
|
||||
strcpy(uri->pathinfo, "");
|
||||
}
|
||||
|
||||
if (!path_exists(uri->webroot)) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
sprintf(buf0, "%s%s", uri->webroot, uri->path);
|
||||
sprintf(buf1, "%s.php", buf0);
|
||||
sprintf(buf2, "%s.html", buf0);
|
||||
p_len = snprintf(buf1, sizeof(buf1), "%s.php", buf0);
|
||||
if (p_len < 0 || p_len >= sizeof(buf1)) return -1;
|
||||
p_len = snprintf(buf2, sizeof(buf2), "%s.html", buf0);
|
||||
if (p_len < 0 || p_len >= sizeof(buf2)) return -1;
|
||||
|
||||
if (strlen(uri->path) <= 1 || path_exists(buf0) || path_is_file(buf1) || path_is_file(buf2)) {
|
||||
break;
|
||||
@ -101,7 +111,7 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
||||
char *ptr;
|
||||
parent_dir:
|
||||
ptr = strrchr(uri->path, '/');
|
||||
size = strlen(ptr);
|
||||
size = (long) strlen(ptr);
|
||||
sprintf(buf3, "%.*s%s", (int) size, ptr, uri->pathinfo);
|
||||
strcpy(uri->pathinfo, buf3);
|
||||
ptr[0] = 0;
|
||||
@ -114,7 +124,7 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
||||
if (path_is_file(buf0)) {
|
||||
uri->filename = malloc(strlen(buf0) + 1);
|
||||
strcpy(uri->filename, buf0);
|
||||
ssize_t len = strlen(uri->path);
|
||||
long len = (long) strlen(uri->path);
|
||||
if (strcmp(uri->path + len - 5, ".html") == 0) {
|
||||
uri->path[len - 5] = 0;
|
||||
} else if (strcmp(uri->path + len - 4, ".php") == 0) {
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* URI and path handlers (header file)
|
||||
* src/uri.h
|
||||
* src/lib/uri.h
|
||||
* Lorenz Stechauner, 2020-12-13
|
||||
*/
|
||||
|
||||
@ -19,7 +19,8 @@ typedef struct {
|
||||
char etag[64];
|
||||
char type[24];
|
||||
char charset[16];
|
||||
char filename_comp[256];
|
||||
char filename_comp_gz[256];
|
||||
char filename_comp_br[256];
|
||||
struct stat stat;
|
||||
} meta_data;
|
||||
|
133
src/lib/utils.c
Normal file
133
src/lib/utils.c
Normal file
@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Utilities
|
||||
* src/lib/utils.c
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
char *log_prefix;
|
||||
|
||||
char *format_duration(unsigned long micros, char *buf) {
|
||||
if (micros < 10000) {
|
||||
sprintf(buf, "%.1f ms", (double) micros / 1000);
|
||||
} else if (micros < 1000000) {
|
||||
sprintf(buf, "%li ms", micros / 1000);
|
||||
} else if (micros < 60000000) {
|
||||
sprintf(buf, "%.1f s", (double) micros / 1000000);
|
||||
} else if (micros < 6000000000) {
|
||||
sprintf(buf, "%.1f min", (double) micros / 1000000 / 60);
|
||||
} else {
|
||||
sprintf(buf, "%li min", micros / 1000000 / 60);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
int url_encode_component(const char *str, char *enc, long *size) {
|
||||
char *ptr = enc;
|
||||
char ch;
|
||||
memset(enc, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - enc) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@' || ch == '!' ||
|
||||
ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '+' || ch == ',' ||
|
||||
ch == ';' || ch == '=' || ch < ' ' || ch > '~') {
|
||||
if ((ptr - enc + 2) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
sprintf(ptr, "%%%02X", ch);
|
||||
ptr += 2;
|
||||
} else if (ch == ' ') {
|
||||
ptr[0] = '+';
|
||||
} else {
|
||||
ptr[0] = ch;
|
||||
}
|
||||
}
|
||||
*size = ptr - enc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int url_encode(const char *str, char *enc, long *size) {
|
||||
char *ptr = enc;
|
||||
unsigned char ch;
|
||||
memset(enc, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - enc) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch > 0x7F || ch == ' ') {
|
||||
if ((ptr - enc + 2) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
sprintf(ptr, "%%%02X", ch);
|
||||
ptr += 2;
|
||||
} else {
|
||||
ptr[0] = (char) ch;
|
||||
}
|
||||
}
|
||||
*size = ptr - enc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int url_decode(const char *str, char *dec, long *size) {
|
||||
char *ptr = dec;
|
||||
char ch, buf[3];
|
||||
memset(dec, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - dec) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch == '+') {
|
||||
ch = ' ';
|
||||
} else if (ch == '%') {
|
||||
memcpy(buf, str + i + 1, 2);
|
||||
buf[2] = 0;
|
||||
ch = (char) strtol(buf, NULL, 16);
|
||||
i += 2;
|
||||
} else if (ch == '?') {
|
||||
strcpy(ptr, str + i);
|
||||
break;
|
||||
}
|
||||
ptr[0] = ch;
|
||||
}
|
||||
*size = ptr - dec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mime_is_compressible(const char *type) {
|
||||
if (type == NULL) return 0;
|
||||
char type_parsed[64];
|
||||
strncpy(type_parsed, type, sizeof(type_parsed) - 1);
|
||||
char *pos = strchr(type_parsed, ';');
|
||||
if (pos != NULL) pos[0] = 0;
|
||||
return
|
||||
strncmp(type_parsed, "text/", 5) == 0 ||
|
||||
strncmp(type_parsed, "message/", 7) == 0 ||
|
||||
strstr(type_parsed, "+xml") != NULL ||
|
||||
strstr(type_parsed, "+json") != NULL ||
|
||||
strcmp(type_parsed, "application/javascript") == 0 ||
|
||||
strcmp(type_parsed, "application/json") == 0 ||
|
||||
strcmp(type_parsed, "application/xml") == 0 ||
|
||||
strcmp(type_parsed, "application/x-www-form-urlencoded") == 0 ||
|
||||
strcmp(type_parsed, "application/x-tex") == 0 ||
|
||||
strcmp(type_parsed, "application/x-httpd-php") == 0 ||
|
||||
strcmp(type_parsed, "application/x-latex") == 0 ||
|
||||
strcmp(type_parsed, "application/vnd.ms-fontobject") == 0 ||
|
||||
strcmp(type_parsed, "application/x-font-ttf") == 0 ||
|
||||
strcmp(type_parsed, "application/x-javascript") == 0 ||
|
||||
strcmp(type_parsed, "application/x-web-app-manifest+json") == 0 ||
|
||||
strcmp(type_parsed, "font/eot") == 0 ||
|
||||
strcmp(type_parsed, "font/opentype") == 0 ||
|
||||
strcmp(type_parsed, "image/bmp") == 0 ||
|
||||
strcmp(type_parsed, "image/vnd.microsoft.icon") == 0 ||
|
||||
strcmp(type_parsed, "image/x-icon") == 0;
|
||||
}
|
@ -1,14 +1,24 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Utilities (header file)
|
||||
* src/utils.h
|
||||
* src/lib/utils.h
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_UTILS_H
|
||||
#define NECRONDA_SERVER_UTILS_H
|
||||
|
||||
char *log_prefix;
|
||||
#include <stdio.h>
|
||||
|
||||
#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"
|
||||
|
||||
extern char *log_prefix;
|
||||
|
||||
#define out_1(fmt) fprintf(stdout, "%s" fmt "\n", log_prefix)
|
||||
#define out_2(fmt, args...) fprintf(stdout, "%s" fmt "\n", log_prefix, args)
|
||||
@ -22,10 +32,12 @@ char *log_prefix;
|
||||
|
||||
char *format_duration(unsigned long micros, char *buf);
|
||||
|
||||
int url_encode(const char *str, char *enc, ssize_t *size);
|
||||
int url_encode_component(const char *str, char *enc, long *size);
|
||||
|
||||
int encode_url(const char *str, char *enc, ssize_t *size);
|
||||
int url_encode(const char *str, char *enc, long *size);
|
||||
|
||||
int url_decode(const char *str, char *dec, ssize_t *size);
|
||||
int url_decode(const char *str, char *dec, long *size);
|
||||
|
||||
int mime_is_compressible(const char *type);
|
||||
|
||||
#endif //NECRONDA_SERVER_UTILS_H
|
@ -7,22 +7,37 @@
|
||||
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
|
||||
#include "necronda.h"
|
||||
#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"
|
||||
|
||||
#include "lib/cache.h"
|
||||
#include "lib/config.h"
|
||||
#include "lib/sock.h"
|
||||
#include "lib/rev_proxy.h"
|
||||
#include "lib/geoip.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/select.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <dirent.h>
|
||||
|
||||
int active = 1;
|
||||
const char *config_file;
|
||||
|
||||
int sockets[NUM_SOCKETS];
|
||||
pid_t children[MAX_CHILDREN];
|
||||
MMDB_s mmdbs[MAX_MMDB];
|
||||
|
||||
void openssl_init() {
|
||||
SSL_library_init();
|
||||
@ -260,6 +275,16 @@ int main(int argc, const char *argv[]) {
|
||||
closedir(geoip);
|
||||
}
|
||||
|
||||
ret = cache_init();
|
||||
if (ret < 0) {
|
||||
config_unload();
|
||||
return 1;
|
||||
} else if (ret != 0) {
|
||||
children[0] = ret; // pid
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
openssl_init();
|
||||
|
||||
client.buf = NULL;
|
||||
@ -268,15 +293,12 @@ int main(int argc, const char *argv[]) {
|
||||
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);
|
||||
SSL_CTX_set_min_proto_version(client.ctx, TLS1_VERSION);
|
||||
SSL_CTX_set_min_proto_version(client.ctx, TLS1_2_VERSION);
|
||||
SSL_CTX_set_mode(client.ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
||||
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());
|
||||
rev_proxy_preload();
|
||||
|
||||
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",
|
||||
@ -307,14 +329,6 @@ 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");
|
||||
|
||||
while (active) {
|
||||
@ -361,6 +375,7 @@ int main(int argc, const char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO outsource in thread
|
||||
int status = 0;
|
||||
for (int i = 0; i < MAX_CHILDREN; i++) {
|
||||
if (children[i] != 0) {
|
||||
|
@ -1,86 +1,37 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Main executable (header file)
|
||||
* src/necronda-server.c
|
||||
* src/necronda-server.h
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_NECRONDA_SERVER_H
|
||||
#define NECRONDA_SERVER_NECRONDA_SERVER_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/select.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/engine.h>
|
||||
#include <openssl/dh.h>
|
||||
#include <sys/time.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 REQ_PER_CONNECTION 200
|
||||
#define CLIENT_TIMEOUT 3600
|
||||
#define SERVER_TIMEOUT 4
|
||||
|
||||
#define CHUNK_SIZE 8192
|
||||
#define CLIENT_MAX_HEADER_SIZE 8192
|
||||
#define FILE_CACHE_SIZE 1024
|
||||
#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"
|
||||
|
||||
#define HTTP_1XX_STR "\x1B[1;32m"
|
||||
#define HTTP_2XX_STR "\x1B[1;32m"
|
||||
#define HTTP_3XX_STR "\x1B[1;33m"
|
||||
#define HTTP_4XX_STR "\x1B[1;31m"
|
||||
#define HTTP_5XX_STR "\x1B[1;31m"
|
||||
|
||||
#define NECRONDA_VERSION "4.2"
|
||||
#define SERVER_STR "Necronda/" NECRONDA_VERSION
|
||||
#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"
|
||||
# define DEFAULT_HOST "www.necronda.net"
|
||||
#endif
|
||||
|
||||
int sockets[NUM_SOCKETS];
|
||||
pid_t children[MAX_CHILDREN];
|
||||
MMDB_s mmdbs[MAX_MMDB];
|
||||
extern int sockets[NUM_SOCKETS];
|
||||
extern pid_t children[MAX_CHILDREN];
|
||||
extern MMDB_s mmdbs[MAX_MMDB];
|
||||
|
||||
char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr, *client_host_str;
|
||||
extern int server_keep_alive;
|
||||
extern char *log_client_prefix, *log_conn_prefix, *log_req_prefix, *client_geoip;
|
||||
extern char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr, *client_host_str;
|
||||
extern struct timeval client_timeout;
|
||||
|
||||
#endif //NECRONDA_SERVER_NECRONDA_SERVER_H
|
||||
|
15
src/necronda.h
Normal file
15
src/necronda.h
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Definitions
|
||||
* src/necronda.h
|
||||
* Lorenz Stechauner, 2021-05-04
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_NECRONDA_H
|
||||
#define NECRONDA_SERVER_NECRONDA_H
|
||||
|
||||
#define NECRONDA_VERSION "4.4"
|
||||
#define SERVER_STR "Necronda/" NECRONDA_VERSION
|
||||
#define SERVER_STR_HTML "Necronda web server " NECRONDA_VERSION
|
||||
|
||||
#endif //NECRONDA_SERVER_NECRONDA_H
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
* 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
|
157
src/utils.c
157
src/utils.c
@ -1,157 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Utilities
|
||||
* src/utils.c
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
char *format_duration(unsigned long micros, char *buf) {
|
||||
if (micros < 10000) {
|
||||
sprintf(buf, "%.1f ms", (double) micros / 1000);
|
||||
} else if (micros < 1000000) {
|
||||
sprintf(buf, "%li ms", micros / 1000);
|
||||
} else if (micros < 60000000) {
|
||||
sprintf(buf, "%.1f s", (double) micros / 1000000);
|
||||
} else if (micros < 6000000000) {
|
||||
sprintf(buf, "%.1f min", (double) micros / 1000000 / 60);
|
||||
} else {
|
||||
sprintf(buf, "%li min", micros / 1000000 / 60);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
int url_encode(const char *str, char *enc, ssize_t *size) {
|
||||
char *ptr = enc;
|
||||
char ch;
|
||||
memset(enc, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - enc) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@' || ch == '!' ||
|
||||
ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '+' || ch == ',' ||
|
||||
ch == ';' || ch == '=' || ch < ' ' || ch > '~') {
|
||||
if ((ptr - enc + 2) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
sprintf(ptr, "%%%02X", ch);
|
||||
ptr += 2;
|
||||
} else if (ch == ' ') {
|
||||
ptr[0] = '+';
|
||||
} else {
|
||||
ptr[0] = ch;
|
||||
}
|
||||
}
|
||||
*size = ptr - enc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int encode_url(const char *str, char *enc, ssize_t *size) {
|
||||
char *ptr = enc;
|
||||
unsigned char ch;
|
||||
memset(enc, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - enc) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch > 0x7F || ch == ' ') {
|
||||
if ((ptr - enc + 2) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
sprintf(ptr, "%%%02X", ch);
|
||||
ptr += 2;
|
||||
} else {
|
||||
ptr[0] = (char) ch;
|
||||
}
|
||||
}
|
||||
*size = ptr - enc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int url_decode(const char *str, char *dec, ssize_t *size) {
|
||||
char *ptr = dec;
|
||||
char ch, buf[3];
|
||||
memset(dec, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - dec) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch == '+') {
|
||||
ch = ' ';
|
||||
} else if (ch == '%') {
|
||||
memcpy(buf, str + i + 1, 2);
|
||||
buf[2] = 0;
|
||||
ch = (char) strtol(buf, NULL, 16);
|
||||
i += 2;
|
||||
}
|
||||
ptr[0] = ch;
|
||||
}
|
||||
*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;
|
||||
}
|
Reference in New Issue
Block a user