99 Commits
v4.2 ... v4.5

Author SHA1 Message Date
cd3bc9aa90 Implement * notation for hosts 2022-01-13 18:07:24 +01:00
6ab65abec9 Fix parsing error 2022-01-13 17:53:00 +01:00
7d6fa4682d Update version to 4.5 2022-01-13 15:39:04 +01:00
6f4cbb6e24 Update readme 2021-12-27 18:34:28 +01:00
174865b71c Fix malloc/alloca 2021-12-27 18:25:28 +01:00
4a002d6c31 Fix config file parsing 2021-12-27 17:17:17 +01:00
2a3f74e825 Increase stat_str size to 8 2021-12-27 17:16:42 +01:00
9eaa644fa1 Add SNI 2021-12-27 16:16:41 +01:00
ce91fecc80 Add image/gif to compressible mime types 2021-12-27 13:08:51 +01:00
15c160a60a Add image/vnd.microsoft.iconbinary 2021-12-06 23:33:40 +01:00
fd5e2302d4 Fix conf seg fault 2021-11-25 23:14:42 +01:00
8c13eecc51 Shorten margin statement in error doc 2021-11-25 10:08:09 +01:00
ae751925fa Fix FastCGI error code 502 -> 500 2021-11-24 23:27:27 +01:00
a1fee3d2ec Fix FastCGI compression 2021-11-21 16:31:26 +01:00
8721f07d00 Add <pre> formatting 2021-11-20 18:16:33 +01:00
81ad3fea3c Fix cache mkdir error handling 2021-11-20 16:46:47 +01:00
877ee12351 Fix msg_content 2021-11-20 14:27:19 +01:00
dfbe3bbb95 Updated warning document icon 2021-11-20 14:16:35 +01:00
6c13922f2f Add redirect document 2021-11-20 14:10:00 +01:00
171bca55fa Fixed reverse proxy timeout 2021-11-12 15:16:09 +01:00
fe0cdc9b1a rev_proxy_void working 2021-11-07 20:46:37 +01:00
930d4db2a2 EINPROGRESS also leads to 504 in rev proxy 2021-11-07 20:15:18 +01:00
b37757fc73 Add reverse proxy error documents 2021-11-07 19:48:09 +01:00
61c645eca8 Fix reverse proxy timeout 2021-10-23 16:31:54 +02:00
2fd71116ec Fix big FastCGI frames 2021-09-29 18:16:15 +02:00
495a3a6aaf Remove webroot from error documents 2021-09-29 17:45:07 +02:00
69dfc562af using return value from fastcgi_send and rev_proxy_send 2021-09-29 17:01:33 +02:00
a2383825ed Replace Script with FastCGI app 2021-09-29 16:58:38 +02:00
55f2318b57 Always use CLR_STR on end of PHP messages 2021-09-17 22:43:16 +02:00
eeb0ce7035 Changed config and cache directory from necronda-server to necronda/server 2021-09-17 22:43:08 +02:00
b5749ae991 Adjust logging width for IPv6 2021-09-17 22:43:05 +02:00
9adb4791df Added basic support for Necronda backend 2021-09-17 22:43:02 +02:00
eba916fad4 Fixed Error string in rev_proxy.c 2021-09-17 22:42:51 +02:00
ac2d17f23a Updated Makefile 2021-09-17 22:33:46 +02:00
433cc7861f Reverse proxy IPv6 and IPv4 2021-09-12 19:25:57 +02:00
52ea670d36 Better reverse proxy error messages 2021-09-12 19:24:26 +02:00
b4b5d77985 Updated PHP-FPM to 7.4 2021-09-12 18:58:05 +02:00
aebc731c4e fixed error status codes 2021-08-03 23:09:57 +02:00
4994b4375b Replace strncpy with snprintf where necessary 2021-05-11 22:10:02 +02:00
43c512dc5a Add gcc opimization 2021-05-10 18:12:08 +02:00
3f5eee236d Fix mime javascript types 2021-05-10 18:05:30 +02:00
0b157bcb74 Add Access-Control-Allow-Origin header for /.well-known/ directory 2021-05-09 12:25:39 +02:00
30b163c6fa Small fixes 2021-05-07 19:57:40 +02:00
cf2c0de697 Added Vary header 2021-05-06 20:55:44 +02:00
7aa47cac61 Fix includes 2021-05-06 20:46:17 +02:00
456deeae20 Bump version to 4.4 2021-05-05 21:58:14 +02:00
b08481818c Updated readme 2021-05-05 21:57:31 +02:00
49ad349775 Fix Content-Encoding header for FastCGI 2021-05-05 21:45:10 +02:00
2b823cabd6 ETag Content-Encoding bugfix 2021-05-05 21:37:36 +02:00
ecd4f16afe Transfer-Encoding implemented 2021-05-05 21:30:44 +02:00
45c5f20345 Preparing for transfer encoding compression 2021-05-05 20:40:23 +02:00
c42f27e961 Fix compression 2021-05-05 19:00:01 +02:00
de8ab406f6 Main executable cleanup 2021-05-05 18:46:24 +02:00
22d50ed4bd compression mode init fix 2021-05-05 18:22:50 +02:00
de44f4a3fe Cache dynamic mem 2021-05-05 18:17:57 +02:00
cc29250d76 FastCGI cleanup 2021-05-05 18:15:20 +02:00
c2f8f4c962 Unified compression interfaces 2021-05-05 18:07:12 +02:00
ff708230bd Fix FastCGI encoding (really really) 2021-05-04 23:14:35 +02:00
52ea541833 Fix FastCGI encoding (really) 2021-05-04 23:07:09 +02:00
f4bd426f3c Fix FastCGI compression 2021-05-04 22:57:44 +02:00
ffbbcc6490 Fix makefile 2021-05-04 22:47:37 +02:00
80986325ce Added brotli compression 2021-05-04 22:32:21 +02:00
10d405e745 Cache PID refactor 2021-05-03 21:04:52 +02:00
28f163f97a Added DEBIAN_OPTS 2021-05-03 20:48:16 +02:00
a8914aa981 Fixed tabs in docs/example.conf 2021-05-03 20:41:19 +02:00
2ada22481d Added docs/ 2021-05-03 20:40:17 +02:00
17b25a3596 Added description 2021-05-03 20:39:01 +02:00
d130474989 Refactor for shared library use 2021-05-03 20:10:23 +02:00
8dea0cd3fc Fixed if indents 2021-05-02 18:12:16 +02:00
0406cad0d8 Added mime_is_compressible 2021-05-02 18:08:57 +02:00
21b7ab585a Allow header only on 405 response 2021-05-02 16:05:02 +02:00
7587e15749 Added support for TRACE method 2021-04-29 22:21:53 +02:00
1b44752f91 Changed icon order in default document 2021-03-19 18:13:00 +01:00
5eeb9ef3c1 Http document alternate icon 2021-03-19 18:10:18 +01:00
dd15b9d906 Http and proxy refactor 2021-03-19 17:55:52 +01:00
12922a0661 Updated version string to 4.3 2021-03-18 20:37:07 +01:00
c0799101b1 Removed TODO escape IPv6 addresses 2021-03-18 20:32:09 +01:00
c1d076db04 Refactor code to avoid warnings 2021-03-18 20:27:45 +01:00
10464f3f30 Added Reverse Proxy Header Support 2021-03-18 19:56:50 +01:00
63781472fa Bugfix for config post processing 2021-03-16 22:07:19 +01:00
81931d287d Cache debug messages improvements 2021-03-16 21:57:33 +01:00
531ddb4880 Caching debug messages 2021-03-16 21:54:49 +01:00
e0d8ab31d5 Improved cache idle performance 2021-03-16 20:22:36 +01:00
3a36d54e9d Added standard favicon /favicon.ico to error document 2021-03-16 19:27:03 +01:00
33d9aa3a5d Date header field fix 2021-03-16 19:15:26 +01:00
f4d30206b0 Fix hidden paths 2021-03-11 22:09:32 +01:00
5b094ba98d Hidden paths -> 403 2021-03-11 22:05:38 +01:00
f60cdc8228 Added TODO for 3xx Redirects 2021-03-11 20:30:03 +01:00
ab1c4d6fd4 Changed 203 -> 404 2021-03-11 20:25:07 +01:00
26d54e9968 Added error documents and 203 response if webroot does not exist 2021-03-11 20:19:14 +01:00
b6c7d8f58e URL UTF-8 Bugfix 2021-01-24 21:20:48 +01:00
53fcceeafb Add FIXME 2021-01-17 18:10:50 +01:00
96567909db FastCGI error bugfix 2021-01-17 18:06:03 +01:00
4b3c067a75 Fix includes 2021-01-17 17:56:16 +01:00
77b80ca67b Bugfix splice 2021-01-13 21:21:24 +01:00
70e76d8783 Error doc update 2 2021-01-10 12:22:48 +01:00
6b1bc54cf3 Error doc update 2021-01-10 12:18:44 +01:00
e1edb48a3c Redirect to https even on reverse proxy 2021-01-09 12:57:24 +01:00
dc5d1bebcc Add TODO for access/error log file 2021-01-09 12:39:55 +01:00
35 changed files with 2561 additions and 1157 deletions

3
.gitignore vendored
View File

@ -1,7 +1,8 @@
*
!src
!src/**
!run.sh
!docs
!docs/**
!Makefile
!.gitignore
!README.md

View File

@ -1,19 +1,26 @@
.DEFAULT_GOAL := install
CC=gcc
CFLAGS=-std=gnu11 -Wall
LIBS=-lssl -lcrypto -lmagic -lz -lmaxminddb -lbrotlienc
DEBIAN_OPTS=-D CACHE_MAGIC_FILE="\"/usr/share/file/magic.mgc\"" -D PHP_FPM_SOCKET="\"/var/run/php/php7.4-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
$(CC) src/lib/*.c -o bin/libnecrondaserver.so --shared -fPIC $(CFLAGS) $(LIBS)
$(CC) src/necronda-server.c -o bin/necronda-server $(CFLAGS) $(LIBS) \
-Lbin -lnecrondaserver -Wl,-rpath=$(shell pwd)/bin
compile-debian:
compile-prod:
@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!"
$(CC) src/lib/*.c -o bin/libnecrondaserver.so --shared -fPIC $(CFLAGS) $(LIBS) $(DEBIAN_OPTS) -O3
$(CC) src/necronda-server.c -o bin/necronda-server $(CFLAGS) $(LIBS) $(DEBIAN_OPTS) -O3 \
-Lbin -lnecrondaserver -Wl,-rpath=$(shell pwd)/bin

View File

@ -1,2 +1,47 @@
# Necronda web server
Necronda web server
===================
## Features
* Full IPv4 and IPv6 support
* TLS Server Name Inspection (SNI)
* 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
* `geoip_dir` (optional) - path to a directory containing GeoIP databases
* `dns_server` (optional) - address of a DNS server
### Configuration
* `[cert <cert-name]` - begins section for a certificate
* `certificate` - path to SSL certificate (or certificate chain)
* `private_key` - path to SSL private key
* `[host <host>]` - begins section for the virtual host `<host>`
* `cert` - the name of the certificate to use
* 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
View 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
View File

@ -1,6 +0,0 @@
#!/bin/bash
echo "-- Building and starting Necronda Server..."
make compile && \
echo "-- Successfully finished compiling!" && \
echo "-- Starting Server..." && \
./bin/necronda-server $@

View File

@ -5,23 +5,42 @@
* 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++) {
host_config *hc = &config[i];
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
host_config *hc = &config->hosts[i];
if (hc->type == CONFIG_TYPE_UNSET) break;
if (strcmp(hc->name, host) == 0) return hc;
if (hc->name[0] == '*' && hc->name[1] == '.') {
const char *pos = strstr(host, hc->name + 1);
if (pos != NULL && strlen(pos) == strlen(hc->name + 1)) return hc;
}
}
return NULL;
}
@ -39,27 +58,31 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
struct timespec begin, end;
long ret;
int client_keep_alive;
char buf0[1024], buf1[1024];
char msg_buf[4096], msg_pre_buf[4096], err_msg[256];
char msg_buf[8192], msg_pre_buf_1[4096], msg_pre_buf_2[4096], err_msg[256];
char msg_content[1024];
char buffer[CHUNK_SIZE];
err_msg[0] = 0;
char host[256], *host_ptr, *hdr_connection;
host_config *conf = NULL;
long content_length = 0;
FILE *file = NULL;
msg_buf[0] = 0;
err_msg[0] = 0;
msg_content[0] = 0;
host_config *conf = NULL;
FILE *file = NULL;
long content_length = 0;
int accept_if_modified_since = 0;
int use_fastcgi = 0;
int use_rev_proxy = 0;
fastcgi_conn php_fpm = {.socket = 0, .req_id = 0};
int p_len;
fastcgi_conn fcgi_conn = {.socket = 0, .req_id = 0};
http_status custom_status;
http_res res;
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);
http_res res = {.version = "1.1", .status = http_get_status(501), .hdr.field_num = 0};
http_status_ctx ctx = {.status = 0, .origin = NONE};
clock_gettime(CLOCK_MONOTONIC, &begin);
@ -69,6 +92,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 +111,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 +127,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 +147,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%*s%s]%s ", getpid(), BLD_STR, INET6_ADDRSTRLEN, host, CLR_STR, log_client_prefix);
log_prefix = log_req_prefix;
print(BLD_STR "%s %s" CLR_STR, req.method, req.uri);
@ -141,10 +166,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 +186,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 +255,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 +277,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 +323,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,63 +373,59 @@ 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);
} else {
int mode;
if (strcmp(uri.filename + strlen(uri.filename) - 4, ".ncr") == 0) {
mode = FASTCGI_NECRONDA;
} else if (strcmp(uri.filename + strlen(uri.filename) - 4, ".php") == 0) {
mode = FASTCGI_PHP;
} else {
res.status = http_get_status(500);
print(ERR_STR "Invalid FastCGI extension: %s" CLR_STR, uri.filename);
goto respond;
}
struct stat statbuf;
stat(uri.filename, &statbuf);
char *last_modified = http_format_date(statbuf.st_mtime, buf0, sizeof(buf0));
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
res.status = http_get_status(200);
if (fastcgi_init(&php_fpm, client_num, req_num, client, &req, &uri) != 0) {
res.status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
if (fastcgi_init(&fcgi_conn, mode, client_num, req_num, client, &req, &uri) != 0) {
res.status = http_get_status(503);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
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);
ret = fastcgi_receive(&php_fpm, client, client_content_len);
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(&fcgi_conn, client, client_content_len);
if (ret != 0) {
if (ret < 0) {
goto abort;
} else {
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
}
res.status = http_get_status(502);
goto respond;
}
}
fastcgi_end:
fastcgi_close_stdin(&php_fpm);
fastcgi_close_stdin(&fcgi_conn);
ret = fastcgi_header(&php_fpm, &res, err_msg);
ret = fastcgi_header(&fcgi_conn, &res, err_msg);
if (ret != 0) {
if (ret < 0) {
goto abort;
}
if (ret < 0) goto abort;
goto respond;
}
char *status = http_get_header_field(&res.hdr, "Status");
if (status != NULL) {
int status_code = (int) strtoul(status, NULL, 10);
@ -338,21 +443,96 @@ 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");
const char *content_length_f = http_get_header_field(&res.hdr, "Content-Length");
content_length = (content_length_f == NULL) ? -1 : strtol(content_length_f, NULL, 10);
const char *content_type = http_get_header_field(&res.hdr, "Content-Type");
const char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
if (content_encoding == NULL &&
content_type != NULL &&
strncmp(content_type, "text/html", 9) == 0 &&
content_length != -1 &&
content_length <= sizeof(msg_content) - 1)
{
fastcgi_dump(&fcgi_conn, msg_content, sizeof(msg_content));
goto respond;
}
use_fastcgi = 1;
if (content_length != -1 && content_length < 1024000) {
use_fastcgi |= FASTCGI_COMPRESS_HOLD;
}
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);
http_remove_header_field(&res.hdr, "Content-Length", HTTP_REMOVE_ALL);
}
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);
ret = rev_proxy_init(&req, &res, conf, client, &custom_status, err_msg);
use_rev_proxy = ret == 0;
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, &ctx, conf, client, &custom_status, err_msg);
use_rev_proxy = (ret == 0);
// Let 300 be formatted by origin server
if (use_rev_proxy && res.status->code >= 301 && res.status->code < 600) {
const char *content_type = http_get_header_field(&res.hdr, "Content-Type");
const char *content_length_f = http_get_header_field(&res.hdr, "Content-Length");
const char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
if (content_encoding == NULL && content_type != NULL && content_length_f != NULL &&
strncmp(content_type, "text/html", 9) == 0)
{
long content_len = strtol(content_length_f, NULL, 10);
if (content_len <= sizeof(msg_content) - 1) {
ctx.status = res.status->code;
ctx.origin = res.status->code >= 400 ? SERVER : NONE;
use_rev_proxy = 0;
rev_proxy_dump(msg_content, content_len);
}
}
}
/*
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,31 +540,73 @@ 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);
if (!use_fastcgi && file == NULL) {
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Content-Type", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Content-Encoding", 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);
http_add_header_field(&res.hdr, "Cache-Control", "no-cache");
http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8");
// 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);
if (res.status->code >= 300 && res.status->code < 400 && msg_content[0] == 0) {
const char *location = http_get_header_field(&res.hdr, "Location");
if (location != NULL) {
snprintf(msg_content, sizeof(msg_content),
"<ul>\n\t<li><a href=\"%1$s\">%1$s</a></li>\n</ul>\n", location);
}
}
char *rev_proxy_doc = "";
if (conf != NULL && conf->type == CONFIG_TYPE_REVERSE_PROXY) {
const http_status *status = http_get_status(ctx.status);
char stat_str[8];
sprintf(stat_str, "%03i", ctx.status);
sprintf(msg_pre_buf_2, http_rev_proxy_document,
" success",
(ctx.origin == CLIENT_REQ) ? " error" : " success",
(ctx.origin == INTERNAL) ? " error" : " success",
(ctx.origin == SERVER_REQ) ? " error" : (ctx.status == 0 ? "" : " success"),
(ctx.origin == CLIENT_RES) ? " error" : " success",
(ctx.origin == SERVER) ? " error" : (ctx.status == 0 ? "" : " success"),
(ctx.origin == SERVER_RES) ? " error" : (ctx.status == 0 ? "" : " success"),
(ctx.origin == INTERNAL) ? " error" : " success",
(ctx.origin == INTERNAL || ctx.origin == SERVER) ? " error" : " success",
res.status->code,
res.status->msg,
(ctx.status == 0) ? "???" : stat_str,
(status != NULL) ? status->msg : "",
host);
rev_proxy_doc = msg_pre_buf_2;
}
sprintf(msg_pre_buf_1, 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_1, info->mode, info->icon, info->color, host,
rev_proxy_doc, msg_content[0] != 0 ? msg_content : "");
}
if (content_length >= 0) {
sprintf(buf0, "%li", content_length);
http_remove_header_field(&res.hdr, "Content-Length", HTTP_REMOVE_ALL);
http_add_header_field(&res.hdr, "Content-Length", buf0);
} else if (http_get_header_field(&res.hdr, "Transfer-Encoding") == NULL) {
server_keep_alive = 0;
}
} else {
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
http_add_header_field(&res.hdr, "Server", SERVER_STR);
}
char *conn = http_get_header_field(&res.hdr, "Connection");
int close_proxy = conn == NULL || (strcmp(conn, "keep-alive") != 0 && strcmp(conn, "Keep-Alive") != 0);
http_remove_header_field(&res.hdr, "Connection", HTTP_REMOVE_ALL);
@ -405,6 +627,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 +654,25 @@ 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);
fastcgi_send(&php_fpm, client, flags);
int flags = (chunked ? FASTCGI_CHUNKED : 0) | (use_fastcgi & (FASTCGI_COMPRESS | FASTCGI_COMPRESS_HOLD));
ret = fastcgi_send(&fcgi_conn, 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);
ret = rev_proxy_send(client, len_to_send, flags);
}
if (ret < 0) {
client_keep_alive = 0;
}
}
@ -457,10 +687,10 @@ int client_request_handler(sock *client, unsigned long client_num, unsigned int
uri_free(&uri);
abort:
if (php_fpm.socket != 0) {
shutdown(php_fpm.socket, SHUT_RDWR);
close(php_fpm.socket);
php_fpm.socket = 0;
if (fcgi_conn.socket != 0) {
shutdown(fcgi_conn.socket, SHUT_RDWR);
close(fcgi_conn.socket);
fcgi_conn.socket = 0;
}
http_free_req(&req);
http_free_res(&res);
@ -550,7 +780,7 @@ int client_connection_handler(sock *client, unsigned long client_num) {
if (pos != NULL) {
pos = strstr(pos, "\"iso_code\":");
pos += 12;
strncpy(client_cc, pos, 2);
snprintf(client_cc, sizeof(client_cc), "%s", pos);
}
}
@ -579,6 +809,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;
}
}
@ -637,16 +868,17 @@ int client_handler(sock *client, unsigned long client_num, struct sockaddr_in6 *
log_req_prefix = malloc(256);
log_client_prefix = malloc(256);
sprintf(log_client_prefix, "[%s%4i%s]%s[%*s][%5i]%s", (int) client->enc ? HTTPS_STR : HTTP_STR,
ntohs(server_addr->sin6_port), CLR_STR, color_table[client_num % 6], INET_ADDRSTRLEN, client_addr_str,
ntohs(server_addr->sin6_port), CLR_STR, color_table[client_num % 6], INET6_ADDRSTRLEN, client_addr_str,
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][%*s]%s ", getpid(), INET6_ADDRSTRLEN, 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 +893,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;
}

View File

@ -1,207 +0,0 @@
/**
* Necronda Web Server
* HTTP implementation (header file)
* src/net/http.h
* Lorenz Stechauner, 2020-12-09
*/
#ifndef NECRONDA_SERVER_HTTP_H
#define NECRONDA_SERVER_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[] = {
{100, "Informational", "Continue"},
{101, "Informational", "Switching Protocols"},
{200, "Success", "OK"},
{201, "Success", "Created"},
{202, "Success", "Accepted"},
{203, "Success", "Non-Authoritative Information"},
{204, "Success", "No Content"},
{205, "Success", "Reset Content"},
{206, "Success", "Partial Content"},
{300, "Redirection", "Multiple Choices"},
{301, "Redirection", "Moved Permanently"},
{302, "Redirection", "Found"},
{303, "Redirection", "See Other"},
{304, "Redirection", "Not Modified"},
{305, "Redirection", "Use Proxy"},
{307, "Redirection", "Temporary Redirect"},
{308, "Redirection", "Permanent Redirect"},
{400, "Client Error", "Bad Request"},
{401, "Client Error", "Unauthorized"},
{402, "Client Error", "Payment Required"},
{403, "Client Error", "Forbidden"},
{404, "Client Error", "Not Found"},
{405, "Client Error", "Method Not Allowed"},
{406, "Client Error", "Not Acceptable"},
{407, "Client Error", "Proxy Authentication Required"},
{408, "Client Error", "Request Timeout"},
{409, "Client Error", "Conflict"},
{410, "Client Error", "Gone"},
{411, "Client Error", "Length Required"},
{412, "Client Error", "Precondition Failed"},
{413, "Client Error", "Request Entity Too Large"},
{414, "Client Error", "Request-URI Too Long"},
{415, "Client Error", "Unsupported Media Type"},
{416, "Client Error", "Range Not Satisfiable"},
{417, "Client Error", "Expectation Failed"},
{500, "Server Error", "Internal Server Error"},
{501, "Server Error", "Not Implemented"},
{502, "Server Error", "Bad Gateway"},
{503, "Server Error", "Service Unavailable"},
{504, "Server Error", "Gateway Timeout"},
{505, "Server Error", "HTTP Version Not Supported"},
};
http_error_msg http_error_messages[] = {
{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."},
{404, "The server has not found anything matching the Request-URI."},
{405, "The method specified in the Request-Line is not allowed for the resource identified by the Request-URI."},
{406, "The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request."},
{407, "The request requires user authentication on the proxy."},
{408, "The client did not produce a request within the time that the server was prepared to wait."},
{409, "The request could not be completed due to a conflict with the current state of the resource."},
{410, "The requested resource is no longer available at the server and no forwarding address is known."},
{411, "The server refuses to accept the request without a defined Content-Length."},
{412, "The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server."},
{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."},
{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."},
{501, "The server does not support the functionality required to fulfill the request."},
{502, "The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."},
{503, "The server is currently unable to handle the request due to a temporary overloading or maintenance of the server."},
{504, "The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI or some other auxiliary server it needed to access in attempting to complete the request."},
{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 =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
"\t<title>%1$i %2$s - %7$s</title>\n"
"\t<meta charset=\"UTF-8\"/>\n"
"\t<meta name=\"theme-color\" content=\"%6$s\"/>\n"
"\t<meta name=\"color-scheme\" content=\"light dark\"/>\n"
"\t<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"/>\n"
"\t<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n"
"%5$s"
"\t<style>\n"
"\t\thtml{font-family:\"Arial\",sans-serif;--error:#C00000;--info:#E0C000;--color:var(--%4$s);}\n"
"\t\tbody{background-color:#F0F0F0;margin:0.5em;}\n"
"\t\tmain{max-width:650px;margin:2em auto;}\n"
"\t\tsection{margin:1em;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em 2em;}\n"
"\t\th1,h2,h3,h4,h5,h6,h7{text-align:center;color:var(--color);font-weight:normal;}\n"
"\t\th1{font-size:3em;margin:0.125em 0 0.125em 0;}\n"
"\t\th2{font-size:1.5em;margin:0.25em 0 1em 0;}\n"
"\t\tp{text-align:center;font-size:0.875em;}\n"
"\t\tdiv.footer{color:#808080;font-size:0.75em;text-align:center;margin:2em 0 0.5em 0;}\n"
"\t\tdiv.footer a{color:#808080;}\n"
"\t\t@media(prefers-color-scheme:dark){\n"
"\t\t\thtml{color:#FFFFFF;}\n"
"\t\t\tbody{background-color:#101010;}\n"
"\t\t\tsection{background-color:#181818;}\n"
"\t\t}\n"
"\t</style>\n"
"</head>\n"
"<body>\n"
"\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</section>\n"
"\t</main>\n"
"</body>\n"
"</html>\n";
const char *http_error_document =
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;(</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char *http_error_icon =
"\t<link rel=\"shortcut 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);
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);
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

View File

@ -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;
@ -44,96 +57,135 @@ int cache_process() {
}
cache = shm_rw;
if (mkdir("/var/necronda-server/", 0755) < 0) {
if (errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda-server/': %s" CLR_STR "\n", strerror(errno));
return -3;
}
if (mkdir("/var/necronda/", 0755) < 0 && errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda/': %s" CLR_STR "\n", strerror(errno));
return -3;
}
FILE *cache_file = fopen("/var/necronda-server/cache", "rb");
if (mkdir("/var/necronda/server/", 0755) < 0 && errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda/server/': %s" CLR_STR "\n", strerror(errno));
return -3;
}
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);
if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory %s: %s" CLR_STR "\n", buf, strerror(errno));
goto comp_err;
}
sprintf(buf, "%.*s/.necronda-server/cache", cache[i].webroot_len, cache[i].filename);
mkdir(buf, 0700);
if (mkdir(buf, 0700) != 0 && errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory %s: %s" CLR_STR "\n", buf, strerror(errno));
goto comp_err;
}
char *rel_path = cache[i].filename + cache[i].webroot_len + 1;
for (int j = 0; j < strlen(rel_path); j++) {
char ch = rel_path[j];
if (ch == '/') {
ch = '_';
}
if (ch == '/') ch = '_';
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 +193,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 +224,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 +243,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 +251,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 +282,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 +301,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 +314,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 +325,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 +334,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 +346,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 +361,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 +374,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;

View File

@ -1,19 +1,23 @@
/**
* 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
typedef struct {
char filename[256];
@ -22,9 +26,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
View 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
View 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

View File

@ -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>
t_config *config;
char 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, sizeof(t_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, sizeof(t_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);
@ -61,43 +68,82 @@ int config_load(const char *filename) {
fseek(file, 0, SEEK_END);
unsigned long len = ftell(file);
fseek(file, 0, SEEK_SET);
char *conf = malloc(len);
char *conf = alloca(len + 1);
fread(conf, 1, len, file);
conf[len] = 0;
fclose(file);
host_config *tmp_config = malloc(MAX_HOST_CONFIG * sizeof(host_config));
memset(tmp_config, 0, MAX_HOST_CONFIG * sizeof(host_config));
t_config *tmp_config = malloc(sizeof(t_config));
memset(tmp_config, 0, sizeof(t_config));
int i = 0;
int j = 0;
int line = 0;
int mode = 0;
char section = 0;
char *ptr = NULL;
char host[256], *source, *target;
host[0] = 0;
while ((ptr = strtok(ptr == NULL ? conf : NULL, "\n")) != NULL) {
char *source, *target;
while ((ptr = strsep(&conf, "\r\n")) != NULL) {
line++;
char *comment = strchr(ptr, '#');
if (comment != NULL) comment[0] = 0;
len = strlen(ptr);
char *end_ptr = ptr + len - 1;
while (end_ptr[0] == ' ' || end_ptr[0] == '\t') {
end_ptr[0] = 0;
end_ptr--;
}
len = strlen(ptr);
if (len == 0) continue;
if (ptr[0] == '[') {
if (ptr[len - 1] != ']') goto err;
strncpy(tmp_config[i].name, ptr + 1, len - 2);
i++;
ptr++;
int l = 0;
if (strncmp(ptr, "host", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
ptr += 4;
while (ptr[0] == ' ' || ptr[0] == '\t' || ptr[0] == ']') ptr++;
while (ptr[l] != ' ' && ptr[l] != '\t' && ptr[l] != ']') l++;
if (l == 0) goto err;
snprintf(tmp_config->hosts[i].name, sizeof(tmp_config->hosts[i].name), "%.*s", l, ptr);
i++;
section = 'h';
} else if (strncmp(ptr, "cert", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
ptr += 4;
while (ptr[0] == ' ' || ptr[0] == '\t' || ptr[0] == ']') ptr++;
while (ptr[l] != ' ' && ptr[l] != '\t' && ptr[l] != ']') l++;
if (l == 0) goto err;
snprintf(tmp_config->certs[j].name, sizeof(tmp_config->certs[j].name), "%.*s", l, ptr);
j++;
section = 'c';
} else {
goto err;
}
continue;
} else if (i == 0) {
if (len > 12 && strncmp(ptr, "certificate", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
source = ptr + 11;
target = cert_file;
} else if (len > 12 && strncmp(ptr, "private_key", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
source = ptr + 11;
target = key_file;
} else if (len > 10 && strncmp(ptr, "geoip_dir", 9) == 0 && (ptr[9] == ' ' || ptr[9] == '\t')) {
} else if (section == 0) {
if (len > 10 && strncmp(ptr, "geoip_dir", 9) == 0 && (ptr[9] == ' ' || ptr[9] == '\t')) {
source = ptr + 9;
target = geoip_dir;
} else if (len > 11 && strncmp(ptr, "dns_server", 10) == 0 && (ptr[10] == ' ' || ptr[10] == '\t')) {
source = ptr + 10;
target = dns_server;
} else {
goto err;
}
} else {
host_config *hc = &tmp_config[i - 1];
} else if (section == 'c') {
cert_config *cc = &tmp_config->certs[j - 1];
if (len > 12 && strncmp(ptr, "certificate", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
source = ptr + 11;
target = cc->full_chain;
} else if (len > 12 && strncmp(ptr, "private_key", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
source = ptr + 11;
target = cc->priv_key;
} else {
goto err;
}
} else if (section == 'h') {
host_config *hc = &tmp_config->hosts[i - 1];
if (len > 8 && strncmp(ptr, "webroot", 7) == 0 && (ptr[7] == ' ' || ptr[7] == '\t')) {
source = ptr + 7;
target = hc->local.webroot;
@ -106,6 +152,9 @@ int config_load(const char *filename) {
} else {
hc->type = CONFIG_TYPE_LOCAL;
}
} else if (len > 5 && strncmp(ptr, "cert", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
source = ptr + 4;
target = hc->cert_name;
} else if (len > 9 && strncmp(ptr, "dir_mode", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) {
source = ptr + 8;
target = NULL;
@ -148,39 +197,64 @@ int config_load(const char *filename) {
hc->rev_proxy.enc = 1;
}
continue;
} else {
goto err;
}
} else {
goto err;
}
char *end_ptr = source + strlen(source) - 1;
while (source[0] == ' ' || source[0] == '\t') source++;
while (end_ptr[0] == ' ' || end_ptr[0] == '\t') end_ptr--;
if (end_ptr <= source) {
if (strlen(source) == 0) {
err:
free(conf);
free(tmp_config);
fprintf(stderr, ERR_STR "Unable to parse config file" CLR_STR "\n");
fprintf(stderr, ERR_STR "Unable to parse config file (line %i)" CLR_STR "\n", line);
return -2;
}
end_ptr[1] = 0;
if (target != NULL) {
strcpy(target, source);
} else if (mode == 1) {
if (strcmp(source, "forbidden") == 0) {
tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN;
tmp_config->hosts[i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN;
} else if (strcmp(source, "info") == 0) {
tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_INFO;
tmp_config->hosts[i - 1].local.dir_mode = URI_DIR_MODE_INFO;
} else if (strcmp(source, "list") == 0) {
tmp_config[i - 1].local.dir_mode = URI_DIR_MODE_LIST;
tmp_config->hosts[i - 1].local.dir_mode = URI_DIR_MODE_LIST;
} else {
goto err;
}
} else if (mode == 2) {
tmp_config[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10);
tmp_config->hosts[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10);
}
}
free(conf);
for (int k = 0; k < i; k++) {
host_config *hc = &tmp_config->hosts[k];
if (hc->type == CONFIG_TYPE_LOCAL) {
char *webroot = tmp_config->hosts[k].local.webroot;
if (webroot[strlen(webroot) - 1] == '/') {
webroot[strlen(webroot) - 1] = 0;
}
}
if (hc->cert_name[0] == 0) goto err2;
int found = 0;
for (int m = 0; m < j; m++) {
if (strcmp(tmp_config->certs[m].name, hc->cert_name) == 0) {
hc->cert = m;
found = 1;
break;
}
}
if (!found) {
err2:
free(tmp_config);
fprintf(stderr, ERR_STR "Unable to parse config file" CLR_STR "\n");
return -2;
}
}
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);
@ -193,7 +267,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, sizeof(t_config));
free(tmp_config);
shmdt(shm_rw);
return 0;

View File

@ -1,22 +1,33 @@
/**
* 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_MAX_CERT_CONFIG 64
#define CONFIG_TYPE_UNSET 0
#define CONFIG_TYPE_LOCAL 1
#define CONFIG_TYPE_REVERSE_PROXY 2
#ifndef DEFAULT_CONFIG_FILE
# define DEFAULT_CONFIG_FILE "/etc/necronda/server.conf"
#endif
typedef struct {
int type;
char name[256];
char cert_name[256];
int cert;
union {
struct {
char hostname[256];
@ -30,10 +41,19 @@ typedef struct {
};
} host_config;
typedef struct {
char name[256];
char full_chain[256];
char priv_key[256];
} cert_config;
host_config *config;
char cert_file[256], key_file[256], geoip_dir[256], dns_server[256];
typedef struct {
host_config hosts[CONFIG_MAX_HOST_CONFIG];
cert_config certs[CONFIG_MAX_CERT_CONFIG];
} t_config;
extern t_config *config;
extern char geoip_dir[256], dns_server[256];
int config_init();

View File

@ -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;
@ -46,7 +49,7 @@ char *fastcgi_add_param(char *buf, const char *key, const char *value) {
return ptr;
}
int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_num, const sock *client,
int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned int req_num, const sock *client,
const http_req *req, const http_uri *uri) {
unsigned short req_id = (client_num & 0xFFF) << 4;
if (client_num == 0) {
@ -54,20 +57,28 @@ int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_n
} else {
req_id |= req_num & 0xF;
}
conn->mode = mode;
conn->req_id = req_id;
conn->out_buf = NULL;
conn->out_off = 0;
conn->webroot = uri->webroot;
int php_fpm = socket(AF_UNIX, SOCK_STREAM, 0);
if (php_fpm < 0) {
int fcgi_sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (fcgi_sock < 0) {
print(ERR_STR "Unable to create unix socket: %s" CLR_STR, strerror(errno));
return -1;
}
conn->socket = php_fpm;
conn->socket = fcgi_sock;
struct sockaddr_un php_fpm_addr = {AF_UNIX, PHP_FPM_SOCKET};
if (connect(conn->socket, (struct sockaddr *) &php_fpm_addr, sizeof(php_fpm_addr)) < 0) {
print(ERR_STR "Unable to connect to unix socket of PHP-FPM: %s" CLR_STR, strerror(errno));
struct sockaddr_un sock_addr = {AF_UNIX};
if (conn->mode == FASTCGI_NECRONDA) {
snprintf(sock_addr.sun_path, sizeof(sock_addr.sun_path) - 1, "%s", NECRONDA_BACKEND_SOCKET);
} else if (conn->mode == FASTCGI_PHP) {
snprintf(sock_addr.sun_path, sizeof(sock_addr.sun_path) - 1, "%s", PHP_FPM_SOCKET);
}
if (connect(conn->socket, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) < 0) {
print(ERR_STR "Unable to connect to unix socket of FastCGI socket: %s" CLR_STR, strerror(errno));
return -1;
}
@ -87,7 +98,7 @@ int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_n
{.roleB1 = (FCGI_RESPONDER >> 8) & 0xFF, .roleB0 = FCGI_RESPONDER & 0xFF, .flags = 0}
};
if (send(conn->socket, &begin, sizeof(begin), 0) != sizeof(begin)) {
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
print(ERR_STR "Unable to send to FastCGI socket: %s" CLR_STR, strerror(errno));
return -2;
}
@ -133,7 +144,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);
@ -170,7 +181,7 @@ int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_n
header.contentLengthB0 = param_len & 0xFF;
memcpy(param_buf, &header, sizeof(header));
if (send(conn->socket, param_buf, param_len + sizeof(header), 0) != param_len + sizeof(header)) {
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
print(ERR_STR "Unable to send to FastCGI socket: %s" CLR_STR, strerror(errno));
return -2;
}
@ -178,7 +189,7 @@ int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_n
header.contentLengthB1 = 0;
header.contentLengthB0 = 0;
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
print(ERR_STR "Unable to send to FastCGI socket: %s" CLR_STR, strerror(errno));
return -2;
}
@ -198,24 +209,28 @@ int fastcgi_close_stdin(fastcgi_conn *conn) {
};
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
print(ERR_STR "Unable to send to FastCGI socket: %s" CLR_STR, strerror(errno));
return -2;
}
return 0;
}
int fastcgi_php_error(char *msg, int msg_len, char *err_msg) {
int fastcgi_php_error(const fastcgi_conn *conn, 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 +238,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,9 +261,9 @@ 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, CLR_STR);
if (msg_type == 2 && ptr2 == ptr0) {
sprintf(err_msg, "%.*s", len2, ptr2);
strcpy_rem_webroot(err_msg, ptr2, len2, conn->webroot);
err = 1;
}
if (ptr3 == NULL) {
@ -265,6 +278,7 @@ int fastcgi_php_error(char *msg, int msg_len, char *err_msg) {
}
ptr0 = ptr1 + 13;
}
free(msg_str);
return err;
}
@ -272,20 +286,20 @@ 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) {
ret = recv(conn->socket, &header, sizeof(header), 0);
if (ret < 0) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
res->status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
return 1;
} else if (ret != sizeof(header)) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
res->status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
print(ERR_STR "Unable to receive from FastCGI socket" CLR_STR);
return 1;
}
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
@ -293,15 +307,15 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
content = malloc(content_len + header.paddingLength);
ret = recv(conn->socket, content, content_len + header.paddingLength, 0);
if (ret < 0) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
res->status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
free(content);
return 1;
} else if (ret != (content_len + header.paddingLength)) {
res->status = http_get_status(502);
sprintf(err_msg, "Unable to communicate with PHP-FPM.");
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
res->status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
print(ERR_STR "Unable to receive from FastCGI socket" CLR_STR);
free(content);
return 1;
}
@ -318,14 +332,17 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
}
if (app_status != 0) {
print(ERR_STR "Script terminated with exit code %i" CLR_STR, app_status);
print(ERR_STR "FastCGI app terminated with exit code %i" CLR_STR, app_status);
}
close(conn->socket);
conn->socket = 0;
free(content);
return 1;
} else if (header.type == FCGI_STDERR) {
err = err || fastcgi_php_error(content, content_len, err_msg);
// TODO implement Necronda backend error handling
if (conn->mode == FASTCGI_PHP) {
err = err || fastcgi_php_error(conn, content, content_len, err_msg);
}
} else if (header.type == FCGI_STDOUT) {
break;
} else {
@ -366,7 +383,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 +395,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 +403,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;
}
}
@ -408,25 +428,28 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
while (1) {
ret = recv(conn->socket, &header, sizeof(header), 0);
if (ret < 0) {
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
return -1;
} else if (ret != sizeof(header)) {
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
print(ERR_STR "Unable to receive from FastCGI socket: received len (%li) != header len (%li)" CLR_STR,
ret, sizeof(header));
return -1;
}
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
content = malloc(content_len + header.paddingLength);
ptr = content;
ret = recv(conn->socket, content, content_len + header.paddingLength, 0);
if (ret < 0) {
print(ERR_STR "Unable to receive from PHP-FPM: %s" CLR_STR, strerror(errno));
free(content);
return -1;
} else if (ret != (content_len + header.paddingLength)) {
print(ERR_STR "Unable to receive from PHP-FPM" CLR_STR);
free(content);
return -1;
long rcv_len = 0;
while (rcv_len < content_len + header.paddingLength) {
ret = recv(conn->socket, content + rcv_len, content_len + header.paddingLength - rcv_len, 0);
if (ret < 0) {
print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
free(content);
return -1;
}
rcv_len += ret;
}
if (header.type == FCGI_END_REQUEST) {
@ -437,7 +460,7 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
}
if (app_status != 0) {
print(ERR_STR "Script terminated with exit code %i" CLR_STR, app_status);
print(ERR_STR "FastCGI app terminated with exit code %i" CLR_STR, app_status);
}
close(conn->socket);
conn->socket = 0;
@ -448,7 +471,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 +480,23 @@ 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);
} else if (header.type == FCGI_STDOUT) {
out:
if (flags & FASTCGI_COMPRESS) {
strm.avail_in = content_len;
strm.next_in = (unsigned char *) ptr;
// TODO implement Necronda backend error handling
if (conn->mode == FASTCGI_PHP) {
fastcgi_php_error(conn, content, content_len, buf0);
}
} else if (header.type == FCGI_STDOUT) {
unsigned long avail_in, avail_out;
out:
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 +504,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);
@ -489,6 +513,72 @@ int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
}
}
int fastcgi_dump(fastcgi_conn *conn, char *buf, long len) {
FCGI_Header header;
long ret;
char buf0[256];
char *content, *ptr = buf;
unsigned short req_id, content_len;
if (conn->out_buf != NULL && conn->out_len > conn->out_off) {
ptr += snprintf(ptr, len, "%.*s", conn->out_len - conn->out_off, conn->out_buf + conn->out_off);
}
while (1) {
ret = recv(conn->socket, &header, sizeof(header), 0);
if (ret < 0) {
print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
return -1;
} else if (ret != sizeof(header)) {
print(ERR_STR "Unable to receive from FastCGI socket: received len (%li) != header len (%li)" CLR_STR,
ret, sizeof(header));
return -1;
}
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
content = malloc(content_len + header.paddingLength);
long rcv_len = 0;
while (rcv_len < content_len + header.paddingLength) {
ret = recv(conn->socket, content + rcv_len, content_len + header.paddingLength - rcv_len, 0);
if (ret < 0) {
print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
free(content);
return -1;
}
rcv_len += ret;
}
if (header.type == FCGI_END_REQUEST) {
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
int app_status = (body->appStatusB3 << 24) | (body->appStatusB2 << 16) | (body->appStatusB1 << 8) |
body->appStatusB0;
if (body->protocolStatus != FCGI_REQUEST_COMPLETE) {
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
}
if (app_status != 0) {
print(ERR_STR "FastCGI app terminated with exit code %i" CLR_STR, app_status);
}
close(conn->socket);
conn->socket = 0;
free(content);
return 0;
} else if (header.type == FCGI_STDERR) {
// TODO implement Necronda backend error handling
if (conn->mode == FASTCGI_PHP) {
fastcgi_php_error(conn, content, content_len, buf0);
}
} else if (header.type == FCGI_STDOUT) {
ptr += snprintf(ptr, len - (ptr - buf), "%.*s", content_len, content);
} else {
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
}
free(content);
}
}
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
unsigned long rcv_len = 0;
char *buf[16384];
@ -524,7 +614,7 @@ int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) goto err;
if (send(conn->socket, buf, ret, 0) != ret) {
err:
print(ERR_STR "Unable to send to PHP-FPM: %s" CLR_STR, strerror(errno));
print(ERR_STR "Unable to send to FastCGI socket: %s" CLR_STR, strerror(errno));
return -2;
}
}

57
src/lib/fastcgi.h Normal file
View File

@ -0,0 +1,57 @@
/**
* 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
#define FASTCGI_COMPRESS_HOLD 8
#define FASTCGI_PHP 1
#define FASTCGI_NECRONDA 2
#ifndef PHP_FPM_SOCKET
# define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock"
#endif
#define NECRONDA_BACKEND_SOCKET "/var/run/necronda/necronda-backend.sock"
typedef struct {
int mode;
int socket;
unsigned short req_id;
char *out_buf;
const char *webroot;
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, int mode, 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 fastcgi_conn *conn, 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_dump(fastcgi_conn *conn, char *buf, long len);
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len);
#endif //NECRONDA_SERVER_FASTCGI_H

69
src/lib/geoip.c Normal file
View 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
View 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

View File

@ -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,27 +115,27 @@ 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;
}
}
strncpy(req->method, ptr, pos1 - ptr - 1);
snprintf(req->method, sizeof(req->method), "%.*s", (int) (pos1 - ptr - 1), ptr);
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;
}

139
src/lib/http.h Normal file
View File

@ -0,0 +1,139 @@
/**
* 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&nbsp;web&nbsp;server"
#endif
typedef struct {
unsigned short code;
char type[16];
char msg[64];
} 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;
typedef enum {
NONE, INTERNAL, CLIENT_REQ, SERVER_REQ, SERVER, SERVER_RES, CLIENT_RES
} http_error_origin;
typedef struct {
unsigned short status;
http_error_origin origin;
} http_status_ctx;
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_rev_proxy_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

303
src/lib/http_static.c Normal file
View File

@ -0,0 +1,303 @@
/**
* Necronda Web Server
* HTTP static implementation
* src/lib/http_static.c
* Lorenz Stechauner, 2021-05-03
*/
#include "../necronda.h"
#include "http.h"
const http_status http_statuses[] = {
{100, "Informational", "Continue"},
{101, "Informational", "Switching Protocols"},
{200, "Success", "OK"},
{201, "Success", "Created"},
{202, "Success", "Accepted"},
{203, "Success", "Non-Authoritative Information"},
{204, "Success", "No Content"},
{205, "Success", "Reset Content"},
{206, "Success", "Partial Content"},
{300, "Redirection", "Multiple Choices"},
{301, "Redirection", "Moved Permanently"},
{302, "Redirection", "Found"},
{303, "Redirection", "See Other"},
{304, "Success", "Not Modified"},
{305, "Redirection", "Use Proxy"},
{307, "Redirection", "Temporary Redirect"},
{308, "Redirection", "Permanent Redirect"},
{400, "Client Error", "Bad Request"},
{401, "Client Error", "Unauthorized"},
{402, "Client Error", "Payment Required"},
{403, "Client Error", "Forbidden"},
{404, "Client Error", "Not Found"},
{405, "Client Error", "Method Not Allowed"},
{406, "Client Error", "Not Acceptable"},
{407, "Client Error", "Proxy Authentication Required"},
{408, "Client Error", "Request Timeout"},
{409, "Client Error", "Conflict"},
{410, "Client Error", "Gone"},
{411, "Client Error", "Length Required"},
{412, "Client Error", "Precondition Failed"},
{413, "Client Error", "Request Entity Too Large"},
{414, "Client Error", "Request-URI Too Long"},
{415, "Client Error", "Unsupported Media Type"},
{416, "Client Error", "Range Not Satisfiable"},
{417, "Client Error", "Expectation Failed"},
{500, "Server Error", "Internal Server Error"},
{501, "Server Error", "Not Implemented"},
{502, "Server Error", "Bad Gateway"},
{503, "Server Error", "Service Unavailable"},
{504, "Server Error", "Gateway Timeout"},
{505, "Server Error", "HTTP Version Not Supported"},
};
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."},
{404, "The server has not found anything matching the Request-URI."},
{405, "The method specified in the Request-Line is not allowed for the resource identified by the Request-URI."},
{406, "The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request."},
{407, "The request requires user authentication on the proxy."},
{408, "The client did not produce a request within the time that the server was prepared to wait."},
{409, "The request could not be completed due to a conflict with the current state of the resource."},
{410, "The requested resource is no longer available at the server and no forwarding address is known."},
{411, "The server refuses to accept the request without a defined Content-Length."},
{412, "The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server."},
{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 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."},
{501, "The server does not support the functionality required to fulfill the request."},
{502, "The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."},
{503, "The server is currently unable to handle the request due to a temporary overloading or maintenance of the server."},
{504, "The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI or some other auxiliary server it needed to access in attempting to complete the request."},
{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[] =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
"\t<title>%1$i %2$s - %7$s</title>\n"
"\t<meta charset=\"UTF-8\"/>\n"
"\t<meta name=\"theme-color\" content=\"%6$s\"/>\n"
"\t<meta name=\"color-scheme\" content=\"light dark\"/>\n"
"\t<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"/>\n"
"\t<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n"
"\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:" HTTP_COLOR_ERROR ";--warning:" HTTP_COLOR_WARNING ";--success:" HTTP_COLOR_SUCCESS ";--info:" HTTP_COLOR_INFO ";--soft:#808080;--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:2em 1em;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em;}\n"
"\t\th1,h2,h3,h4,h5,h6{text-align:center;color:var(--color);font-weight:normal;}\n"
"\t\th1{font-size:3em;margin:0.125em 0;}\n"
"\t\th2{font-size:1.5em;margin:0.25em 0 1em 0;}\n"
"\t\tp{text-align:center;font-size:0.875em;}\n"
"\t\tdiv.footer{color:var(--soft);font-size:0.75em;text-align:center;margin:2em 0 0.5em 0;}\n"
"\t\tdiv.footer a{color:var(--soft);}\n"
"\t\tul,ol{width:fit-content;margin:auto;}\n"
"\t\tpre{width:fit-content;margin:2em auto 0 auto;}\n"
"\n"
"\t\tsection.error-ctx{display:flex;padding:0;border:none;}\n"
"\t\tdiv.box{flex:100%% 1 1;border:1px solid var(--info);color:var(--info);position:relative;padding:1em;box-sizing:border-box;text-align:center;}\n"
"\t\tdiv.box.error{border-color:var(--error);color:var(--error);}\n"
"\t\tdiv.box.success{border-color:var(--success);color:var(--success);}\n"
"\t\tdiv.arrow{position:absolute;height:20px;width:30px;z-index:10;background-repeat:no-repeat;background-size:contain;}\n"
"\t\tdiv.arrow.response{left:-17.5px;bottom:calc(33.3333%% - 10px);}\n"
"\t\tdiv.arrow.request{right:-17.5px;top:calc(33.3333%% - 10px);}\n"
"\t\tdiv.border{flex:1px 0 0;background-color:var(--info);}\n"
"\t\tdiv.border.error{background-color:var(--error);}\n"
"\t\tdiv.border.success{background-color:var(--success);}\n"
"\t\tdiv.content>span{display:block;color:var(--soft);font-size:0.75em;}\n"
"\t\tdiv.content>img{height:3.75rem;margin:0.75rem auto;display:block;}\n"
"\t\th3{font-size:2.25em;margin:0.75rem 0 0 0;color:unset;height:2.5rem;}\n"
"\t\th4{font-size:1em;margin:0 0 0.75rem 0;color:unset;height:1.25rem;}\n"
"\n"
"\t\tdiv.arrow.request.success{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjRkZG"
"RkZGIiBzdHJva2U9IiMwMDgwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}\n"
"\t\tdiv.arrow.request.error{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjRkZG"
"RkZGIiBzdHJva2U9IiNDMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}\n"
"\t\tdiv.arrow.response.success{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiNGRkZG"
"RkYiIHN0cm9rZT0iIzAwODAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}\n"
"\t\tdiv.arrow.response.error{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiNGRkZG"
"RkYiIHN0cm9rZT0iI0MwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}\n"
"\n"
"\t\t@media(prefers-color-scheme:dark){\n"
"\t\t\thtml{color:#FFFFFF;--soft:#404040;}\n"
"\t\t\tbody{background-color:#101010;}\n"
"\t\t\tsection{background-color:#181818;}\n"
"\n"
"\t\t\tdiv.arrow.request.success{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjMTgx"
"ODE4IiBzdHJva2U9IiMwMDgwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}\n"
"\t\t\tdiv.arrow.request.error{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjMTgx"
"ODE4IiBzdHJva2U9IiNDMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}\n"
"\t\t\tdiv.arrow.response.success{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiMxODE4"
"MTgiIHN0cm9rZT0iIzAwODAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}\n"
"\t\t\tdiv.arrow.response.error{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiMxODE4"
"MTgiIHN0cm9rZT0iI0MwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}\n"
"\t\t}\n"
"\t\t@media(min-width:650px){\n"
"\t\t\tdiv.box:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px;border-right:none;}\n"
"\t\t\tdiv.box:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px;border-left:none;}\n"
"\t\t\tdiv.box:not(:last-child):not(:first-child){border-left:none;border-right:none;}\n"
"\t\t}\n"
"\t\t@media(max-width:650px){\n"
"\t\t\tsection.error-ctx{flex-direction:column;height:unset;}\n"
"\t\t\tdiv.box:first-child{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom:none;padding-top:1em;}\n"
"\t\t\tdiv.box:last-child{border-bottom-right-radius:4px;border-bottom-left-radius:4px;border-top:none;padding-bottom:1em;}\n"
"\t\t\tdiv.box:not(:last-child):not(:first-child){border-top:none;border-bottom:none;}\n"
"\t\t\tdiv.arrow.response{transform:rotate(90deg);top:-10px;left:calc(33.3333%% - 22.5px);right:unset;}\n"
"\t\t\tdiv.arrow.request{transform:rotate(90deg);bottom:-10px;right:calc(33.3333%% - 22.5px);top:unset;}\n"
"\t\t}\n"
"\t</style>\n"
"</head>\n"
"<body>\n"
"\t<main>\n"
"\t\t<section>\n"
"%3$s"
"%9$s"
"\t\t\t<div class=\"footer\"><a href=\"https://%7$s/\">%7$s</a> - " SERVER_STR_HTML "</div>\n"
"\t\t</section>\n"
"%8$s"
"\t</main>\n"
"</body>\n"
"</html>\n";
const char http_rev_proxy_document[] =
"\t\t<section class=\"error-ctx\">\n"
"\t\t\t<div class=\"box%1$s\">\n"
"\t\t\t\t<div class=\"content\">\n"
"\t\t\t\t\t<span>Client</span>\n"
"\t\t\t\t\t<img src=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTIsMzIgYTMwLDMwLDAsMSwwLDYwLDAgYTMwLDMwLDAsMSwwLC02MCww"
"IEw2MiwzMiBNNiwxNiBMNTgsMTYgTTYsNDggTDU4LDQ4IE0zMiwyIEwzMiw2MiBhMTUsMzAsMCwx"
"LDAsMCwtNjAgYTE1LDMwLDAsMSwwLDAsNjAgWiIgc3Ryb2tlPSIjMDA4MDAwIiBzdHJva2Utd2lk"
"dGg9IjIiIGZpbGw9IiMwMDAwMDAwMCIvPjwvc3ZnPgo=\"/>\n"
"\t\t\t\t\t<span>Your Browser</span>\n"
"\t\t\t\t</div>\n"
"\t\t\t\t<div class=\"arrow request%2$s\"></div>\n"
"\t\t\t</div>\n"
"\t\t\t<div class=\"border%8$s\"></div>\n"
"\t\t\t<div class=\"box%3$s\">\n"
"\t\t\t\t<div class=\"content\">\n"
"\t\t\t\t\t<span>Reverse Proxy</span>\n"
"\t\t\t\t\t<h3>%10$03i</h3>\n"
"\t\t\t\t\t<h4>%11$s</h4>\n"
"\t\t\t\t\t<span>" SERVER_NAME "</span>\n"
"\t\t\t\t</div>\n"
"\t\t\t\t<div class=\"arrow request%4$s\"></div>\n"
"\t\t\t\t<div class=\"arrow response%5$s\"></div>\n"
"\t\t\t</div>\n"
"\t\t\t<div class=\"border%9$s\"></div>\n"
"\t\t\t<div class=\"box%6$s\">\n"
"\t\t\t\t<div class=\"content\">\n"
"\t\t\t\t\t<span>Server</span>\n"
"\t\t\t\t\t<h3>%12$s</h3>\n"
"\t\t\t\t\t<h4>%13$s</h4>\n"
"\t\t\t\t\t<span>%14$s</span>\n"
"\t\t\t\t</div>\n"
"\t\t\t\t<div class=\"arrow response%7$s\"></div>\n"
"\t\t\t</div>\n"
"\t\t</section>\n";
const char http_error_document[] =
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;(</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char http_error_icon[] =
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNDMDAwMDAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjooPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
const char http_warning_document[] =
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;)</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char http_warning_icon[] =
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNFMEMwMDAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
const char http_success_document[] =
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;)</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char http_success_icon[] =
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiMwMDgwMDAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
const char http_info_document[] =
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;)</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char http_info_icon[] =
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiM2MDYwNjAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
const int http_statuses_size = sizeof(http_statuses);
const int http_status_messages_size = sizeof(http_status_messages);

View File

@ -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

541
src/lib/rev_proxy.c Normal file
View File

@ -0,0 +1,541 @@
/**
* Necronda Web Server
* Reverse proxy
* 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, SERVER_NAME);
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, SERVER_NAME);
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, http_status_ctx *ctx, host_config *conf, sock *client,
http_status *custom_status, char *err_msg) {
char buffer[CHUNK_SIZE];
long ret;
int tries = 0;
int retry = 0;
if (rev_proxy.socket != 0 && strcmp(rev_proxy_host, conf->name) == 0 && sock_check(&rev_proxy) == 0) {
goto rev_proxy;
}
retry:
if (rev_proxy.socket != 0) {
print(BLUE_STR "Closing proxy connection" CLR_STR);
sock_close(&rev_proxy);
}
retry = 0;
tries++;
rev_proxy.socket = socket(AF_INET6, SOCK_STREAM, 0);
if (rev_proxy.socket < 0) {
print(ERR_STR "Unable to create socket: %s" CLR_STR, strerror(errno));
res->status = http_get_status(500);
ctx->origin = INTERNAL;
return -1;
}
server_timeout.tv_sec = SERVER_TIMEOUT_INIT;
server_timeout.tv_usec = 0;
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
goto rev_proxy_timeout_err;
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
goto rev_proxy_timeout_err;
struct hostent *host_ent = gethostbyname2(conf->rev_proxy.hostname, AF_INET6);
if (host_ent == NULL) {
host_ent = gethostbyname2(conf->rev_proxy.hostname, AF_INET);
if (host_ent == NULL) {
res->status = http_get_status(503);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to connect to server: Name or service not known" CLR_STR);
sprintf(err_msg, "Unable to connect to server: Name or service not known.");
goto proxy_err;
}
}
struct sockaddr_in6 address = {.sin6_family = AF_INET6, .sin6_port = htons(conf->rev_proxy.port)};
if (host_ent->h_addrtype == AF_INET6) {
memcpy(&address.sin6_addr, host_ent->h_addr_list[0], host_ent->h_length);
} else if (host_ent->h_addrtype == AF_INET) {
unsigned char addr[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0};
memcpy(addr + 12, host_ent->h_addr_list[0], host_ent->h_length);
memcpy(&address.sin6_addr, addr, 16);
}
inet_ntop(address.sin6_family, (void *) &address.sin6_addr, buffer, sizeof(buffer));
print(BLUE_STR "Connecting to " BLD_STR "[%s]:%i" CLR_STR BLUE_STR "..." CLR_STR, buffer, conf->rev_proxy.port);
if (connect(rev_proxy.socket, (struct sockaddr *) &address, sizeof(address)) < 0) {
if (errno == ETIMEDOUT || errno == EINPROGRESS) {
res->status = http_get_status(504);
ctx->origin = SERVER_REQ;
} else if (errno == ECONNREFUSED) {
res->status = http_get_status(503);
ctx->origin = SERVER_REQ;
} else {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
}
print(ERR_STR "Unable to connect to [%s]:%i: %s" CLR_STR, buffer, conf->rev_proxy.port, strerror(errno));
sprintf(err_msg, "Unable to connect to server: %s.", strerror(errno));
goto proxy_err;
}
server_timeout.tv_sec = SERVER_TIMEOUT;
server_timeout.tv_usec = 0;
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
goto rev_proxy_timeout_err;
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0) {
rev_proxy_timeout_err:
res->status = http_get_status(500);
ctx->origin = INTERNAL;
print(ERR_STR "Unable to set timeout for reverse proxy socket: %s" CLR_STR, strerror(errno));
sprintf(err_msg, "Unable to set timeout for reverse proxy socket: %s", strerror(errno));
goto proxy_err;
}
if (conf->rev_proxy.enc) {
rev_proxy.ssl = SSL_new(rev_proxy.ctx);
SSL_set_fd(rev_proxy.ssl, rev_proxy.socket);
SSL_set_connect_state(rev_proxy.ssl);
ret = SSL_do_handshake(rev_proxy.ssl);
rev_proxy._last_ret = ret;
rev_proxy._errno = errno;
rev_proxy._ssl_error = ERR_get_error();
rev_proxy.enc = 1;
if (ret < 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to perform handshake: %s.", sock_strerror(&rev_proxy));
goto proxy_err;
}
}
rev_proxy_host = conf->name;
print(BLUE_STR "Established new connection with " BLD_STR "[%s]:%i" CLR_STR, buffer, conf->rev_proxy.port);
rev_proxy:
ret = rev_proxy_request_header(req, (int) client->enc);
if (ret != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
return -1;
}
ret = http_send_request(&rev_proxy, req);
if (ret < 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to send request to server (1): %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
retry = tries < 4;
goto proxy_err;
}
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
if (content_length != NULL) {
unsigned long content_len = strtoul(content_length, NULL, 10);
if (client->buf_len - client->buf_off > 0) {
unsigned long len = client->buf_len - client->buf_off;
if (len > content_len) {
len = content_len;
}
ret = sock_send(&rev_proxy, client->buf, len, 0);
if (ret <= 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to send request to server (2): %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
retry = tries < 4;
goto proxy_err;
}
content_len -= len;
}
if (content_len > 0) {
ret = sock_splice(&rev_proxy, client, buffer, sizeof(buffer), content_len);
if (ret <= 0) {
if (ret == -1) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to send request to server (3): %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
goto proxy_err;
} else if (ret == -2) {
res->status = http_get_status(400);
ctx->origin = CLIENT_REQ;
print(ERR_STR "Unable to receive request from client: %s" CLR_STR, sock_strerror(client));
sprintf(err_msg, "Unable to receive request from client: %s.", sock_strerror(client));
return -1;
}
res->status = http_get_status(500);
ctx->origin = INTERNAL;
print(ERR_STR "Unknown Error" CLR_STR);
return -1;
}
}
}
ret = sock_recv(&rev_proxy, buffer, sizeof(buffer), MSG_PEEK);
if (ret <= 0) {
int enc_err = sock_enc_error(&rev_proxy);
if (errno == EAGAIN || errno == EINPROGRESS || enc_err == SSL_ERROR_WANT_READ ||
enc_err == SSL_ERROR_WANT_WRITE)
{
res->status = http_get_status(504);
ctx->origin = SERVER_RES;
} else {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
}
print(ERR_STR "Unable to receive response from server: %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to receive response from server: %s.", sock_strerror(&rev_proxy));
goto proxy_err;
}
char *buf = buffer;
unsigned short header_len = (unsigned short) (strstr(buffer, "\r\n\r\n") - buffer + 4);
if (header_len <= 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
sprintf(err_msg, "Unable to parser header: End of header not found.");
goto proxy_err;
}
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
goto proxy_err;
}
}
char *ptr = buf;
while (header_len != (ptr - buf)) {
char *pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
sprintf(err_msg, "Unable to parse header: Invalid header format.");
goto proxy_err;
}
if (ptr == buf) {
if (strncmp(ptr, "HTTP/", 5) != 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
sprintf(err_msg, "Unable to parse header: Invalid header format.");
goto proxy_err;
}
int status_code = (int) strtol(ptr + 9, NULL, 10);
res->status = http_get_status(status_code);
if (res->status == NULL && status_code >= 100 && status_code <= 999) {
custom_status->code = status_code;
strcpy(custom_status->type, "");
snprintf(custom_status->msg, sizeof(custom_status->msg), "%.*s",
(int) (strchr(ptr, '\r') - ptr - 13), ptr + 13);
res->status = custom_status;
} else if (res->status == NULL) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: Invalid or unknown status code" CLR_STR);
sprintf(err_msg, "Unable to parse header: Invalid or unknown status code.");
goto proxy_err;
}
} else {
ret = http_parse_header_field(&res->hdr, ptr, pos0);
if (ret != 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header" CLR_STR);
sprintf(err_msg, "Unable to parse header.");
goto proxy_err;
}
}
if (pos0[2] == '\r' && pos0[3] == '\n') {
break;
}
ptr = pos0 + 2;
}
sock_recv(&rev_proxy, buffer, header_len, 0);
ret = rev_proxy_response_header(req, res);
if (ret != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
return -1;
}
return 0;
proxy_err:
if (retry) goto retry;
return -1;
}
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;
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 {
snd_len = 0;
if (flags & REV_PROXY_CHUNKED) {
char *pos;
ret = sock_recv(&rev_proxy, buffer, 16, MSG_PEEK);
if (ret <= 0) goto err0;
len_to_send = strtol(buffer, NULL, 16);
pos = strstr(buffer, "\r\n");
len = pos - buffer + 2;
sock_recv(&rev_proxy, buffer, len, 0);
if (ret <= 0) {
err0:
print("Unable to receive from server: %s", sock_strerror(&rev_proxy));
break;
}
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 (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;
}
}
return 0;
}
int rev_proxy_dump(char *buf, long len) {
sock_recv(&rev_proxy, buf, len, 0);
sock_close(&rev_proxy);
return 0;
}

38
src/lib/rev_proxy.h Normal file
View File

@ -0,0 +1,38 @@
/**
* 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
#ifndef SERVER_NAME
# define SERVER_NAME "revproxy"
#endif
#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, http_status_ctx *ctx, 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);
int rev_proxy_dump(char *buf, long len);
#endif //NECRONDA_SERVER_REV_PROXY_H

View File

@ -1,11 +1,20 @@
/**
* 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>
int sock_enc_error(sock *s) {
return (int) s->enc ? SSL_get_error(s->ssl, (int) s->_last_ret) : 0;
}
const char *sock_strerror(sock *s) {
if (s->_last_ret == 0) {
@ -16,7 +25,7 @@ const char *sock_strerror(sock *s) {
}
const char *err1 = ERR_reason_error_string(s->_ssl_error);
const char *err2 = strerror(errno);
switch (SSL_get_error(s->ssl, (int) s->_last_ret)) {
switch (sock_enc_error(s)) {
case SSL_ERROR_NONE:
return NULL;
case SSL_ERROR_ZERO_RETURN:
@ -47,12 +56,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 +73,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 +90,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 +101,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;
}

View File

@ -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;
@ -21,6 +23,8 @@ typedef struct {
unsigned long _ssl_error;
} sock;
int sock_enc_error(sock *s);
const char *sock_strerror(sock *s);
long sock_send(sock *s, void *buf, unsigned long len, int flags);

View File

@ -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,8 @@ 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];
char buf4[1024];
int p_len;
uri->webroot = NULL;
uri->req_path = NULL;
uri->path = NULL;
@ -50,12 +54,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 +69,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,50 +93,68 @@ 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.ncr", buf0);
if (p_len < 0 || p_len >= sizeof(buf1)) return -1;
p_len = snprintf(buf2, sizeof(buf2), "%s.php", buf0);
if (p_len < 0 || p_len >= sizeof(buf2)) return -1;
p_len = snprintf(buf3, sizeof(buf3), "%s.html", buf0);
if (p_len < 0 || p_len >= sizeof(buf3)) return -1;
if (strlen(uri->path) <= 1 || path_exists(buf0) || path_is_file(buf1) || path_is_file(buf2)) {
if (strlen(uri->path) <= 1 ||
path_exists(buf0) ||
path_is_file(buf1) ||
path_is_file(buf2) ||
path_is_file(buf3)) {
break;
}
char *ptr;
parent_dir:
ptr = strrchr(uri->path, '/');
size = strlen(ptr);
sprintf(buf3, "%.*s%s", (int) size, ptr, uri->pathinfo);
strcpy(uri->pathinfo, buf3);
size = (long) strlen(ptr);
sprintf(buf4, "%.*s%s", (int) size, ptr, uri->pathinfo);
strcpy(uri->pathinfo, buf4);
ptr[0] = 0;
}
if (uri->pathinfo[0] != 0) {
sprintf(buf3, "%s", uri->pathinfo + 1);
strcpy(uri->pathinfo, buf3);
sprintf(buf4, "%s", uri->pathinfo + 1);
strcpy(uri->pathinfo, buf4);
}
if (path_is_file(buf0)) {
uri->filename = malloc(strlen(buf0) + 1);
strcpy(uri->filename, buf0);
ssize_t len = 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) {
long len = (long) strlen(uri->path);
if (strcmp(uri->path + len - 4, ".ncr") == 0 || strcmp(uri->path + len - 4, ".php") == 0) {
uri->path[len - 4] = 0;
uri->is_static = 0;
} else if (strcmp(uri->path + len - 5, ".html") == 0) {
uri->path[len - 5] = 0;
}
} else if (path_is_file(buf1)) {
uri->is_static = 0;
uri->filename = malloc(strlen(buf1) + 1);
strcpy(uri->filename, buf1);
} else if (path_is_file(buf2)) {
uri->is_static = 0;
uri->filename = malloc(strlen(buf2) + 1);
strcpy(uri->filename, buf2);
} else if (path_is_file(buf3)) {
uri->filename = malloc(strlen(buf3) + 1);
strcpy(uri->filename, buf3);
} else {
uri->is_dir = 1;
strcpy(uri->path + strlen(uri->path), "/");
sprintf(buf1, "%s%sindex.php", uri->webroot, uri->path);
sprintf(buf2, "%s%sindex.html", uri->webroot, uri->path);
sprintf(buf1, "%s%sindex.ncr", uri->webroot, uri->path);
sprintf(buf2, "%s%sindex.php", uri->webroot, uri->path);
sprintf(buf3, "%s%sindex.html", uri->webroot, uri->path);
if (path_is_file(buf1)) {
uri->filename = malloc(strlen(buf1) + 1);
strcpy(uri->filename, buf1);
@ -140,6 +162,10 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
} else if (path_is_file(buf2)) {
uri->filename = malloc(strlen(buf2) + 1);
strcpy(uri->filename, buf2);
uri->is_static = 0;
} else if (path_is_file(buf3)) {
uri->filename = malloc(strlen(buf3) + 1);
strcpy(uri->filename, buf3);
} else {
if (dir_mode == URI_DIR_MODE_FORBIDDEN) {
uri->is_static = 1;
@ -159,7 +185,9 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
if (strcmp(uri->path + strlen(uri->path) - 5, "index") == 0) {
uri->path[strlen(uri->path) - 5] = 0;
}
if (strcmp(uri->pathinfo, "index.php") == 0 || strcmp(uri->pathinfo, "index.html") == 0) {
if (strcmp(uri->pathinfo, "index.ncr") == 0 ||
strcmp(uri->pathinfo, "index.php") == 0 ||
strcmp(uri->pathinfo, "index.html") == 0) {
uri->pathinfo[0] = 0;
}

View File

@ -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;

145
src/lib/utils.c Normal file
View File

@ -0,0 +1,145 @@
/**
* 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];
snprintf(type_parsed, sizeof(type_parsed), "%s", type);
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/gif") == 0 ||
strcmp(type_parsed, "image/vnd.microsoft.icon") == 0 ||
strcmp(type_parsed, "image/vnd.microsoft.iconbinary") == 0 ||
strcmp(type_parsed, "image/x-icon") == 0;
}
int strcpy_rem_webroot(char *dst, const char *src, long len, const char *webroot) {
strncpy(dst, src, len);
if (webroot == NULL) return 0;
char *pos;
while ((pos = strstr(dst, webroot)) != NULL) {
strcpy(pos, pos + strlen(webroot));
}
return 0;
}

View File

@ -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,14 @@ 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);
int strcpy_rem_webroot(char *dst, const char *str, long len, const char *webroot);
#endif //NECRONDA_SERVER_UTILS_H

View File

@ -7,22 +7,38 @@
#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];
SSL_CTX *contexts[CONFIG_MAX_CERT_CONFIG];
void openssl_init() {
SSL_library_init();
@ -31,6 +47,15 @@ void openssl_init() {
OpenSSL_add_all_algorithms();
}
static int ssl_servername_cb(SSL *ssl, int *ad, void *arg) {
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (servername != NULL) {
const host_config *conf = get_host_config(servername);
if (conf != NULL) SSL_set_SSL_CTX(ssl, contexts[conf->cert]);
}
return SSL_TLSEXT_ERR_OK;
}
void destroy() {
fprintf(stderr, "\n" ERR_STR "Terminating forcefully!" CLR_STR "\n");
int status = 0;
@ -260,37 +285,55 @@ 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;
client.buf_len = 0;
client.buf_off = 0;
client.ctx = SSL_CTX_new(TLS_server_method());
SSL_CTX_set_options(client.ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_verify(client.ctx, SSL_VERIFY_NONE, NULL);
SSL_CTX_set_min_proto_version(client.ctx, TLS1_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());
for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) {
const cert_config *conf = &config->certs[i];
if (conf->name[0] == 0) break;
if (SSL_CTX_use_certificate_chain_file(client.ctx, cert_file) != 1) {
fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), cert_file);
config_unload();
return 1;
}
if (SSL_CTX_use_PrivateKey_file(client.ctx, key_file, SSL_FILETYPE_PEM) != 1) {
fprintf(stderr, ERR_STR "Unable to load private key file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), key_file);
config_unload();
return 1;
contexts[i] = SSL_CTX_new(TLS_server_method());
SSL_CTX *ctx = contexts[i];
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
SSL_CTX_set_ecdh_auto(ctx, 1);
SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
if (SSL_CTX_use_certificate_chain_file(ctx, conf->full_chain) != 1) {
fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), conf->full_chain);
config_unload();
return 1;
}
if (SSL_CTX_use_PrivateKey_file(ctx, conf->priv_key, SSL_FILETYPE_PEM) != 1) {
fprintf(stderr, ERR_STR "Unable to load private key file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), conf->priv_key);
config_unload();
return 1;
}
}
client.ctx = contexts[0];
rev_proxy_preload();
for (int i = 0; i < NUM_SOCKETS; i++) {
if (listen(sockets[i], LISTEN_BACKLOG) < 0) {
fprintf(stderr, ERR_STR "Unable to listen on socket %i: %s" CLR_STR "\n", i, strerror(errno));
@ -307,14 +350,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 +396,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) {

View File

@ -1,86 +1,34 @@
/**
* 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 SERVER_TIMEOUT_INIT 4
#define SERVER_TIMEOUT 3600
#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
extern int sockets[NUM_SOCKETS];
extern pid_t children[MAX_CHILDREN];
extern MMDB_s mmdbs[MAX_MMDB];
#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"
#endif
int sockets[NUM_SOCKETS];
pid_t children[MAX_CHILDREN];
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

23
src/necronda.h Normal file
View File

@ -0,0 +1,23 @@
/**
* 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.5"
#define SERVER_STR "Necronda/" NECRONDA_VERSION
#define SERVER_STR_HTML "Necronda&nbsp;web&nbsp;server&nbsp;" NECRONDA_VERSION
#ifndef DEFAULT_HOST
# define DEFAULT_HOST "www.necronda.net"
#endif
#ifndef SERVER_NAME
# define SERVER_NAME DEFAULT_HOST
#endif
#endif //NECRONDA_SERVER_NECRONDA_H

View File

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

View File

@ -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

View File

@ -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;
}