Compare commits
194 Commits
Author | SHA1 | Date | |
---|---|---|---|
081af44eba | |||
782c7440b0
|
|||
88346fe722
|
|||
0f75aeea7a
|
|||
933aac0f09
|
|||
7f1299feb4
|
|||
8435048150
|
|||
206ae3264d
|
|||
5e050512ad
|
|||
db053121f2
|
|||
89a9d4b9d6
|
|||
bc7c3591a2
|
|||
1859c432c8
|
|||
33ec943e8b
|
|||
b30f9fa56d
|
|||
90e324cf87
|
|||
e7e1e7b18f
|
|||
63b1ca5d6a | |||
54313551fc | |||
92779e5dba | |||
100eb1597d | |||
f4c3345445
|
|||
893316ebfa
|
|||
6a511732af
|
|||
bb895c5bca
|
|||
a2f4bf57e0
|
|||
635271ec50
|
|||
2b4569aabe
|
|||
f361fce561
|
|||
c92742275a
|
|||
041e4d43a7
|
|||
170337d4d5
|
|||
f0b27b3b37
|
|||
ee8aedce91
|
|||
0648c75baa | |||
b6ba58d406
|
|||
abe0e326cb
|
|||
4fe067ed7d
|
|||
41e12d6293
|
|||
a738f1abfe
|
|||
b6309eec39
|
|||
9923a76ba7
|
|||
ea4cdff233
|
|||
6526b5cbcb
|
|||
0119945e03
|
|||
3fe1fe023a
|
|||
bc1c6d3498
|
|||
50bb537074
|
|||
c060ee5bb6
|
|||
4062883cb3
|
|||
e0e44e9c26
|
|||
f0d8a3db4c
|
|||
557e176d3d
|
|||
cd3bc9aa90
|
|||
6ab65abec9
|
|||
7d6fa4682d
|
|||
6f4cbb6e24
|
|||
174865b71c
|
|||
4a002d6c31
|
|||
2a3f74e825
|
|||
9eaa644fa1
|
|||
ce91fecc80
|
|||
15c160a60a
|
|||
fd5e2302d4
|
|||
8c13eecc51
|
|||
ae751925fa
|
|||
a1fee3d2ec
|
|||
8721f07d00
|
|||
81ad3fea3c
|
|||
877ee12351
|
|||
dfbe3bbb95
|
|||
6c13922f2f
|
|||
171bca55fa
|
|||
fe0cdc9b1a
|
|||
930d4db2a2
|
|||
b37757fc73
|
|||
61c645eca8
|
|||
2fd71116ec
|
|||
495a3a6aaf
|
|||
69dfc562af
|
|||
a2383825ed
|
|||
55f2318b57
|
|||
eeb0ce7035
|
|||
b5749ae991
|
|||
9adb4791df
|
|||
eba916fad4
|
|||
ac2d17f23a
|
|||
433cc7861f
|
|||
52ea670d36
|
|||
b4b5d77985
|
|||
aebc731c4e
|
|||
4994b4375b
|
|||
43c512dc5a
|
|||
3f5eee236d
|
|||
0b157bcb74
|
|||
30b163c6fa
|
|||
cf2c0de697
|
|||
7aa47cac61
|
|||
456deeae20
|
|||
b08481818c
|
|||
49ad349775
|
|||
2b823cabd6
|
|||
ecd4f16afe
|
|||
45c5f20345
|
|||
c42f27e961
|
|||
de8ab406f6
|
|||
22d50ed4bd
|
|||
de44f4a3fe
|
|||
cc29250d76
|
|||
c2f8f4c962
|
|||
ff708230bd
|
|||
52ea541833
|
|||
f4bd426f3c
|
|||
ffbbcc6490
|
|||
80986325ce
|
|||
10d405e745
|
|||
28f163f97a
|
|||
a8914aa981
|
|||
2ada22481d
|
|||
17b25a3596
|
|||
d130474989
|
|||
8dea0cd3fc
|
|||
0406cad0d8
|
|||
21b7ab585a
|
|||
7587e15749
|
|||
1b44752f91
|
|||
5eeb9ef3c1
|
|||
dd15b9d906
|
|||
12922a0661
|
|||
c0799101b1
|
|||
c1d076db04
|
|||
10464f3f30
|
|||
63781472fa
|
|||
81931d287d
|
|||
531ddb4880
|
|||
e0d8ab31d5
|
|||
3a36d54e9d
|
|||
33d9aa3a5d
|
|||
f4d30206b0
|
|||
5b094ba98d
|
|||
f60cdc8228
|
|||
ab1c4d6fd4
|
|||
26d54e9968
|
|||
b6c7d8f58e
|
|||
53fcceeafb
|
|||
96567909db
|
|||
4b3c067a75
|
|||
77b80ca67b
|
|||
70e76d8783
|
|||
6b1bc54cf3
|
|||
e1edb48a3c
|
|||
dc5d1bebcc
|
|||
7b562c4b78
|
|||
413ab2aa5b
|
|||
68315b9765
|
|||
820a232a96
|
|||
b676388018
|
|||
debac11f90
|
|||
9297788cdf
|
|||
e61d16fb41
|
|||
ebf3258092
|
|||
59d0c485fd
|
|||
c90df2397f
|
|||
c4eb6709cf
|
|||
a73cbac7a1
|
|||
95946b1666
|
|||
785ab31890
|
|||
5481d314c9
|
|||
5986f39802
|
|||
a972340209
|
|||
e6dd8b84f9
|
|||
c3942b3382
|
|||
fde2b7aabb
|
|||
d0be587a36
|
|||
351568004f
|
|||
be757add02
|
|||
037c150868
|
|||
576d0a0dba
|
|||
ee6f9115a8
|
|||
6bbf79245b
|
|||
080d729f31
|
|||
2f6ba62715
|
|||
4d4d94fc81
|
|||
55028bd9cd
|
|||
8b0eb45854
|
|||
df49236130
|
|||
c7ca62a7e9
|
|||
5d01f7e219
|
|||
ceb0167742
|
|||
8fa9f2528f
|
|||
2e16fdee96
|
|||
f9b7e83ac8
|
|||
9b9ddbb913
|
|||
566ac0ca93
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,7 +1,10 @@
|
||||
*
|
||||
!src
|
||||
!src/**
|
||||
!run.sh
|
||||
!doc
|
||||
!doc/**
|
||||
!test
|
||||
!test/**
|
||||
!Makefile
|
||||
!.gitignore
|
||||
!README.md
|
||||
|
96
Makefile
96
Makefile
@ -1,19 +1,85 @@
|
||||
.DEFAULT_GOAL := install
|
||||
|
||||
packages:
|
||||
@echo "Installing packages..."
|
||||
sudo apt-get install gcc libmagic-dev libssl-dev php-fpm
|
||||
@echo "Finished downloading!"
|
||||
CC=gcc
|
||||
CFLAGS=-std=gnu11 -Wno-unused-but-set-variable -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_SVID_SOURCE -D_POSIX_C_SOURCE=200809L
|
||||
LDFLAGS=-lssl -lcrypto -lmagic -lz -lmaxminddb -lbrotlienc
|
||||
|
||||
compile:
|
||||
@mkdir -p bin
|
||||
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz -lmaxminddb
|
||||
DEBIAN_OPTS=-D CACHE_MAGIC_FILE="\"/usr/share/file/magic.mgc\"" -D PHP_FPM_SOCKET="\"/var/run/php/php8.2-fpm.sock\""
|
||||
|
||||
compile-debian:
|
||||
@mkdir -p bin
|
||||
gcc src/necronda-server.c -o bin/necronda-server -std=c11 -lssl -lcrypto -lmagic -lz -lmaxminddb \
|
||||
-D MAGIC_FILE="\"/usr/share/file/magic.mgc\"" \
|
||||
-D PHP_FPM_SOCKET="\"/var/run/php/php7.3-fpm.sock\""
|
||||
.PHONY: all prod debug default debian permit clean test
|
||||
all: prod
|
||||
default: bin bin/lib bin/sesimos
|
||||
|
||||
install: | packages compile
|
||||
@echo "Finished!"
|
||||
prod: CFLAGS += -O3
|
||||
prod: default
|
||||
|
||||
debug: CFLAGS += -Wall -pedantic
|
||||
debug: default
|
||||
|
||||
debian: CFLAGS += $(DEBIAN_OPTS)
|
||||
debian: prod
|
||||
|
||||
test: CFLAGS += -include test/mock_*.h
|
||||
test: bin bin/test
|
||||
bin/test
|
||||
|
||||
|
||||
bin:
|
||||
mkdir -p bin
|
||||
|
||||
bin/lib:
|
||||
mkdir -p bin/lib
|
||||
|
||||
|
||||
bin/test: test/mock_*.c test/test_*.c src/lib/utils.c src/lib/sock.c
|
||||
$(CC) -o $@ $(CFLAGS) $^ -lcriterion
|
||||
|
||||
|
||||
bin/%.o: src/%.c
|
||||
$(CC) -c -o $@ $(CFLAGS) $<
|
||||
|
||||
bin/lib/%.o: src/lib/%.c
|
||||
$(CC) -c -o $@ $(CFLAGS) $<
|
||||
|
||||
bin/sesimos: bin/server.o bin/client.o \
|
||||
bin/lib/cache.o bin/lib/compress.o bin/lib/config.o bin/lib/fastcgi.o bin/lib/geoip.o \
|
||||
bin/lib/http.o bin/lib/http_static.o bin/lib/rev_proxy.o bin/lib/sock.o bin/lib/uri.o \
|
||||
bin/lib/utils.o bin/lib/websocket.o
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
|
||||
|
||||
|
||||
bin/server.o: src/server.h src/defs.h src/client.h src/lib/cache.h src/lib/config.h src/lib/sock.h \
|
||||
src/lib/rev_proxy.h src/lib/geoip.h src/lib/utils.h
|
||||
|
||||
bin/client.o: src/client.h src/defs.h src/server.h src/lib/utils.h src/lib/config.h src/lib/sock.h \
|
||||
src/lib/http.h src/lib/rev_proxy.h src/lib/fastcgi.h src/lib/cache.h src/lib/geoip.h src/lib/compress.h \
|
||||
src/lib/websocket.h
|
||||
|
||||
bin/lib/cache.o: src/lib/cache.h src/lib/utils.h src/lib/uri.h src/lib/compress.h
|
||||
|
||||
bin/lib/compress.o: src/lib/compress.h
|
||||
|
||||
bin/lib/config.o: src/lib/config.h src/lib/utils.h src/lib/uri.h
|
||||
|
||||
bin/lib/fastcgi.o: src/lib/fastcgi.h src/server.h src/lib/utils.h src/lib/compress.h src/lib/http.h \
|
||||
src/lib/uri.h src/lib/include/fastcgi.h
|
||||
|
||||
bin/lib/geoip.o: src/lib/geoip.h
|
||||
|
||||
bin/lib/http.o: src/lib/http.h src/lib/utils.h src/lib/compress.h src/lib/sock.h
|
||||
|
||||
bin/lib/rev_proxy.o: src/lib/rev_proxy.h src/defs.h src/server.h src/lib/compress.h
|
||||
|
||||
bin/lib/sock.o: src/lib/sock.h
|
||||
|
||||
bin/lib/uri.o: src/lib/uri.h src/lib/utils.h
|
||||
|
||||
bin/lib/utils.o: src/lib/utils.h
|
||||
|
||||
bin/lib/websocket.o: src/lib/websocket.h src/defs.h src/lib/utils.h src/lib/sock.h
|
||||
|
||||
|
||||
permit:
|
||||
sudo setcap 'cap_net_bind_service=+ep' "$(shell pwd)/bin/sesimos"
|
||||
|
||||
clean:
|
||||
rm -rf bin/*
|
||||
|
50
README.md
50
README.md
@ -1,2 +1,50 @@
|
||||
|
||||
# Necronda web server
|
||||
Sesimos – Secure, simple, modern 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/))
|
||||
* Disk cache for compressed files
|
||||
* Reverse proxy for other HTTP and HTTPS servers
|
||||
* Transparent WebSocket reverse proxy
|
||||
* FastCGI support (e.g. [PHP-FPM](https://php-fpm.org/))
|
||||
* Automatic path info detection (e.g. `/my/file/extra/path` -> script: `/my/file.php`, path info: `extra/path`)
|
||||
* 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 [doc/example.conf](doc/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
|
||||
|
24
doc/example.conf
Normal file
24
doc/example.conf
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
#geoip_dir /var/dir
|
||||
#dns_server 192.168.0.1
|
||||
|
||||
[cert cert1]
|
||||
certificate /var/cert/cert.pem
|
||||
private_key /var/cert/cert.key
|
||||
|
||||
[host localhost]
|
||||
webroot /var/www/localhost
|
||||
dir_mode forbidden
|
||||
cert cert1
|
||||
|
||||
[host me.local]
|
||||
hostname www.example.com
|
||||
port 80
|
||||
cert cert1
|
||||
http
|
||||
|
||||
[host secure.local]
|
||||
hostname www.example.com
|
||||
port 443
|
||||
cert cert1
|
||||
https
|
6
run.sh
6
run.sh
@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo "-- Building and starting Necronda Server..."
|
||||
make compile && \
|
||||
echo "-- Successfully finished compiling!" && \
|
||||
echo "-- Starting Server..." && \
|
||||
./bin/necronda-server $@
|
327
src/cache.c
327
src/cache.c
@ -1,327 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* File cache implementation
|
||||
* src/cache.c
|
||||
* Lorenz Stechauner, 2020-12-19
|
||||
*/
|
||||
|
||||
#include <zlib.h>
|
||||
#include "cache.h"
|
||||
#include "uri.h"
|
||||
|
||||
int magic_init() {
|
||||
magic = magic_open(MAGIC_MIME);
|
||||
if (magic == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (magic_load(magic, MAGIC_FILE) != 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to load magic cookie: %s" CLR_STR "\n", magic_error(magic));
|
||||
return -2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cache_process_term() {
|
||||
cache_continue = 0;
|
||||
}
|
||||
|
||||
int cache_process() {
|
||||
signal(SIGINT, cache_process_term);
|
||||
signal(SIGTERM, cache_process_term);
|
||||
|
||||
int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
shmdt(cache);
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
FILE *cache_file = fopen("/var/necronda-server/cache", "rb");
|
||||
if (cache_file != NULL) {
|
||||
fread(cache, sizeof(cache_entry), FILE_CACHE_SIZE, cache_file);
|
||||
fclose(cache_file);
|
||||
}
|
||||
|
||||
for (int i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
cache[i].is_updating = 0;
|
||||
}
|
||||
|
||||
FILE *file;
|
||||
char buf[16384];
|
||||
char comp_buf[16384];
|
||||
char filename_comp[256];
|
||||
unsigned long read;
|
||||
int compress;
|
||||
SHA_CTX ctx;
|
||||
unsigned char hash[SHA_DIGEST_LENGTH];
|
||||
while (cache_continue) {
|
||||
for (int i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
if (cache[i].filename[0] != 0 && cache[i].meta.etag[0] == 0 && !cache[i].is_updating) {
|
||||
cache[i].is_updating = 1;
|
||||
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);
|
||||
|
||||
int level = NECRONDA_ZLIB_LEVEL;
|
||||
z_stream strm;
|
||||
FILE *comp_file = NULL;
|
||||
if (compress) {
|
||||
sprintf(buf, "%.*s/.necronda-server", cache[i].webroot_len, cache[i].filename);
|
||||
mkdir(buf, 0755);
|
||||
sprintf(buf, "%.*s/.necronda-server/cache", cache[i].webroot_len, cache[i].filename);
|
||||
mkdir(buf, 0700);
|
||||
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 = '_';
|
||||
}
|
||||
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) {
|
||||
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));
|
||||
compress = 0;
|
||||
fclose(comp_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((read = fread(buf, 1, sizeof(buf), file)) > 0) {
|
||||
SHA1_Update(&ctx, buf, read);
|
||||
if (compress) {
|
||||
strm.avail_in = read;
|
||||
strm.next_in = (unsigned char *) buf;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (compress) {
|
||||
deflateEnd(&strm);
|
||||
fclose(comp_file);
|
||||
strcpy(cache[i].meta.filename_comp, filename_comp);
|
||||
} else {
|
||||
memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
|
||||
}
|
||||
SHA1_Final(hash, &ctx);
|
||||
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
|
||||
for (int j = 0; j < SHA_DIGEST_LENGTH; j++) {
|
||||
sprintf(cache[i].meta.etag + j * 2, "%02x", hash[j]);
|
||||
}
|
||||
fclose(file);
|
||||
cache[i].is_updating = 0;
|
||||
}
|
||||
}
|
||||
|
||||
cache_file = fopen("/var/necronda-server/cache", "wb");
|
||||
fwrite(cache, sizeof(cache_entry), FILE_CACHE_SIZE , cache_file);
|
||||
fclose(cache_file);
|
||||
sleep(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_init() {
|
||||
if (magic_init() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int shm_id = shmget(SHM_KEY, FILE_CACHE_SIZE * sizeof(cache_entry), IPC_CREAT | IPC_EXCL | 0600);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
|
||||
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
|
||||
if (shm == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
|
||||
return -3;
|
||||
}
|
||||
cache = shm;
|
||||
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -4;
|
||||
}
|
||||
cache = shm_rw;
|
||||
memset(cache, 0, FILE_CACHE_SIZE * sizeof(cache_entry));
|
||||
shmdt(shm_rw);
|
||||
cache = shm;
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
// child
|
||||
if (cache_process() == 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return -6;
|
||||
}
|
||||
} else if (pid > 0) {
|
||||
// parent
|
||||
fprintf(stderr, "Started child process with PID %i as cache-updater\n", pid);
|
||||
children[0] = 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, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
}
|
||||
shmdt(cache);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_update_entry(int entry_num, const char *filename, const char *webroot) {
|
||||
void *cache_ro = cache;
|
||||
int shm_id = shmget(SHM_KEY, 0, 0);
|
||||
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));
|
||||
return -1;
|
||||
}
|
||||
cache = shm_rw;
|
||||
|
||||
struct stat statbuf;
|
||||
stat(filename, &statbuf);
|
||||
memcpy(&cache[entry_num].meta.stat, &statbuf, sizeof(statbuf));
|
||||
|
||||
cache[entry_num].webroot_len = (unsigned char) strlen(webroot);
|
||||
strcpy(cache[entry_num].filename, filename);
|
||||
|
||||
magic_setflags(magic, MAGIC_MIME_TYPE);
|
||||
const char *type = magic_file(magic, filename);
|
||||
char type_new[24];
|
||||
sprintf(type_new, "%s", type);
|
||||
if (strcmp(type, "text/plain") == 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");
|
||||
}
|
||||
}
|
||||
strcpy(cache[entry_num].meta.type, type_new);
|
||||
|
||||
magic_setflags(magic, MAGIC_MIME_ENCODING);
|
||||
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));
|
||||
cache[entry_num].is_updating = 0;
|
||||
|
||||
shmdt(shm_rw);
|
||||
cache = cache_ro;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_filename_comp_invalid(const char *filename) {
|
||||
void *cache_ro = cache;
|
||||
int shm_id = shmget(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));
|
||||
return -1;
|
||||
}
|
||||
cache = shm_rw;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(filename) &&
|
||||
strcmp(cache[i].filename, filename) == 0) {
|
||||
if (cache[i].is_updating) {
|
||||
return 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
|
||||
memset(cache[i].meta.filename_comp, 0, sizeof(cache[i].meta.filename_comp));
|
||||
cache[i].is_updating = 0;
|
||||
|
||||
shmdt(shm_rw);
|
||||
cache = cache_ro;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uri_cache_init(http_uri *uri) {
|
||||
if (uri->filename == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(uri->filename) &&
|
||||
strcmp(cache[i].filename, uri->filename) == 0) {
|
||||
uri->meta = &cache[i].meta;
|
||||
if (cache[i].is_updating) {
|
||||
return 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uri->meta == NULL) {
|
||||
for (i = 0; i < FILE_CACHE_SIZE; i++) {
|
||||
if (cache[i].filename[0] == 0) {
|
||||
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
|
||||
return -1;
|
||||
}
|
||||
uri->meta = &cache[i].meta;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
struct stat statbuf;
|
||||
stat(uri->filename, &statbuf);
|
||||
if (memcmp(&uri->meta->stat.st_mtime, &statbuf.st_mtime, sizeof(statbuf.st_mtime)) != 0) {
|
||||
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
45
src/cache.h
45
src/cache.h
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* File cache implementation (header file)
|
||||
* src/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;
|
||||
|
||||
typedef struct {
|
||||
char filename[256];
|
||||
unsigned char webroot_len;
|
||||
unsigned char is_updating:1;
|
||||
meta_data meta;
|
||||
} cache_entry;
|
||||
|
||||
cache_entry *cache;
|
||||
|
||||
int cache_continue = 1;
|
||||
|
||||
int magic_init();
|
||||
|
||||
void cache_process_term();
|
||||
|
||||
int cache_process();
|
||||
|
||||
int cache_init();
|
||||
|
||||
int cache_unload();
|
||||
|
||||
int cache_update_entry(int entry_num, const char *filename, const char *webroot);
|
||||
|
||||
int cache_filename_comp_invalid(const char *filename);
|
||||
|
||||
int uri_cache_init(http_uri *uri);
|
||||
|
||||
#endif //NECRONDA_SERVER_CACHE_H
|
909
src/client.c
909
src/client.c
File diff suppressed because it is too large
Load Diff
21
src/client.h
Normal file
21
src/client.h
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Client connection and request handlers (header file)
|
||||
* @file src/client.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2022-08-16
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_CLIENT_H
|
||||
#define SESIMOS_CLIENT_H
|
||||
|
||||
#include "lib/config.h"
|
||||
#include "lib/sock.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
host_config *get_host_config(const char *host);
|
||||
|
||||
int client_handler(sock *client, unsigned long client_num, struct sockaddr_in6 *client_addr);
|
||||
|
||||
#endif //SESIMOS_CLIENT_H
|
26
src/defs.h
Normal file
26
src/defs.h
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Definitions
|
||||
* @file src/defs.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-05-04
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_DEF_H
|
||||
#define SESIMOS_DEF_H
|
||||
|
||||
#define SERVER_VERSION "4.6"
|
||||
#define SERVER_STR "Sesimos/" SERVER_VERSION
|
||||
#define SERVER_STR_HTML "Sesimos web server " SERVER_VERSION
|
||||
|
||||
#define CHUNK_SIZE 8192
|
||||
|
||||
#ifndef DEFAULT_HOST
|
||||
# define DEFAULT_HOST "www.necronda.net"
|
||||
#endif
|
||||
|
||||
#ifndef SERVER_NAME
|
||||
# define SERVER_NAME DEFAULT_HOST
|
||||
#endif
|
||||
|
||||
#endif //SESIMOS_DEF_H
|
291
src/http.c
291
src/http.c
@ -1,291 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* HTTP implementation
|
||||
* src/net/http.c
|
||||
* Lorenz Stechauner, 2020-12-09
|
||||
*/
|
||||
|
||||
#include "http.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
void http_to_camel_case(char *str, int mode) {
|
||||
char last = '-';
|
||||
char ch;
|
||||
for (int i = 0; i < strlen(str); i++) {
|
||||
ch = str[i];
|
||||
if (mode == HTTP_CAMEL && last == '-' && ch >= 'a' && ch <= 'z') {
|
||||
str[i] = (char) ((int) ch & 0x5F);
|
||||
} else if (mode == HTTP_LOWER && ch >= 'A' && ch <= 'Z') {
|
||||
str[i] = (char) ((int) ch | 0x20);
|
||||
}
|
||||
last = str[i];
|
||||
}
|
||||
}
|
||||
|
||||
void http_free_hdr(http_hdr *hdr) {
|
||||
for (int i = 0; i < hdr->field_num; i++) {
|
||||
free(hdr->fields[i][0]);
|
||||
free(hdr->fields[i][1]);
|
||||
}
|
||||
hdr->field_num = 0;
|
||||
}
|
||||
|
||||
void http_free_req(http_req *req) {
|
||||
if (req->uri == NULL) free(req->uri);
|
||||
req->uri = NULL;
|
||||
http_free_hdr(&req->hdr);
|
||||
}
|
||||
|
||||
void http_free_res(http_res *res) {
|
||||
http_free_hdr(&res->hdr);
|
||||
}
|
||||
|
||||
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) {
|
||||
char *pos1 = memchr(buf, ':', end_ptr - buf);
|
||||
char *pos2;
|
||||
if (pos1 == NULL) {
|
||||
print(ERR_STR "Unable to parse header" CLR_STR);
|
||||
return 3;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
pos1++;
|
||||
pos2 = (char *) end_ptr - 1;
|
||||
while (pos1[0] == ' ') pos1++;
|
||||
while (pos2[0] == ' ') pos2--;
|
||||
len = pos2 - pos1 + 1;
|
||||
|
||||
if (len <= 0) {
|
||||
hdr->fields[hdr->field_num][1] = malloc(1);
|
||||
hdr->fields[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->field_num++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_receive_request(sock *client, http_req *req) {
|
||||
unsigned long rcv_len, len;
|
||||
char *ptr, *pos0, *pos1, *pos2;
|
||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
memset(req->method, 0, sizeof(req->method));
|
||||
memset(req->version, 0, sizeof(req->version));
|
||||
req->uri = NULL;
|
||||
req->hdr.field_num = 0;
|
||||
|
||||
while (1) {
|
||||
if (client->enc) {
|
||||
rcv_len = SSL_read(client->ssl, buf, CLIENT_MAX_HEADER_SIZE);
|
||||
if (rcv_len < 0) {
|
||||
print(ERR_STR "Unable to receive: %s" CLR_STR, ssl_get_error(client->ssl, rcv_len));
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
rcv_len = recv(client->socket, buf, CLIENT_MAX_HEADER_SIZE, 0);
|
||||
if (rcv_len < 0) {
|
||||
print(ERR_STR "Unable to receive: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (rcv_len == 0) {
|
||||
print("Unable to receive: closed");
|
||||
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);
|
||||
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);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
ptr = buf;
|
||||
while (header_len > (ptr - buf + 2)) {
|
||||
pos0 = strstr(ptr, "\r\n");
|
||||
if (pos0 == NULL) {
|
||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (req->version[0] == 0) {
|
||||
pos1 = memchr(ptr, ' ', rcv_len - (ptr - buf)) + 1;
|
||||
if (pos1 == NULL) goto err_hdr_fmt;
|
||||
|
||||
if (pos1 - ptr - 1 >= sizeof(req->method)) {
|
||||
print(ERR_STR "Unable to parse header: Method name too long" CLR_STR);
|
||||
return 2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (pos1 - ptr - 1); i++) {
|
||||
if (ptr[i] < 'A' || ptr[i] > 'Z') {
|
||||
print(ERR_STR "Unable to parse header: Invalid method" CLR_STR);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
strncpy(req->method, ptr, pos1 - ptr - 1);
|
||||
|
||||
pos2 = memchr(pos1, ' ', rcv_len - (pos1 - buf)) + 1;
|
||||
if (pos2 == NULL) {
|
||||
err_hdr_fmt:
|
||||
print(ERR_STR "Unable to parse 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);
|
||||
return 3;
|
||||
}
|
||||
|
||||
len = pos2 - pos1 - 1;
|
||||
req->uri = malloc(len + 1);
|
||||
sprintf(req->uri, "%.*s", (int) len, pos1);
|
||||
sprintf(req->version, "%.3s", pos2 + 5);
|
||||
} else {
|
||||
int ret = http_parse_header_field(&req->hdr, ptr, pos0);
|
||||
if (ret != 0) return ret;
|
||||
}
|
||||
ptr = pos0 + 2;
|
||||
}
|
||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
client->buf_len = rcv_len - (pos0 - buf + 4);
|
||||
if (client->buf_len > 0) {
|
||||
client->buf = malloc(client->buf_len);
|
||||
client->buf_off = 0;
|
||||
memcpy(client->buf, pos0 + 4, client->buf_len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
|
||||
char field_name_1[256], field_name_2[256];
|
||||
strcpy(field_name_1, field_name);
|
||||
http_to_camel_case(field_name_1, HTTP_LOWER);
|
||||
for (int i = 0; i < hdr->field_num; i++) {
|
||||
strcpy(field_name_2, hdr->fields[i][0]);
|
||||
http_to_camel_case(field_name_2, HTTP_LOWER);
|
||||
if (strcmp(field_name_1, field_name_2) == 0) {
|
||||
return hdr->fields[i][1];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
|
||||
size_t len_name = strlen(field_name);
|
||||
size_t len_value = strlen(field_value);
|
||||
char *_field_name = malloc(len_name + 1);
|
||||
char *_field_value = malloc(len_value + 1);
|
||||
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->field_num++;
|
||||
}
|
||||
|
||||
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
|
||||
char field_name_1[256], field_name_2[256];
|
||||
strcpy(field_name_1, field_name);
|
||||
http_to_camel_case(field_name_1, HTTP_LOWER);
|
||||
for (int i = 0; i < hdr->field_num; i++) {
|
||||
strcpy(field_name_2, hdr->fields[i][0]);
|
||||
http_to_camel_case(field_name_2, HTTP_LOWER);
|
||||
if (strcmp(field_name_1, field_name_2) == 0) {
|
||||
for (int j = i; j < hdr->field_num - 1; j++) {
|
||||
memcpy(hdr->fields[j], hdr->fields[j + 1], sizeof(hdr->fields[0]));
|
||||
}
|
||||
hdr->field_num--;
|
||||
if (mode == HTTP_REMOVE_ONE) {
|
||||
return;
|
||||
} else if (mode == HTTP_REMOVE_ALL) {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int http_send_response(sock *client, http_res *res) {
|
||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||
int len = 0;
|
||||
int snd_len = 0;
|
||||
|
||||
len += sprintf(buf + len, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
|
||||
for (int i = 0; i < res->hdr.field_num; i++) {
|
||||
len += sprintf(buf + len, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]);
|
||||
}
|
||||
len += sprintf(buf + len, "\r\n");
|
||||
|
||||
if (client->enc) {
|
||||
snd_len = SSL_write(client->ssl, buf, len);
|
||||
} else {
|
||||
snd_len = send(client->socket, buf, len, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
http_status *http_get_status(unsigned short status_code) {
|
||||
for (int i = 0; i < sizeof(http_statuses) / sizeof(http_status); i++) {
|
||||
if (http_statuses[i].code == status_code) {
|
||||
return &http_statuses[i];
|
||||
}
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *http_get_status_color(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) {
|
||||
return HTTP_2XX_STR;
|
||||
} else if (code >= 300 && code < 400) {
|
||||
return HTTP_3XX_STR;
|
||||
} else if (code >= 400 && code < 500) {
|
||||
return HTTP_4XX_STR;
|
||||
} else if (code >= 500 && code < 600) {
|
||||
return HTTP_5XX_STR;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
char *http_format_date(time_t time, char *buf, size_t size) {
|
||||
struct tm *timeinfo = gmtime(&time);
|
||||
strftime(buf, size, "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
|
||||
return buf;
|
||||
}
|
||||
|
||||
char *http_get_date(char *buf, size_t size) {
|
||||
time_t rawtime;
|
||||
time(&rawtime);
|
||||
return http_format_date(rawtime, buf, size);
|
||||
}
|
192
src/http.h
192
src/http.h
@ -1,192 +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
|
||||
|
||||
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"
|
||||
" <title>%1$i %2$s</title>\n"
|
||||
" <meta charset=\"UTF-8\"/>\n"
|
||||
" <meta name=\"theme-color\" content=\"%6$s\"/>\n"
|
||||
" <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"/>\n"
|
||||
" <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n"
|
||||
"%5$s"
|
||||
" <style>\n"
|
||||
" html{font-family:\"Arial\",sans-serif;--error:#C00000;--info:#E0C000;--color:var(--%4$s);}\n"
|
||||
" body{background-color:#F0F0F0;margin:0.5em;}\n"
|
||||
" main{max-width:600px;margin:2em auto;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em 2em;}\n"
|
||||
" h1,h2,h3,h4,h5,h6,h7{text-align:center;color:var(--color);}\n"
|
||||
" h1{margin:0.5em 0;font-size:1.5em;}\n"
|
||||
" p{text-align:center;}\n"
|
||||
" div.footer{color:#808080;font-size:0.75em;text-align:center;margin:0.5em 0;}\n"
|
||||
" </style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
" <main>\n"
|
||||
"%3$s"
|
||||
" <div class=\"footer\">Necronda web server " NECRONDA_VERSION "</div>\n"
|
||||
" </main>\n"
|
||||
"</body>\n"
|
||||
"</html>\n";
|
||||
|
||||
const char *http_error_document =
|
||||
" <h1>%1$i %2$s :(</h1>\n"
|
||||
" <p>%3$s</p>\n"
|
||||
" <p>%4$s</p>\n";
|
||||
|
||||
const char *http_error_icon =
|
||||
" <link rel=\"shortcut icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
||||
"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);
|
||||
|
||||
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
|
394
src/lib/cache.c
Normal file
394
src/lib/cache.c
Normal file
@ -0,0 +1,394 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief File cache implementation
|
||||
* @file src/lib/cache.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-19
|
||||
*/
|
||||
|
||||
#include "cache.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/evp.h>
|
||||
|
||||
|
||||
int cache_continue = 1;
|
||||
magic_t magic;
|
||||
cache_entry *cache;
|
||||
|
||||
int magic_init(void) {
|
||||
magic = magic_open(MAGIC_MIME);
|
||||
if (magic == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cache_process_term(int _) {
|
||||
cache_continue = 0;
|
||||
}
|
||||
|
||||
int cache_process(void) {
|
||||
signal(SIGINT, cache_process_term);
|
||||
signal(SIGTERM, cache_process_term);
|
||||
|
||||
int shm_id = shmget(CACHE_SHM_KEY, CACHE_ENTRIES * sizeof(cache_entry), 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to create cache shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
shmdt(cache);
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach cache shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
cache = shm_rw;
|
||||
|
||||
if (mkdir("/var/sesimos/", 0755) < 0 && errno != EEXIST) {
|
||||
fprintf(stderr, ERR_STR "Unable to create directory '/var/sesimos/': %s" CLR_STR "\n", strerror(errno));
|
||||
return -3;
|
||||
}
|
||||
|
||||
if (mkdir("/var/sesimos/server/", 0755) < 0 && errno != EEXIST) {
|
||||
fprintf(stderr, ERR_STR "Unable to create directory '/var/sesimos/server/': %s" CLR_STR "\n", strerror(errno));
|
||||
return -3;
|
||||
}
|
||||
|
||||
FILE *cache_file = fopen("/var/sesimos/server/cache", "rb");
|
||||
if (cache_file != NULL) {
|
||||
fread(cache, sizeof(cache_entry), CACHE_ENTRIES, cache_file);
|
||||
fclose(cache_file);
|
||||
}
|
||||
|
||||
for (int i = 0; i < CACHE_ENTRIES; i++) {
|
||||
cache[i].is_updating = 0;
|
||||
}
|
||||
|
||||
FILE *file;
|
||||
char buf[CACHE_BUF_SIZE], comp_buf[CACHE_BUF_SIZE], filename_comp_gz[256], filename_comp_br[256];
|
||||
unsigned long read;
|
||||
int compress;
|
||||
EVP_MD_CTX *ctx;
|
||||
unsigned char hash[EVP_MAX_MD_SIZE];
|
||||
unsigned int md_len;
|
||||
int cache_changed = 0;
|
||||
int p_len_gz, p_len_br;
|
||||
int ret;
|
||||
while (cache_continue) {
|
||||
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);
|
||||
|
||||
ctx = EVP_MD_CTX_new();
|
||||
EVP_DigestInit(ctx, EVP_sha1());
|
||||
file = fopen(cache[i].filename, "rb");
|
||||
compress = mime_is_compressible(cache[i].meta.type);
|
||||
|
||||
compress_ctx comp_ctx;
|
||||
FILE *comp_file_gz = NULL;
|
||||
FILE *comp_file_br = NULL;
|
||||
if (compress) {
|
||||
sprintf(buf, "%.*s/.sesimos", cache[i].webroot_len, cache[i].filename);
|
||||
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/.sesimos/cache", cache[i].webroot_len, cache[i].filename);
|
||||
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 = '_';
|
||||
buf[j] = ch;
|
||||
}
|
||||
buf[strlen(rel_path)] = 0;
|
||||
|
||||
p_len_gz = snprintf(filename_comp_gz, sizeof(filename_comp_gz),
|
||||
"%.*s/.sesimos/cache/%s.gz",
|
||||
cache[i].webroot_len, cache[i].filename, buf);
|
||||
p_len_br = snprintf(filename_comp_br, sizeof(filename_comp_br),
|
||||
"%.*s/.sesimos/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;
|
||||
} else {
|
||||
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_gz);
|
||||
fclose(comp_file_br);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((read = fread(buf, 1, CACHE_BUF_SIZE, file)) > 0) {
|
||||
EVP_DigestUpdate(ctx, buf, read);
|
||||
if (compress) {
|
||||
unsigned long avail_in, avail_out;
|
||||
avail_in = read;
|
||||
do {
|
||||
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) {
|
||||
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_gz, 0, sizeof(cache[i].meta.filename_comp_gz));
|
||||
memset(cache[i].meta.filename_comp_br, 0, sizeof(cache[i].meta.filename_comp_br));
|
||||
}
|
||||
|
||||
EVP_DigestFinal(ctx, hash, &md_len);
|
||||
EVP_MD_CTX_free(ctx);
|
||||
|
||||
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
|
||||
for (int j = 0; j < md_len; j++) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (cache_changed) {
|
||||
cache_changed = 0;
|
||||
cache_file = fopen("/var/sesimos/server/cache", "wb");
|
||||
if (cache_file == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open cache file: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
fwrite(cache, sizeof(cache_entry), CACHE_ENTRIES, cache_file);
|
||||
fclose(cache_file);
|
||||
} else {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_init(void) {
|
||||
if (magic_init() != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
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 cache shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
|
||||
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
|
||||
if (shm == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach cache shared memory (ro): %s" CLR_STR "\n", strerror(errno));
|
||||
return -3;
|
||||
}
|
||||
cache = shm;
|
||||
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach cache shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -4;
|
||||
}
|
||||
cache = shm_rw;
|
||||
memset(cache, 0, CACHE_ENTRIES * sizeof(cache_entry));
|
||||
shmdt(shm_rw);
|
||||
cache = shm;
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
// child
|
||||
if (cache_process() == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return -6;
|
||||
}
|
||||
} else if (pid > 0) {
|
||||
// parent
|
||||
fprintf(stderr, "Started child process with PID %i as cache-updater\n", pid);
|
||||
return pid;
|
||||
} else {
|
||||
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
|
||||
return -5;
|
||||
}
|
||||
}
|
||||
|
||||
int cache_unload(void) {
|
||||
int shm_id = shmget(CACHE_SHM_KEY, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to get cache shared memory id: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(cache);
|
||||
return -1;
|
||||
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to configure cache shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(cache);
|
||||
return -1;
|
||||
}
|
||||
shmdt(cache);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_update_entry(int entry_num, const char *filename, const char *webroot) {
|
||||
void *cache_ro = cache;
|
||||
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 cache shared memory (rw): %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
cache = shm_rw;
|
||||
|
||||
struct stat statbuf;
|
||||
stat(filename, &statbuf);
|
||||
memcpy(&cache[entry_num].meta.stat, &statbuf, sizeof(statbuf));
|
||||
|
||||
cache[entry_num].webroot_len = (unsigned char) strlen(webroot);
|
||||
strcpy(cache[entry_num].filename, filename);
|
||||
|
||||
magic_setflags(magic, MAGIC_MIME_TYPE);
|
||||
const char *type = magic_file(magic, filename);
|
||||
char type_new[24];
|
||||
sprintf(type_new, "%s", type);
|
||||
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, "application/javascript");
|
||||
}
|
||||
}
|
||||
strcpy(cache[entry_num].meta.type, type_new);
|
||||
|
||||
magic_setflags(magic, MAGIC_MIME_ENCODING);
|
||||
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_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);
|
||||
cache = cache_ro;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cache_filename_comp_invalid(const char *filename) {
|
||||
void *cache_ro = cache;
|
||||
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 cache shared memory (rw): %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
cache = shm_rw;
|
||||
|
||||
int 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) {
|
||||
if (cache[i].is_updating) {
|
||||
return 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
|
||||
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);
|
||||
cache = cache_ro;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uri_cache_init(http_uri *uri) {
|
||||
if (uri->filename == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int 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) {
|
||||
uri->meta = &cache[i].meta;
|
||||
if (cache[i].is_updating) {
|
||||
return 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uri->meta == NULL) {
|
||||
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;
|
||||
}
|
||||
uri->meta = &cache[i].meta;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
struct stat statbuf;
|
||||
stat(uri->filename, &statbuf);
|
||||
if (memcmp(&uri->meta->stat.st_mtime, &statbuf.st_mtime, sizeof(statbuf.st_mtime)) != 0) {
|
||||
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
50
src/lib/cache.h
Normal file
50
src/lib/cache.h
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief File cache implementation (header file)
|
||||
* @file src/lib/cache.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-19
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_CACHE_H
|
||||
#define SESIMOS_CACHE_H
|
||||
|
||||
#include "uri.h"
|
||||
|
||||
#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];
|
||||
unsigned char webroot_len;
|
||||
unsigned char is_updating:1;
|
||||
meta_data meta;
|
||||
} cache_entry;
|
||||
|
||||
extern cache_entry *cache;
|
||||
|
||||
extern int cache_continue;
|
||||
|
||||
int magic_init(void);
|
||||
|
||||
void cache_process_term(int _);
|
||||
|
||||
int cache_process(void);
|
||||
|
||||
int cache_init(void);
|
||||
|
||||
int cache_unload(void);
|
||||
|
||||
int cache_update_entry(int entry_num, const char *filename, const char *webroot);
|
||||
|
||||
int cache_filename_comp_invalid(const char *filename);
|
||||
|
||||
int uri_cache_init(http_uri *uri);
|
||||
|
||||
#endif //SESIMOS_CACHE_H
|
82
src/lib/compress.c
Normal file
82
src/lib/compress.c
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Compression interface
|
||||
* @file src/lib/compress.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 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;
|
||||
}
|
38
src/lib/compress.h
Normal file
38
src/lib/compress.h
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Compression interface (header file)
|
||||
* @file src/lib/compress.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-05-05
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_COMPRESS_H
|
||||
#define SESIMOS_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 //SESIMOS_COMPRESS_H
|
277
src/lib/config.c
Normal file
277
src/lib/config.c
Normal file
@ -0,0 +1,277 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Configuration file loader
|
||||
* @file src/lib/config.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-01-05
|
||||
*/
|
||||
|
||||
#include "config.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(void) {
|
||||
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 config shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
|
||||
if (shm == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach config shared memory (ro): %s" CLR_STR "\n", strerror(errno));
|
||||
return -2;
|
||||
}
|
||||
config = shm;
|
||||
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
fprintf(stderr, ERR_STR "Unable to attach config shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -3;
|
||||
}
|
||||
config = shm_rw;
|
||||
memset(config, 0, sizeof(t_config));
|
||||
shmdt(shm_rw);
|
||||
config = shm;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_unload(void) {
|
||||
int shm_id = shmget(CONFIG_SHM_KEY, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to get config shared memory id: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(config);
|
||||
return -1;
|
||||
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to configure config shared memory: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(config);
|
||||
return -1;
|
||||
}
|
||||
shmdt(config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int config_load(const char *filename) {
|
||||
FILE *file = fopen(filename, "r");
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open config file: %s" CLR_STR "\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
unsigned long len = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
char *conf = alloca(len + 1);
|
||||
fread(conf, 1, len, file);
|
||||
conf[len] = 0;
|
||||
fclose(file);
|
||||
|
||||
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 *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;
|
||||
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 (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 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;
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
|
||||
goto err;
|
||||
} 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;
|
||||
mode = 1;
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_LOCAL;
|
||||
}
|
||||
} else if (len > 9 && strncmp(ptr, "hostname", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) {
|
||||
source = ptr + 8;
|
||||
target = hc->rev_proxy.hostname;
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||
}
|
||||
} else if (len > 5 && strncmp(ptr, "port", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
|
||||
source = ptr + 4;
|
||||
target = NULL;
|
||||
mode = 2;
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||
}
|
||||
} else if (strcmp(ptr, "http") == 0) {
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||
hc->rev_proxy.enc = 0;
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(ptr, "https") == 0) {
|
||||
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||
goto err;
|
||||
} else {
|
||||
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||
hc->rev_proxy.enc = 1;
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
goto err;
|
||||
}
|
||||
} else {
|
||||
goto err;
|
||||
}
|
||||
|
||||
while (source[0] == ' ' || source[0] == '\t') source++;
|
||||
if (strlen(source) == 0) {
|
||||
err:
|
||||
free(tmp_config);
|
||||
fprintf(stderr, ERR_STR "Unable to parse config file (line %i)" CLR_STR "\n", line);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (target != NULL) {
|
||||
strcpy(target, source);
|
||||
} else if (mode == 1) {
|
||||
if (strcmp(source, "forbidden") == 0) {
|
||||
tmp_config->hosts[i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN;
|
||||
} else if (strcmp(source, "info") == 0) {
|
||||
tmp_config->hosts[i - 1].local.dir_mode = URI_DIR_MODE_INFO;
|
||||
} else if (strcmp(source, "list") == 0) {
|
||||
tmp_config->hosts[i - 1].local.dir_mode = URI_DIR_MODE_LIST;
|
||||
} else {
|
||||
goto err;
|
||||
}
|
||||
} else if (mode == 2) {
|
||||
tmp_config->hosts[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10);
|
||||
}
|
||||
}
|
||||
|
||||
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(CONFIG_SHM_KEY, 0, 0);
|
||||
if (shm_id < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to get config shared memory id: %s" CLR_STR "\n", strerror(errno));
|
||||
shmdt(config);
|
||||
return -3;
|
||||
}
|
||||
|
||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
||||
if (shm_rw == (void *) -1) {
|
||||
free(tmp_config);
|
||||
fprintf(stderr, ERR_STR "Unable to attach config shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
||||
return -4;
|
||||
}
|
||||
memcpy(shm_rw, tmp_config, sizeof(t_config));
|
||||
free(tmp_config);
|
||||
shmdt(shm_rw);
|
||||
return 0;
|
||||
}
|
65
src/lib/config.h
Normal file
65
src/lib/config.h
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Configuration file loader (header file)
|
||||
* @file src/lib/config.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-01-05
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_CONFIG_H
|
||||
#define SESIMOS_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/sesimos/server.conf"
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
char name[256];
|
||||
char cert_name[256];
|
||||
int cert;
|
||||
union {
|
||||
struct {
|
||||
char hostname[256];
|
||||
unsigned short port;
|
||||
unsigned char enc:1;
|
||||
} rev_proxy;
|
||||
struct {
|
||||
char webroot[256];
|
||||
unsigned char dir_mode:2;
|
||||
} local;
|
||||
};
|
||||
} host_config;
|
||||
|
||||
typedef struct {
|
||||
char name[256];
|
||||
char full_chain[256];
|
||||
char priv_key[256];
|
||||
} cert_config;
|
||||
|
||||
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(void);
|
||||
|
||||
int config_load(const char *filename);
|
||||
|
||||
int config_unload(void);
|
||||
|
||||
#endif //SESIMOS_CONFIG_H
|
@ -1,14 +1,20 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* FastCGI interface implementation
|
||||
* src/fastcgi.c
|
||||
* Lorenz Stechauner, 2020-12-26
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief FastCGI interface implementation
|
||||
* @file src/lib/fastcgi.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-26
|
||||
*/
|
||||
|
||||
#include "fastcgi.h"
|
||||
#include "necronda-server.h"
|
||||
#include "utils.h"
|
||||
#include "compress.h"
|
||||
#include "../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) {
|
||||
@ -38,15 +44,15 @@ char *fastcgi_add_param(char *buf, const char *key, const char *value) {
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
memcpy(ptr, key, key_len);
|
||||
strcpy(ptr, key);
|
||||
ptr += key_len;
|
||||
memcpy(ptr, value, val_len);
|
||||
strcpy(ptr, value);
|
||||
ptr += val_len;
|
||||
|
||||
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 +60,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_SESIMOS) {
|
||||
snprintf(sock_addr.sun_path, sizeof(sock_addr.sun_path) - 1, "%s", SESIMOS_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 +101,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,24 +147,26 @@ 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);
|
||||
|
||||
//param_ptr = fastcgi_add_param(param_ptr, "AUTH_TYPE", "");
|
||||
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
|
||||
const char *content_length = http_get_header_field(&req->hdr, "Content-Length");
|
||||
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_LENGTH", content_length != NULL ? content_length : "");
|
||||
char *content_type = http_get_header_field(&req->hdr, "Content-Type");
|
||||
const char *content_type = http_get_header_field(&req->hdr, "Content-Type");
|
||||
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_TYPE", content_type != NULL ? content_type : "");
|
||||
if (client_geoip != NULL) {
|
||||
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", client_geoip);
|
||||
}
|
||||
|
||||
for (int i = 0; i < req->hdr.field_num; i++) {
|
||||
const http_field *f = &req->hdr.fields[i];
|
||||
const char *name = http_field_get_name(f);
|
||||
char *ptr = buf0;
|
||||
ptr += sprintf(ptr, "HTTP_");
|
||||
for (int j = 0; j < strlen(req->hdr.fields[i][0]); j++, ptr++) {
|
||||
char ch = req->hdr.fields[i][0][j];
|
||||
for (int j = 0; j < strlen(name); j++, ptr++) {
|
||||
char ch = name[j];
|
||||
if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
|
||||
ch = ch;
|
||||
} else if (ch >= 'a' && ch <= 'z') {
|
||||
@ -161,7 +177,7 @@ int fastcgi_init(fastcgi_conn *conn, unsigned int client_num, unsigned int req_n
|
||||
ptr[0] = ch;
|
||||
ptr[1] = 0;
|
||||
}
|
||||
param_ptr = fastcgi_add_param(param_ptr, buf0, req->hdr.fields[i][1]);
|
||||
param_ptr = fastcgi_add_param(param_ptr, buf0, http_field_get_value(f));
|
||||
}
|
||||
|
||||
unsigned short param_len = param_ptr - param_buf - sizeof(header);
|
||||
@ -170,7 +186,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 +194,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 +214,29 @@ 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);
|
||||
memcpy(msg_str, msg, msg_len);
|
||||
msg_str[msg_len] = 0;
|
||||
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 +244,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 +267,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 +284,7 @@ int fastcgi_php_error(char *msg, int msg_len, char *err_msg) {
|
||||
}
|
||||
ptr0 = ptr1 + 13;
|
||||
}
|
||||
free(msg_str);
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -272,20 +292,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 +313,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 +338,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 Sesimos 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 {
|
||||
@ -365,8 +388,8 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ret = http_parse_header_field(&res->hdr, ptr, pos0);
|
||||
if (ret != 0) return ret;
|
||||
ret = http_parse_header_field(&res->hdr, ptr, pos0, 0);
|
||||
if (ret != 0) return (int) ret;
|
||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||
return 0;
|
||||
}
|
||||
@ -378,7 +401,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 +409,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 +434,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 +466,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,49 +477,40 @@ 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) {
|
||||
if (client->enc) {
|
||||
SSL_write(client->ssl, "0\r\n\r\n", 5);
|
||||
} else {
|
||||
send(client->socket, "0\r\n\r\n", 5, 0);
|
||||
}
|
||||
sock_send(client, "0\r\n\r\n", 5, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
} 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 Sesimos 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);
|
||||
if (client->enc) {
|
||||
if (flags & FASTCGI_CHUNKED) SSL_write(client->ssl, buf0, len);
|
||||
SSL_write(client->ssl, ptr, buf_len);
|
||||
if (flags & FASTCGI_CHUNKED) SSL_write(client->ssl, "\r\n", 2);
|
||||
} else {
|
||||
if (flags & FASTCGI_CHUNKED) send(client->socket, buf0, len, 0);
|
||||
send(client->socket, ptr, buf_len, 0);
|
||||
if (flags & FASTCGI_CHUNKED) send(client->socket, "\r\n", 2, 0);
|
||||
}
|
||||
if (flags & FASTCGI_CHUNKED) sock_send(client, buf0, len, 0);
|
||||
sock_send(client, ptr, buf_len, 0);
|
||||
if (flags & FASTCGI_CHUNKED) sock_send(client, "\r\n", 2, 0);
|
||||
}
|
||||
} while ((flags & FASTCGI_COMPRESS) && strm.avail_out == 0);
|
||||
} 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);
|
||||
@ -499,10 +519,76 @@ 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 Sesimos 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];
|
||||
int ret;
|
||||
long ret;
|
||||
FCGI_Header header = {
|
||||
.version = FCGI_VERSION_1,
|
||||
.type = FCGI_STDIN,
|
||||
@ -514,36 +600,40 @@ int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
|
||||
.reserved = 0
|
||||
};
|
||||
|
||||
if (client->buf != NULL && client->buf_len - client->buf_off > 0) {
|
||||
ret = (int) (client->buf_len - client->buf_off);
|
||||
memcpy(buf, client->buf + client->buf_off, ret);
|
||||
goto send;
|
||||
}
|
||||
|
||||
while (rcv_len < len) {
|
||||
if (client->enc) {
|
||||
ret = SSL_read(client->ssl, buf, sizeof(buf));
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to receive: %s" CLR_STR, ssl_get_error(client->ssl, rcv_len));
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
ret = recv(client->socket, buf, sizeof(buf), 0);
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to receive: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
ret = sock_recv(client, buf, sizeof(buf), 0);
|
||||
if (ret <= 0) {
|
||||
print(ERR_STR "Unable to receive: %s" CLR_STR, sock_strerror(client));
|
||||
return -1;
|
||||
}
|
||||
send:
|
||||
|
||||
rcv_len += ret;
|
||||
header.contentLengthB1 = (ret >> 8) & 0xFF;
|
||||
header.contentLengthB0 = ret & 0xFF;
|
||||
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;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fastcgi_receive_chunked(fastcgi_conn *conn, sock *client) {
|
||||
long ret;
|
||||
unsigned long next_len;
|
||||
|
||||
while (1) {
|
||||
ret = sock_get_chunk_header(client);
|
||||
if (ret < 0) return (int) ret;
|
||||
|
||||
next_len = ret;
|
||||
if (next_len <= 0) break;
|
||||
|
||||
ret = fastcgi_receive(conn, client, next_len);
|
||||
if (ret < 0) return (int) ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
60
src/lib/fastcgi.h
Normal file
60
src/lib/fastcgi.h
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief FastCGI interface implementation (header file)
|
||||
* @file src/lib/fastcgi.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-26
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_FASTCGI_H
|
||||
#define SESIMOS_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_SESIMOS 2
|
||||
|
||||
#ifndef PHP_FPM_SOCKET
|
||||
# define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock"
|
||||
#endif
|
||||
|
||||
#define SESIMOS_BACKEND_SOCKET "/var/run/sesimos/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);
|
||||
|
||||
int fastcgi_receive_chunked(fastcgi_conn *conn, sock *client);
|
||||
|
||||
#endif //SESIMOS_FASTCGI_H
|
71
src/lib/geoip.c
Normal file
71
src/lib/geoip.c
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief MaxMind GeoIP Database interface
|
||||
* @file src/lib/geoip.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 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;
|
||||
}
|
19
src/lib/geoip.h
Normal file
19
src/lib/geoip.h
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief MaxMind GeoIP Database interface (header file)
|
||||
* @file src/lib/geoip.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-05-04
|
||||
*/
|
||||
|
||||
|
||||
#ifndef SESIMOS_GEOIP_H
|
||||
#define SESIMOS_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 //SESIMOS_GEOIP_H
|
450
src/lib/http.c
Normal file
450
src/lib/http.c
Normal file
@ -0,0 +1,450 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief HTTP implementation
|
||||
* @file src/lib/http.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-09
|
||||
*/
|
||||
|
||||
#include "http.h"
|
||||
#include "utils.h"
|
||||
#include "compress.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
void http_to_camel_case(char *str, int mode) {
|
||||
if (mode == HTTP_PRESERVE)
|
||||
return;
|
||||
|
||||
char ch, last = '-';
|
||||
for (int i = 0; i < strlen(str); i++) {
|
||||
ch = str[i];
|
||||
if (mode == HTTP_CAMEL && last == '-' && ch >= 'a' && ch <= 'z') {
|
||||
str[i] = (char) ((int) ch & 0x5F);
|
||||
} else if (mode == HTTP_LOWER && ch >= 'A' && ch <= 'Z') {
|
||||
str[i] = (char) ((int) ch | 0x20);
|
||||
}
|
||||
last = str[i];
|
||||
}
|
||||
}
|
||||
|
||||
const char *http_field_get_name(const http_field *field) {
|
||||
if (field->type == HTTP_FIELD_NORMAL) {
|
||||
return field->normal.name;
|
||||
} else if (field->type == HTTP_FIELD_EX_VALUE) {
|
||||
return field->ex_value.name;
|
||||
} else if (field->type == HTTP_FIELD_EX_NAME) {
|
||||
return field->ex_name.name;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *http_field_get_value(const http_field *field) {
|
||||
if (field->type == HTTP_FIELD_NORMAL) {
|
||||
return field->normal.value;
|
||||
} else if (field->type == HTTP_FIELD_EX_VALUE) {
|
||||
return field->ex_value.value;
|
||||
} else if (field->type == HTTP_FIELD_EX_NAME) {
|
||||
return field->ex_name.value;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void http_free_field(http_field *f) {
|
||||
if (f->type == HTTP_FIELD_NORMAL) {
|
||||
f->normal.name[0] = 0;
|
||||
f->normal.value[0] = 0;
|
||||
} else if (f->type == HTTP_FIELD_EX_VALUE) {
|
||||
f->ex_value.name[0] = 0;
|
||||
free(f->ex_value.value);
|
||||
f->ex_value.value = NULL;
|
||||
} else if (f->type == HTTP_FIELD_EX_NAME) {
|
||||
free(f->ex_name.name);
|
||||
free(f->ex_name.value);
|
||||
f->ex_name.name = NULL;
|
||||
f->ex_name.value = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void http_free_hdr(http_hdr *hdr) {
|
||||
for (int i = 0; i < hdr->field_num; i++) {
|
||||
http_free_field(&hdr->fields[i]);
|
||||
}
|
||||
hdr->field_num = 0;
|
||||
hdr->last_field_num = -1;
|
||||
}
|
||||
|
||||
void http_free_req(http_req *req) {
|
||||
if (req->uri != NULL) free(req->uri);
|
||||
req->uri = NULL;
|
||||
http_free_hdr(&req->hdr);
|
||||
}
|
||||
|
||||
void http_free_res(http_res *res) {
|
||||
http_free_hdr(&res->hdr);
|
||||
}
|
||||
|
||||
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr, int flags) {
|
||||
if (hdr->last_field_num > hdr->field_num) {
|
||||
print(ERR_STR "Unable to parse header: Invalid state" CLR_STR);
|
||||
return 3;
|
||||
}
|
||||
|
||||
char *pos1 = (char *) buf, *pos2 = (char *) end_ptr;
|
||||
if (buf[0] == ' ' || buf[0] == '\t') {
|
||||
if (hdr->last_field_num == -1) {
|
||||
print(ERR_STR "Unable to parse header" CLR_STR);
|
||||
return 3;
|
||||
}
|
||||
http_field *f = &hdr->fields[(int) hdr->last_field_num];
|
||||
|
||||
str_trim_lws(&pos1, &pos2);
|
||||
http_append_to_header_field(f, pos1, pos2 - pos1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
pos1 = memchr(buf, ':', end_ptr - buf);
|
||||
if (pos1 == NULL) {
|
||||
print(ERR_STR "Unable to parse header" CLR_STR);
|
||||
return 3;
|
||||
}
|
||||
long len1 = pos1 - buf;
|
||||
|
||||
pos1++;
|
||||
str_trim_lws(&pos1, &pos2);
|
||||
long len2 = pos2 - pos1;
|
||||
|
||||
char field_num = hdr->field_num;
|
||||
int found = http_get_header_field_num_len(hdr, buf, len1);
|
||||
if (!(flags & HTTP_MERGE_FIELDS) || found == -1) {
|
||||
if (http_add_header_field_len(hdr, buf, len1, pos1, len2 < 0 ? 0 : len2) != 0) {
|
||||
print(ERR_STR "Unable to parse header: Too many header fields" CLR_STR);
|
||||
return 3;
|
||||
}
|
||||
} else {
|
||||
field_num = (char) found;
|
||||
http_append_to_header_field(&hdr->fields[found], ", ", 2);
|
||||
http_append_to_header_field(&hdr->fields[found], pos1, len2);
|
||||
}
|
||||
|
||||
hdr->last_field_num = (char) field_num;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_receive_request(sock *client, http_req *req) {
|
||||
long rcv_len, len;
|
||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||
char *ptr, *pos0 = buf, *pos1, *pos2;
|
||||
memset(buf, 0, sizeof(buf));
|
||||
memset(req->method, 0, sizeof(req->method));
|
||||
memset(req->version, 0, sizeof(req->version));
|
||||
req->uri = NULL;
|
||||
req->hdr.field_num = 0;
|
||||
req->hdr.last_field_num = -1;
|
||||
|
||||
while (1) {
|
||||
rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE, MSG_PEEK);
|
||||
if (rcv_len <= 0) {
|
||||
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 http header: End of header not found" CLR_STR);
|
||||
return 5;
|
||||
} else {
|
||||
rcv_len = sock_recv(client, buf, header_len, 0);
|
||||
}
|
||||
|
||||
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 http header: Header contains illegal characters" CLR_STR);
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
ptr = buf;
|
||||
while (header_len > (ptr - buf + 2)) {
|
||||
pos0 = strstr(ptr, "\r\n");
|
||||
if (pos0 == NULL) {
|
||||
print(ERR_STR "Unable to parse http header: Invalid header format" CLR_STR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (req->version[0] == 0) {
|
||||
pos1 = memchr(ptr, ' ', rcv_len - (ptr - buf)) + 1;
|
||||
if (pos1 == NULL) goto err_hdr_fmt;
|
||||
|
||||
if (pos1 - ptr - 1 >= sizeof(req->method)) {
|
||||
print(ERR_STR "Unable to parse 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 http header: Invalid method" CLR_STR);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
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 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 http header: Invalid version" CLR_STR);
|
||||
return 3;
|
||||
}
|
||||
|
||||
len = pos2 - pos1 - 1;
|
||||
req->uri = malloc(len + 1);
|
||||
sprintf(req->uri, "%.*s", (int) len, pos1);
|
||||
sprintf(req->version, "%.3s", pos2 + 5);
|
||||
} else {
|
||||
int ret = http_parse_header_field(&req->hdr, ptr, pos0, HTTP_MERGE_FIELDS);
|
||||
if (ret != 0) return ret;
|
||||
}
|
||||
ptr = pos0 + 2;
|
||||
}
|
||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
|
||||
return http_get_header_field_len(hdr, field_name, strlen(field_name));
|
||||
}
|
||||
|
||||
const char *http_get_header_field_len(const http_hdr *hdr, const char *field_name, unsigned long len) {
|
||||
int num = http_get_header_field_num_len(hdr, field_name, len);
|
||||
return (num >= 0 && num < HTTP_MAX_HEADER_FIELD_NUM) ? http_field_get_value(&hdr->fields[num]) : NULL;
|
||||
}
|
||||
|
||||
int http_get_header_field_num(const http_hdr *hdr, const char *field_name) {
|
||||
return http_get_header_field_num_len(hdr, field_name, strlen(field_name));
|
||||
}
|
||||
|
||||
int http_get_header_field_num_len(const http_hdr *hdr, const char *field_name, unsigned long len) {
|
||||
char field_name_1[256], field_name_2[256];
|
||||
memcpy(field_name_1, field_name, len);
|
||||
field_name_1[len] = 0;
|
||||
http_to_camel_case(field_name_1, HTTP_LOWER);
|
||||
|
||||
for (int i = 0; i < hdr->field_num; i++) {
|
||||
strcpy(field_name_2, http_field_get_name(&hdr->fields[i]));
|
||||
http_to_camel_case(field_name_2, HTTP_LOWER);
|
||||
|
||||
if (strcmp(field_name_1, field_name_2) == 0)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
|
||||
return http_add_header_field_len(hdr, field_name, strlen(field_name), field_value, strlen(field_value));
|
||||
}
|
||||
|
||||
int http_add_header_field_len(http_hdr *hdr, const char *name, unsigned long name_len, const char *value, unsigned long value_len) {
|
||||
if (hdr->field_num >= HTTP_MAX_HEADER_FIELD_NUM)
|
||||
return -1;
|
||||
|
||||
http_field *f = &hdr->fields[(int) hdr->field_num];
|
||||
|
||||
if (name_len < sizeof(f->normal.name) && value_len < sizeof(f->normal.value)) {
|
||||
f->type = HTTP_FIELD_NORMAL;
|
||||
memcpy(f->normal.name, name, name_len);
|
||||
memcpy(f->normal.value, value, value_len);
|
||||
f->normal.name[name_len] = 0;
|
||||
f->normal.value[value_len] = 0;
|
||||
http_to_camel_case(f->normal.name, HTTP_PRESERVE);
|
||||
} else if (name_len < sizeof(f->ex_value.name)) {
|
||||
f->type = HTTP_FIELD_EX_VALUE;
|
||||
f->ex_value.value = malloc(value_len + 1);
|
||||
memcpy(f->ex_value.name, name, name_len);
|
||||
memcpy(f->ex_value.value, value, value_len);
|
||||
f->ex_value.name[name_len] = 0;
|
||||
f->ex_value.value[value_len] = 0;
|
||||
http_to_camel_case(f->ex_value.name, HTTP_PRESERVE);
|
||||
} else {
|
||||
f->type = HTTP_FIELD_EX_NAME;
|
||||
f->ex_name.name = malloc(name_len + 1);
|
||||
f->ex_name.value = malloc(value_len + 1);
|
||||
memcpy(f->ex_name.name, name, name_len);
|
||||
memcpy(f->ex_name.value, value, value_len);
|
||||
f->ex_name.name[name_len] = 0;
|
||||
f->ex_name.value[value_len] = 0;
|
||||
http_to_camel_case(f->ex_name.name, HTTP_PRESERVE);
|
||||
}
|
||||
|
||||
hdr->field_num++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_append_to_header_field(http_field *field, const char *value, unsigned long len) {
|
||||
if (field->type == HTTP_FIELD_NORMAL) {
|
||||
unsigned long total_len = strlen(field->normal.value) + len + 1;
|
||||
if (total_len < sizeof(field->normal.value)) {
|
||||
strncat(field->normal.value, value, len);
|
||||
} else {
|
||||
field->type = HTTP_FIELD_EX_VALUE;
|
||||
char *new = malloc(total_len);
|
||||
strcpy(new, field->normal.value);
|
||||
strncat(new, value, len);
|
||||
field->ex_value.value = new;
|
||||
}
|
||||
} else if (field->type == HTTP_FIELD_EX_VALUE) {
|
||||
field->ex_value.value = realloc(field->ex_value.value, strlen(field->ex_value.value) + len + 1);
|
||||
strncat(field->ex_value.value, value, len);
|
||||
} else if (field->type == HTTP_FIELD_EX_NAME) {
|
||||
field->ex_name.value = realloc(field->ex_name.value, strlen(field->ex_name.value) + len + 1);
|
||||
strncat(field->ex_name.value, value, len);
|
||||
}
|
||||
}
|
||||
|
||||
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
|
||||
char field_name_1[256], field_name_2[256];
|
||||
strcpy(field_name_1, field_name);
|
||||
http_to_camel_case(field_name_1, HTTP_LOWER);
|
||||
|
||||
int i = 0;
|
||||
int diff = 1;
|
||||
if (mode == HTTP_REMOVE_LAST) {
|
||||
i = hdr->field_num - 1;
|
||||
diff = -1;
|
||||
}
|
||||
for (; i < hdr->field_num && i >= 0; i += diff) {
|
||||
strcpy(field_name_2, http_field_get_name(&hdr->fields[i]));
|
||||
http_to_camel_case(field_name_2, HTTP_LOWER);
|
||||
if (strcmp(field_name_1, field_name_2) == 0) {
|
||||
http_free_field(&hdr->fields[i]);
|
||||
memmove(&hdr->fields[i], &hdr->fields[i + 1], sizeof(hdr->fields[0]) * (hdr->field_num - i));
|
||||
hdr->field_num--;
|
||||
if (mode == HTTP_REMOVE_ALL) {
|
||||
i -= diff;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int http_send_response(sock *client, http_res *res) {
|
||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||
long off = sprintf(buf, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
|
||||
for (int i = 0; i < res->hdr.field_num; i++) {
|
||||
const http_field *f = &res->hdr.fields[i];
|
||||
off += sprintf(buf + off, "%s: %s\r\n", http_field_get_name(f), http_field_get_value(f));
|
||||
}
|
||||
off += sprintf(buf + off, "\r\n");
|
||||
if (sock_send(client, buf, off, 0) < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int http_send_request(sock *server, http_req *req) {
|
||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||
long off = sprintf(buf, "%s %s HTTP/%s\r\n", req->method, req->uri, req->version);
|
||||
for (int i = 0; i < req->hdr.field_num; i++) {
|
||||
const http_field *f = &req->hdr.fields[i];
|
||||
off += sprintf(buf + off, "%s: %s\r\n", http_field_get_name(f), http_field_get_value(f));
|
||||
}
|
||||
off += sprintf(buf + off, "\r\n");
|
||||
long ret = sock_send(server, buf, off, 0);
|
||||
if (ret <= 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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(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) {
|
||||
return HTTP_2XX_STR;
|
||||
} else if (code >= 300 && code < 400) {
|
||||
return HTTP_3XX_STR;
|
||||
} else if (code >= 400 && code < 500) {
|
||||
return HTTP_4XX_STR;
|
||||
} else if (code >= 500 && code < 600) {
|
||||
return HTTP_5XX_STR;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
char *http_format_date(time_t time, char *buf, size_t size) {
|
||||
struct tm *timeinfo = gmtime(&time);
|
||||
strftime(buf, size, "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
|
||||
return buf;
|
||||
}
|
||||
|
||||
char *http_get_date(char *buf, size_t size) {
|
||||
time_t rawtime;
|
||||
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) {
|
||||
const char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
|
||||
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 (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;
|
||||
}
|
183
src/lib/http.h
Normal file
183
src/lib/http.h
Normal file
@ -0,0 +1,183 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief HTTP implementation (header file)
|
||||
* @file src/lib/http.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-09
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_HTTP_H
|
||||
#define SESIMOS_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_FIELD_NORMAL 0
|
||||
#define HTTP_FIELD_EX_VALUE 1
|
||||
#define HTTP_FIELD_EX_NAME 2
|
||||
|
||||
#define HTTP_MERGE_FIELDS 1
|
||||
|
||||
#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
|
||||
#define HTTP_MAX_HEADER_FIELD_NUM 64
|
||||
|
||||
#ifndef SERVER_STR
|
||||
# define SERVER_STR "Sesimos"
|
||||
#endif
|
||||
|
||||
#ifndef SERVER_STR_HTML
|
||||
# define SERVER_STR_HTML "Sesimos web 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 type;
|
||||
union {
|
||||
struct {
|
||||
char name[64];
|
||||
char value[192];
|
||||
} normal;
|
||||
struct {
|
||||
char name[192];
|
||||
char *value;
|
||||
} ex_value;
|
||||
struct {
|
||||
char *name;
|
||||
char *value;
|
||||
} ex_name;
|
||||
};
|
||||
} http_field;
|
||||
|
||||
typedef struct {
|
||||
char field_num;
|
||||
char last_field_num;
|
||||
http_field fields[HTTP_MAX_HEADER_FIELD_NUM];
|
||||
} 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;
|
||||
const char* ws_key;
|
||||
} 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);
|
||||
|
||||
const char *http_field_get_name(const http_field *field);
|
||||
|
||||
const char *http_field_get_value(const http_field *field);
|
||||
|
||||
void http_free_field(http_field *f);
|
||||
|
||||
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, int flags);
|
||||
|
||||
const char *http_get_header_field(const http_hdr *hdr, const char *field_name);
|
||||
|
||||
const char *http_get_header_field_len(const http_hdr *hdr, const char *field_name, unsigned long len);
|
||||
|
||||
int http_get_header_field_num(const http_hdr *hdr, const char *field_name);
|
||||
|
||||
int http_get_header_field_num_len(const http_hdr *hdr, const char *field_name, unsigned long len);
|
||||
|
||||
int http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
|
||||
|
||||
int http_add_header_field_len(http_hdr *hdr, const char *name, unsigned long name_len, const char *value, unsigned long value_len);
|
||||
|
||||
void http_append_to_header_field(http_field *field, const char *value, unsigned long len);
|
||||
|
||||
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 //SESIMOS_HTTP_H
|
305
src/lib/http_static.c
Normal file
305
src/lib/http_static.c
Normal file
@ -0,0 +1,305 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief HTTP static implementation
|
||||
* @file src/lib/http_static.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-05-03
|
||||
*/
|
||||
|
||||
#include "../defs.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 :(</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 :)</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 :)</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 :)</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);
|
@ -1,37 +1,11 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* FastCGI interface implementation (header file)
|
||||
* src/fastcgi.h
|
||||
* Lorenz Stechauner, 2020-12-26
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief FastCGI header file
|
||||
* @file src/lib/include/fastcgi.h
|
||||
*/
|
||||
|
||||
#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 SESIMOS_EXTERN_FASTCGI_H
|
||||
#define SESIMOS_EXTERN_FASTCGI_H
|
||||
|
||||
/*
|
||||
* Listening socket file number
|
||||
@ -144,4 +118,4 @@ typedef struct {
|
||||
FCGI_UnknownTypeBody body;
|
||||
} FCGI_UnknownTypeRecord;
|
||||
|
||||
#endif //NECRONDA_SERVER_FASTCGI_H
|
||||
#endif //SESIMOS_EXTERN_FASTCGI_H
|
566
src/lib/rev_proxy.c
Normal file
566
src/lib/rev_proxy.c
Normal file
@ -0,0 +1,566 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Reverse proxy
|
||||
* @file src/lib/rev_proxy.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-01-07
|
||||
*/
|
||||
|
||||
#include "../defs.h"
|
||||
#include "../server.h"
|
||||
#include "rev_proxy.h"
|
||||
#include "utils.h"
|
||||
#include "compress.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(void) {
|
||||
rev_proxy.ctx = SSL_CTX_new(TLS_client_method());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rev_proxy_request_header(http_req *req, int enc) {
|
||||
char buf1[256], buf2[256];
|
||||
int p_len;
|
||||
|
||||
const 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);
|
||||
}
|
||||
|
||||
const char *host = http_get_header_field(&req->hdr, "Host");
|
||||
const 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);
|
||||
}
|
||||
|
||||
const 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);
|
||||
}
|
||||
|
||||
const 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const 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, host_config *conf) {
|
||||
char buf1[256], buf2[256];
|
||||
int p_len;
|
||||
|
||||
const 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);
|
||||
}
|
||||
|
||||
const char *location = http_get_header_field(&res->hdr, "Location");
|
||||
if (location != NULL) {
|
||||
char *hostnames[] = {conf->name, conf->rev_proxy.hostname};
|
||||
for (int i = 0; i < sizeof(hostnames) / sizeof(hostnames[0]); i++) {
|
||||
char *hostname = hostnames[i];
|
||||
|
||||
p_len = snprintf(buf1, sizeof(buf1), "http://%s/", hostname);
|
||||
if (strncmp(location, buf1, p_len) == 0) goto match;
|
||||
|
||||
p_len = snprintf(buf1, sizeof(buf1), "https://%s/", hostname);
|
||||
if (strncmp(location, buf1, p_len) == 0) goto match;
|
||||
|
||||
p_len = snprintf(buf1, sizeof(buf1), "http://%s:%i/", hostname, conf->rev_proxy.port);
|
||||
if (strncmp(location, buf1, p_len) == 0) goto match;
|
||||
|
||||
p_len = snprintf(buf1, sizeof(buf1), "https://%s:%i/", hostname, conf->rev_proxy.port);
|
||||
if (strncmp(location, buf1, p_len) == 0) goto match;
|
||||
}
|
||||
|
||||
if (0) {
|
||||
match:
|
||||
strcpy(buf1, location + p_len - 1);
|
||||
http_remove_header_field(&res->hdr, "Location", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&res->hdr, "Location", buf1);
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
const char *connection, *upgrade, *ws_version;
|
||||
long ret;
|
||||
int tries = 0, 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:
|
||||
connection = http_get_header_field(&req->hdr, "Connection");
|
||||
if (connection != NULL && (strstr(connection, "upgrade") != NULL || strstr(connection, "Upgrade") != NULL)) {
|
||||
upgrade = http_get_header_field(&req->hdr, "Upgrade");
|
||||
ws_version = http_get_header_field(&req->hdr, "Sec-WebSocket-Version");
|
||||
if (upgrade != NULL && ws_version != NULL && strcmp(upgrade, "websocket") == 0 && strcmp(ws_version, "13") == 0) {
|
||||
ctx->ws_key = http_get_header_field(&req->hdr, "Sec-WebSocket-Key");
|
||||
} else {
|
||||
res->status = http_get_status(501);
|
||||
ctx->origin = INTERNAL;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "Connection", "keep-alive");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const char *content_length = http_get_header_field(&req->hdr, "Content-Length");
|
||||
unsigned long content_len = content_length != NULL ? strtoul(content_length, NULL, 10) : 0;
|
||||
const char *transfer_encoding = http_get_header_field(&req->hdr, "Transfer-Encoding");
|
||||
|
||||
ret = 0;
|
||||
if (content_len > 0) {
|
||||
ret = sock_splice(&rev_proxy, client, buffer, sizeof(buffer), content_len);
|
||||
} else if (transfer_encoding != NULL && strstr(transfer_encoding, "chunked") != NULL) {
|
||||
ret = sock_splice_chunked(&rev_proxy, client, buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
if (ret < 0 || (content_len != 0 && ret != content_len)) {
|
||||
if (ret == -1) {
|
||||
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;
|
||||
} 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));
|
||||
retry = tries < 4;
|
||||
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, 0);
|
||||
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, conf);
|
||||
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) {
|
||||
char buffer[CHUNK_SIZE], comp_out[CHUNK_SIZE], buf[256], *ptr;
|
||||
long ret = 0, len, snd_len;
|
||||
int finish_comp = 0;
|
||||
|
||||
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) {
|
||||
ret = sock_get_chunk_header(&rev_proxy);
|
||||
if (ret < 0) {
|
||||
if (ret == -1) {
|
||||
print("Unable to receive from server: Malformed chunk header");
|
||||
} else {
|
||||
print("Unable to receive from server: %s", sock_strerror(&rev_proxy));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
len_to_send = ret;
|
||||
ret = 1;
|
||||
if (len_to_send == 0 && (flags & REV_PROXY_COMPRESS)) {
|
||||
finish_comp = 1;
|
||||
len = 0;
|
||||
ptr = NULL;
|
||||
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 -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;
|
||||
}
|
39
src/lib/rev_proxy.h
Normal file
39
src/lib/rev_proxy.h
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Reverse proxy (header file)
|
||||
* @file src/lib/rev_proxy.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-01-07
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_REV_PROXY_H
|
||||
#define SESIMOS_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(void);
|
||||
|
||||
int rev_proxy_request_header(http_req *req, int enc);
|
||||
|
||||
int rev_proxy_response_header(http_req *req, http_res *res, host_config *conf);
|
||||
|
||||
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 //SESIMOS_REV_PROXY_H
|
204
src/lib/sock.c
Normal file
204
src/lib/sock.c
Normal file
@ -0,0 +1,204 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Basic TCP and TLS socket
|
||||
* @file src/lib/sock.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-01-07
|
||||
*/
|
||||
|
||||
#include "sock.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.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) {
|
||||
return "closed";
|
||||
} else if (s->enc) {
|
||||
if (s->_last_ret > 0) {
|
||||
return NULL;
|
||||
}
|
||||
const char *err1 = ERR_reason_error_string(s->_ssl_error);
|
||||
const char *err2 = strerror(errno);
|
||||
switch (sock_enc_error(s)) {
|
||||
case SSL_ERROR_NONE:
|
||||
return NULL;
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
return "closed";
|
||||
case SSL_ERROR_WANT_READ:
|
||||
return "want read";
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
return "want write";
|
||||
case SSL_ERROR_WANT_CONNECT:
|
||||
return "want connect";
|
||||
case SSL_ERROR_WANT_ACCEPT:
|
||||
return "want accept";
|
||||
case SSL_ERROR_WANT_X509_LOOKUP:
|
||||
return "want x509 lookup";
|
||||
case SSL_ERROR_SYSCALL:
|
||||
return ((s->_ssl_error == 0) ? ((s->_last_ret == 0) ? "protocol violation" : err2) : err1);
|
||||
case SSL_ERROR_SSL:
|
||||
return err1;
|
||||
default:
|
||||
return "unknown error";
|
||||
}
|
||||
} else {
|
||||
return strerror(s->_errno);
|
||||
}
|
||||
}
|
||||
|
||||
long sock_send(sock *s, void *buf, unsigned long len, int flags) {
|
||||
long ret;
|
||||
if (s->enc) {
|
||||
ret = SSL_write(s->ssl, buf, (int) len);
|
||||
s->_ssl_error = ERR_get_error();
|
||||
} else {
|
||||
ret = send(s->socket, buf, len, flags);
|
||||
}
|
||||
s->_last_ret = ret;
|
||||
s->_errno = errno;
|
||||
return ret >= 0 ? ret : -1;
|
||||
}
|
||||
|
||||
long sock_recv(sock *s, void *buf, unsigned long len, int flags) {
|
||||
long ret;
|
||||
if (s->enc) {
|
||||
int (*func)(SSL*, void*, int) = (flags & MSG_PEEK) ? SSL_peek : SSL_read;
|
||||
ret = func(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;
|
||||
return ret >= 0 ? ret : -1;
|
||||
}
|
||||
|
||||
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len) {
|
||||
long ret;
|
||||
unsigned long send_len = 0;
|
||||
unsigned long next_len;
|
||||
while (send_len < len) {
|
||||
next_len = (buf_len < (len - send_len)) ? buf_len : (len - send_len);
|
||||
ret = sock_recv(src, buf, next_len, 0);
|
||||
if (ret <= 0) return -2;
|
||||
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;
|
||||
send_len += next_len;
|
||||
}
|
||||
return (long) send_len;
|
||||
}
|
||||
|
||||
long sock_splice_chunked(sock *dst, sock *src, void *buf, unsigned long buf_len) {
|
||||
long ret;
|
||||
unsigned long send_len = 0;
|
||||
unsigned long next_len;
|
||||
|
||||
while (1) {
|
||||
ret = sock_get_chunk_header(src);
|
||||
if (ret < 0) return -2;
|
||||
|
||||
next_len = ret;
|
||||
if (next_len <= 0) break;
|
||||
|
||||
ret = sock_splice(dst, src, buf, buf_len, next_len);
|
||||
if (ret < 0) return ret;
|
||||
}
|
||||
|
||||
return (long) send_len;
|
||||
}
|
||||
|
||||
int sock_close(sock *s) {
|
||||
if ((int) s->enc && s->ssl != NULL) {
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sock_check(sock *s) {
|
||||
char buf;
|
||||
return recv(s->socket, &buf, 1, MSG_PEEK | MSG_DONTWAIT) == 1;
|
||||
}
|
||||
|
||||
int sock_poll(sock *sockets[], sock *ready[], sock *error[], int n_sock, int *n_ready, int *n_error, short events, int timeout_ms) {
|
||||
struct pollfd fds[n_sock];
|
||||
for (int i = 0; i < n_sock; i++) {
|
||||
fds[i].fd = sockets[i]->socket;
|
||||
fds[i].events = events;
|
||||
}
|
||||
|
||||
int ret = poll(fds, n_sock, timeout_ms);
|
||||
if (ret < 0 || ready == NULL || error == NULL) return ret;
|
||||
|
||||
*n_ready = 0, *n_error = 0;
|
||||
for (int i = 0; i < n_sock; i++) {
|
||||
if (fds[i].revents & events)
|
||||
ready[(*n_ready)++] = sockets[i];
|
||||
if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL))
|
||||
error[(*n_error)++] = sockets[i];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sock_poll_read(sock *sockets[], sock *readable[], sock *error[], int n_sock, int *n_readable, int *n_error, int timeout_ms) {
|
||||
return sock_poll(sockets, readable, error, n_sock, n_readable, n_error, POLLIN, timeout_ms);
|
||||
}
|
||||
|
||||
int sock_poll_write(sock *sockets[], sock *writable[], sock *error[], int n_sock, int *n_writable, int *n_error, int timeout_ms) {
|
||||
return sock_poll(sockets, writable, error, n_sock, n_writable, n_error, POLLOUT, timeout_ms);
|
||||
}
|
||||
|
||||
long sock_parse_chunk_header(const char *buf, long len, long *ret_len) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
char ch = buf[i];
|
||||
if (ch == '\r') {
|
||||
continue;
|
||||
} else if (ch == '\n') {
|
||||
if (ret_len != NULL) *ret_len = i + 1;
|
||||
return strtol(buf, NULL, 16);
|
||||
} else if (!((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))) {
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
long sock_get_chunk_header(sock *s) {
|
||||
long ret, len;
|
||||
char buf[16];
|
||||
|
||||
do {
|
||||
print("debug1"); // TODO remove
|
||||
ret = sock_recv(s, buf, sizeof(buf), MSG_PEEK);
|
||||
if (ret <= 0) return -2;
|
||||
else if (ret < 2) continue;
|
||||
|
||||
ret = sock_parse_chunk_header(buf, ret, &len);
|
||||
if (ret == -2) return -1;
|
||||
} while (ret < 0);
|
||||
|
||||
if (sock_recv(s, buf, len, 0) != len)
|
||||
return -2;
|
||||
|
||||
return ret;
|
||||
}
|
51
src/lib/sock.h
Normal file
51
src/lib/sock.h
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Basic TCP and TLS socket (header file)
|
||||
* @file src/lib/sock.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2021-01-07
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_SOCK_H
|
||||
#define SESIMOS_SOCK_H
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
typedef struct {
|
||||
unsigned int enc:1;
|
||||
int socket;
|
||||
SSL_CTX *ctx;
|
||||
SSL *ssl;
|
||||
long _last_ret;
|
||||
int _errno;
|
||||
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);
|
||||
|
||||
long sock_recv(sock *s, void *buf, unsigned long len, int flags);
|
||||
|
||||
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len);
|
||||
|
||||
long sock_splice_chunked(sock *dst, sock *src, void *buf, unsigned long buf_len);
|
||||
|
||||
int sock_close(sock *s);
|
||||
|
||||
int sock_check(sock *s);
|
||||
|
||||
int sock_poll(sock *sockets[], sock *ready[], sock *error[], int n_sock, int *n_ready, int *n_error, short events, int timeout_ms);
|
||||
|
||||
int sock_poll_read(sock *sockets[], sock *readable[], sock *error[], int n_sock, int *n_readable, int *n_error, int timeout_ms);
|
||||
|
||||
int sock_poll_write(sock *sockets[], sock *writable[], sock *error[], int n_sock, int *n_writable, int *n_error, int timeout_ms);
|
||||
|
||||
long sock_parse_chunk_header(const char *buf, long len, long *ret_len);
|
||||
|
||||
long sock_get_chunk_header(sock *s);
|
||||
|
||||
#endif //SESIMOS_SOCK_H
|
@ -1,11 +1,16 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* URI and path handlers
|
||||
* src/uri.c
|
||||
* Lorenz Stechauner, 2020-12-13
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief URI and path handlers
|
||||
* @file src/lib/uri.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-13
|
||||
*/
|
||||
|
||||
#include "uri.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
int path_is_directory(const char *path) {
|
||||
@ -28,6 +33,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 +57,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,60 +72,92 @@ 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);
|
||||
strcpy(uri->path, uri->req_path);
|
||||
|
||||
char last = 0;
|
||||
for (int i = 0, j = 0; i < size - 1; i++) {
|
||||
char ch = uri->req_path[i];
|
||||
if (last != '/' || ch != '/') {
|
||||
uri->path[j++] = ch;
|
||||
uri->path[j] = 0;
|
||||
}
|
||||
last = ch;
|
||||
}
|
||||
|
||||
if (dir_mode == URI_DIR_MODE_NO_VALIDATION) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (uri->path[strlen(uri->path) - 1] == '/') {
|
||||
uri->path[strlen(uri->path) - 1] = 0;
|
||||
strcpy(uri->pathinfo, "/");
|
||||
} 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);
|
||||
@ -126,6 +165,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;
|
||||
@ -145,7 +188,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;
|
||||
}
|
||||
|
@ -1,24 +1,27 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* URI and path handlers (header file)
|
||||
* src/uri.h
|
||||
* Lorenz Stechauner, 2020-12-13
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief URI and path handlers (header file)
|
||||
* @file src/lib/uri.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-13
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_URI_H
|
||||
#define NECRONDA_SERVER_URI_H
|
||||
#ifndef SESIMOS_URI_H
|
||||
#define SESIMOS_URI_H
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define URI_DIR_MODE_FORBIDDEN 0
|
||||
#define URI_DIR_MODE_LIST 1
|
||||
#define URI_DIR_MODE_INFO 2
|
||||
#define URI_DIR_MODE_NO_VALIDATION 0
|
||||
#define URI_DIR_MODE_FORBIDDEN 1
|
||||
#define URI_DIR_MODE_LIST 2
|
||||
#define URI_DIR_MODE_INFO 3
|
||||
|
||||
typedef struct {
|
||||
char etag[64];
|
||||
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;
|
||||
|
||||
@ -42,4 +45,4 @@ int uri_init_cache(http_uri *uri);
|
||||
|
||||
void uri_free(http_uri *uri);
|
||||
|
||||
#endif //NECRONDA_SERVER_URI_H
|
||||
#endif //SESIMOS_URI_H
|
207
src/lib/utils.c
Normal file
207
src/lib/utils.c
Normal file
@ -0,0 +1,207 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Utilities
|
||||
* @file src/lib/utils.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-03
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
char *log_prefix;
|
||||
|
||||
static const char base64_encode_table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
static const int base64_mod_table[3] = {0, 2, 1};
|
||||
|
||||
|
||||
char *format_duration(unsigned long micros, char *buf) {
|
||||
if (micros < 10000) {
|
||||
sprintf(buf, "%.1f ms", (double) micros / 1000);
|
||||
} else if (micros < 1000000 - 1000) {
|
||||
sprintf(buf, "%.0f ms", (double) micros / 1000);
|
||||
} else if (micros < 60000000 - 1000000) {
|
||||
sprintf(buf, "%.1f s", (double) micros / 1000000);
|
||||
} else if (micros < 6000000000) {
|
||||
sprintf(buf, "%.1f min", (double) micros / 1000000 / 60);
|
||||
} else {
|
||||
sprintf(buf, "%.0f min", (double) micros / 1000000 / 60);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
int url_encode_component(const void *in, size_t size_in, char *out, size_t size_out) {
|
||||
int size = 0;
|
||||
|
||||
// Encode control characters
|
||||
for (int i = 0; i < size_in; i++) {
|
||||
unsigned char ch = ((unsigned char *) in)[i];
|
||||
if (ch == ' ') {
|
||||
ch = '+';
|
||||
} else if (
|
||||
ch <= 0x20 || ch >= 0x7F ||
|
||||
!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') ||
|
||||
ch == '-' || ch == '_' || ch == '.' || ch == '!' || ch == '~' || ch == '*' || ch == '\'' ||
|
||||
ch == '(' || ch == ')')
|
||||
) {
|
||||
size += 3;
|
||||
if (size < size_out) sprintf(out + size - 3, "%%%02X", ch);
|
||||
ch = 0;
|
||||
}
|
||||
|
||||
if (ch != 0) {
|
||||
size++;
|
||||
if (size < size_out) out[size - 1] = (char) ch;
|
||||
}
|
||||
}
|
||||
|
||||
// Set terminating null byte
|
||||
if (size_out > 0) out[size < size_out ? size : size_out - 1] = 0;
|
||||
|
||||
// Return theoretical size
|
||||
return size;
|
||||
}
|
||||
|
||||
int url_encode(const void *in, size_t size_in, char *out, size_t size_out) {
|
||||
int size = 0;
|
||||
|
||||
// Encode control characters
|
||||
for (int i = 0; i < size_in; i++) {
|
||||
unsigned char ch = ((unsigned char *) in)[i];
|
||||
if (ch <= 0x20 || ch >= 0x7F) {
|
||||
size += 3;
|
||||
if (size < size_out) sprintf(out + size - 3, "%%%02X", ch);
|
||||
} else {
|
||||
size++;
|
||||
if (size < size_out) out[size - 1] = (char) ch;
|
||||
}
|
||||
}
|
||||
|
||||
// Set terminating null byte
|
||||
if (size_out > 0) out[size < size_out ? size : size_out - 1] = 0;
|
||||
|
||||
// Return theoretical size
|
||||
return size;
|
||||
}
|
||||
|
||||
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, "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) {
|
||||
memcpy(dst, src, len);
|
||||
dst[len] = 0;
|
||||
if (webroot == NULL)
|
||||
return 0;
|
||||
|
||||
char *pos;
|
||||
const unsigned long webroot_len = strlen(webroot);
|
||||
if (webroot_len == 0)
|
||||
return 0;
|
||||
|
||||
while ((pos = strstr(dst, webroot)) != NULL) {
|
||||
strcpy(pos, pos + webroot_len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int str_trim(char **start, char **end) {
|
||||
if (start == NULL || end == NULL || *start == NULL || *end == NULL)
|
||||
return -1;
|
||||
|
||||
(*end)--;
|
||||
while (*start[0] == ' ' || *start[0] == '\t' || *start[0] == '\r' || *start[0] == '\n') (*start)++;
|
||||
while (*end[0] == ' ' || *end[0] == '\t' || *end[0] == '\r' || *end[0] == '\n') (*end)--;
|
||||
(*end)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int str_trim_lws(char **start, char **end) {
|
||||
if (start == NULL || end == NULL || *start == NULL || *end == NULL)
|
||||
return -1;
|
||||
|
||||
(*end)--;
|
||||
while (*start[0] == ' ' || *start[0] == '\t') (*start)++;
|
||||
while (*end[0] == ' ' || *end[0] == '\t') (*end)--;
|
||||
(*end)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int base64_encode(void *data, unsigned long data_len, char *output, unsigned long *output_len) {
|
||||
unsigned long out_len = 4 * ((data_len + 2) / 3);
|
||||
if (output_len != NULL) *output_len = out_len;
|
||||
|
||||
for (int i = 0, j = 0; i < data_len;) {
|
||||
unsigned int octet_a = (i < data_len) ? ((unsigned char *) data)[i++] : 0;
|
||||
unsigned int octet_b = (i < data_len) ? ((unsigned char *) data)[i++] : 0;
|
||||
unsigned int octet_c = (i < data_len) ? ((unsigned char *) data)[i++] : 0;
|
||||
unsigned int triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
|
||||
output[j++] = base64_encode_table[(triple >> 3 * 6) & 0x3F];
|
||||
output[j++] = base64_encode_table[(triple >> 2 * 6) & 0x3F];
|
||||
output[j++] = base64_encode_table[(triple >> 1 * 6) & 0x3F];
|
||||
output[j++] = base64_encode_table[(triple >> 0 * 6) & 0x3F];
|
||||
}
|
||||
|
||||
for (int i = 0; i < base64_mod_table[data_len % 3]; i++)
|
||||
output[out_len - 1 - i] = '=';
|
||||
output[out_len] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
53
src/lib/utils.h
Normal file
53
src/lib/utils.h
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Utilities (header file)
|
||||
* @file src/lib/utils.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-03
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_UTILS_H
|
||||
#define SESIMOS_UTILS_H
|
||||
|
||||
#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)
|
||||
|
||||
#define out_x(x, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, FUNC, ...) FUNC
|
||||
|
||||
#define print(...) out_x(, ##__VA_ARGS__, out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
|
||||
out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
|
||||
out_2(__VA_ARGS__), out_1(__VA_ARGS__))
|
||||
|
||||
|
||||
char *format_duration(unsigned long micros, char *buf);
|
||||
|
||||
int url_encode_component(const void *in, size_t size_in, char *out, size_t size_out);
|
||||
|
||||
int url_encode(const void *in, size_t size_in, char *out, size_t size_out);
|
||||
|
||||
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);
|
||||
|
||||
int str_trim(char **start, char **end);
|
||||
|
||||
int str_trim_lws(char **start, char **end);
|
||||
|
||||
int base64_encode(void *data, unsigned long data_len, char *output, unsigned long *output_len);
|
||||
|
||||
#endif //SESIMOS_UTILS_H
|
207
src/lib/websocket.c
Normal file
207
src/lib/websocket.c
Normal file
@ -0,0 +1,207 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief WebSocket reverse proxy
|
||||
* @file src/lib/websocket.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2022-08-16
|
||||
*/
|
||||
|
||||
#include "../defs.h"
|
||||
#include "websocket.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
static const char ws_key_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
static volatile sig_atomic_t terminate = 0;
|
||||
|
||||
void ws_terminate(int _) {
|
||||
terminate = 1;
|
||||
}
|
||||
|
||||
int ws_calc_accept_key(const char *key, char *accept_key) {
|
||||
if (key == NULL || accept_key == NULL)
|
||||
return -1;
|
||||
|
||||
char input[256] = "";
|
||||
unsigned char output[SHA_DIGEST_LENGTH];
|
||||
strcat(input, key);
|
||||
strcat(input, ws_key_uuid);
|
||||
|
||||
if (SHA1((unsigned char *) input, strlen(input), output) == NULL) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
base64_encode(output, sizeof(output), accept_key, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ws_recv_frame_header(sock *s, ws_frame *frame) {
|
||||
unsigned char buf[12];
|
||||
|
||||
long ret = sock_recv(s, buf, 2, 0);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to receive from socket: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
} else if (ret != 2) {
|
||||
print(ERR_STR "Unable to receive 2 bytes from socket" CLR_STR);
|
||||
return -2;
|
||||
}
|
||||
|
||||
unsigned short bits = (buf[0] << 8) | buf[1];
|
||||
frame->f_fin = (bits >> 15) & 1;
|
||||
frame->f_rsv1 = (bits >> 14) & 1;
|
||||
frame->f_rsv2 = (bits >> 13) & 1;
|
||||
frame->f_rsv3 = (bits >> 12) & 1;
|
||||
frame->opcode = (bits >> 8) & 0xF;
|
||||
frame->f_mask = (bits >> 7) & 1;
|
||||
unsigned short len = (bits & 0x7F);
|
||||
|
||||
int remaining = frame->f_mask ? 4 : 0;
|
||||
if (len == 126) {
|
||||
remaining += 2;
|
||||
} else if (len == 127) {
|
||||
remaining += 8;
|
||||
}
|
||||
|
||||
ret = sock_recv(s, buf, remaining, 0);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to receive from socket: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
} else if (ret != remaining) {
|
||||
print(ERR_STR "Unable to receive correct number of bytes from socket" CLR_STR);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (len == 126) {
|
||||
frame->len = (((unsigned long) buf[0]) << 8) | ((unsigned long) buf[1]);
|
||||
} else if (len == 127) {
|
||||
frame->len =
|
||||
(((unsigned long) buf[0]) << 56) |
|
||||
(((unsigned long) buf[1]) << 48) |
|
||||
(((unsigned long) buf[2]) << 40) |
|
||||
(((unsigned long) buf[3]) << 32) |
|
||||
(((unsigned long) buf[4]) << 24) |
|
||||
(((unsigned long) buf[5]) << 16) |
|
||||
(((unsigned long) buf[6]) << 8) |
|
||||
(((unsigned long) buf[7]) << 0);
|
||||
} else {
|
||||
frame->len = len;
|
||||
}
|
||||
|
||||
if (frame->f_mask) memcpy(frame->masking_key, buf + (remaining - 4), 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ws_send_frame_header(sock *s, ws_frame *frame) {
|
||||
unsigned char buf[14], *ptr = buf;
|
||||
|
||||
unsigned short len;
|
||||
if (frame->len > 0x7FFF) {
|
||||
len = 127;
|
||||
} else if (frame->len > 125) {
|
||||
len = 126;
|
||||
} else {
|
||||
len = frame->len;
|
||||
}
|
||||
|
||||
unsigned short bits =
|
||||
(frame->f_fin << 15) |
|
||||
(frame->f_rsv1 << 14) |
|
||||
(frame->f_rsv2 << 13) |
|
||||
(frame->f_rsv3 << 12) |
|
||||
(frame->opcode << 8) |
|
||||
(frame->f_mask << 7) |
|
||||
len;
|
||||
|
||||
ptr++[0] = bits >> 8;
|
||||
ptr++[0] = bits & 0xFF;
|
||||
|
||||
if (len >= 126) {
|
||||
for (int i = (len == 126 ? 2 : 8) - 1; i >= 0; i--)
|
||||
ptr++[0] = (unsigned char) ((frame->len >> (i * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
if (frame->f_mask) {
|
||||
memcpy(ptr, frame->masking_key, 4);
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
long ret = sock_send(s, buf, ptr - buf, frame->len != 0 ? MSG_MORE : 0);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to send to socket: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
} else if (ret != ptr - buf) {
|
||||
print(ERR_STR "Unable to send to socket" CLR_STR);
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ws_handle_connection(sock *s1, sock *s2) {
|
||||
sock *poll_socks[2] = {s1, s2};
|
||||
sock *readable[2], *error[2];
|
||||
int n_sock = 2, n_readable = 0, n_error = 0;
|
||||
ws_frame frame;
|
||||
char buf[CHUNK_SIZE];
|
||||
int closes = 0;
|
||||
long ret;
|
||||
|
||||
signal(SIGINT, ws_terminate);
|
||||
signal(SIGTERM, ws_terminate);
|
||||
|
||||
while (!terminate && closes != 3) {
|
||||
ret = sock_poll_read(poll_socks, readable, error, n_sock, &n_readable, &n_error, WS_TIMEOUT * 1000);
|
||||
if (terminate) {
|
||||
break;
|
||||
} else if (ret < 0) {
|
||||
print(ERR_STR "Unable to poll sockets: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
} else if (n_readable == 0) {
|
||||
print(ERR_STR "Connection timed out" CLR_STR);
|
||||
return -2;
|
||||
} else if (n_error > 0) {
|
||||
print(ERR_STR "Peer closed connection" CLR_STR);
|
||||
return -3;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n_readable; i++) {
|
||||
sock *s = readable[i];
|
||||
sock *o = (s == s1) ? s2 : s1;
|
||||
if (ws_recv_frame_header(s, &frame) != 0) return -3;
|
||||
|
||||
// print("WebSocket: Peer %s, Opcode=0x%X, Len=%li", (s == s1) ? "1" : "2", frame.opcode, frame.len);
|
||||
|
||||
if (frame.opcode == 0x8) {
|
||||
n_sock--;
|
||||
if (s == s1) {
|
||||
poll_socks[0] = s2;
|
||||
closes |= 1;
|
||||
} else {
|
||||
closes |= 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (ws_send_frame_header(o, &frame) != 0) return -3;
|
||||
|
||||
if (frame.len > 0) {
|
||||
ret = sock_splice(o, s, buf, sizeof(buf), frame.len);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to forward data in WebSocket: %s" CLR_STR, strerror(errno));
|
||||
return -4;
|
||||
} else if (ret != frame.len) {
|
||||
print(ERR_STR "Unable to forward correct number of bytes in WebSocket" CLR_STR);
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
35
src/lib/websocket.h
Normal file
35
src/lib/websocket.h
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief WebSocket reverse proxy (header file)
|
||||
* @file src/lib/websocket.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2022-08-16
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_WEBSOCKET_H
|
||||
#define SESIMOS_WEBSOCKET_H
|
||||
|
||||
#include "sock.h"
|
||||
|
||||
#define WS_TIMEOUT 3600
|
||||
|
||||
typedef struct {
|
||||
unsigned char f_fin:1;
|
||||
unsigned char f_rsv1:1;
|
||||
unsigned char f_rsv2:1;
|
||||
unsigned char f_rsv3:1;
|
||||
unsigned char opcode:4;
|
||||
unsigned char f_mask:1;
|
||||
unsigned long len;
|
||||
char masking_key[4];
|
||||
} ws_frame;
|
||||
|
||||
int ws_calc_accept_key(const char *key, char *accept_key);
|
||||
|
||||
int ws_recv_frame_header(sock *s, ws_frame *frame);
|
||||
|
||||
int ws_send_frame_header(sock *s, ws_frame *frame);
|
||||
|
||||
int ws_handle_connection(sock *s1, sock *s2);
|
||||
|
||||
#endif //SESIMOS_WEBSOCKET_H
|
@ -1,90 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Main executable (header file)
|
||||
* src/necronda-server.c
|
||||
* 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 <maxminddb.h>
|
||||
#include <dirent.h>
|
||||
|
||||
|
||||
#define NUM_SOCKETS 2
|
||||
#define MAX_CHILDREN 1024
|
||||
#define MAX_MMDB 3
|
||||
#define LISTEN_BACKLOG 16
|
||||
#define REQ_PER_CONNECTION 100
|
||||
#define CLIENT_TIMEOUT 3600
|
||||
|
||||
#define CHUNK_SIZE 4096
|
||||
#define CLIENT_MAX_HEADER_SIZE 8192
|
||||
#define FILE_CACHE_SIZE 1024
|
||||
#define SHM_KEY 255641
|
||||
#define GEOIP_MAX_SIZE 8192
|
||||
|
||||
#define ERR_STR "\x1B[1;31m"
|
||||
#define CLR_STR "\x1B[0m"
|
||||
#define BLD_STR "\x1B[1m"
|
||||
#define WRN_STR "\x1B[1;33m"
|
||||
#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.1"
|
||||
#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
|
||||
|
||||
int sockets[NUM_SOCKETS];
|
||||
pid_t children[MAX_CHILDREN];
|
||||
MMDB_s mmdbs[MAX_MMDB];
|
||||
|
||||
const char *cert_file, *key_file, *webroot_base, *geoip_dir, *dns_server;
|
||||
|
||||
typedef struct {
|
||||
unsigned int enc:1;
|
||||
int socket;
|
||||
SSL_CTX *ctx;
|
||||
SSL *ssl;
|
||||
char *buf;
|
||||
unsigned long buf_len;
|
||||
unsigned long buf_off;
|
||||
} sock;
|
||||
|
||||
char *ssl_get_error(SSL *ssl, int ret);
|
||||
|
||||
#endif //NECRONDA_SERVER_NECRONDA_SERVER_H
|
@ -1,66 +1,63 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Main executable
|
||||
* src/necronda-server.c
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
* Sesimos - secure, simple, modern web server
|
||||
* @brief Main executable
|
||||
* @file src/server.c
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-03
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 199309L
|
||||
#include "defs.h"
|
||||
#include "server.h"
|
||||
#include "client.h"
|
||||
|
||||
#include "necronda-server.h"
|
||||
#include "lib/cache.h"
|
||||
#include "lib/config.h"
|
||||
#include "lib/sock.h"
|
||||
#include "lib/rev_proxy.h"
|
||||
#include "lib/geoip.h"
|
||||
#include "lib/utils.h"
|
||||
|
||||
#include "utils.c"
|
||||
#include "uri.c"
|
||||
#include "cache.c"
|
||||
#include "http.c"
|
||||
#include "client.c"
|
||||
#include "fastcgi.c"
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.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;
|
||||
volatile sig_atomic_t 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() {
|
||||
void openssl_init(void) {
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
ERR_load_BIO_strings();
|
||||
OpenSSL_add_all_algorithms();
|
||||
}
|
||||
|
||||
char *ssl_get_error(SSL *ssl, int ret) {
|
||||
if (ret > 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unsigned long ret2 = ERR_get_error();
|
||||
char *err2 = strerror(errno);
|
||||
char *err1 = (char *) ERR_reason_error_string(ret2);
|
||||
|
||||
switch (SSL_get_error(ssl, ret)) {
|
||||
case SSL_ERROR_NONE:
|
||||
return "none";
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
return "closed";
|
||||
case SSL_ERROR_WANT_READ:
|
||||
return "want read";
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
return "want write";
|
||||
case SSL_ERROR_WANT_CONNECT:
|
||||
return "want connect";
|
||||
case SSL_ERROR_WANT_ACCEPT:
|
||||
return "want accept";
|
||||
case SSL_ERROR_WANT_X509_LOOKUP:
|
||||
return "want x509 lookup";
|
||||
case SSL_ERROR_SYSCALL:
|
||||
return ((ret2 == 0) ? ((ret == 0) ? "protocol violation" : err2) : err1);
|
||||
case SSL_ERROR_SSL:
|
||||
return err1;
|
||||
default:
|
||||
return "unknown error";
|
||||
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() {
|
||||
void destroy(int _) {
|
||||
fprintf(stderr, "\n" ERR_STR "Terminating forcefully!" CLR_STR "\n");
|
||||
int status = 0;
|
||||
int ret;
|
||||
@ -69,13 +66,11 @@ void destroy() {
|
||||
if (children[i] != 0) {
|
||||
ret = waitpid(children[i], &status, WNOHANG);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
|
||||
children[i], strerror(errno));
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n", children[i], strerror(errno));
|
||||
} else if (ret == children[i]) {
|
||||
children[i] = 0;
|
||||
if (status != 0) {
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
|
||||
ret, status);
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n", ret, status);
|
||||
}
|
||||
} else {
|
||||
kill(children[i], SIGKILL);
|
||||
@ -87,10 +82,11 @@ void destroy() {
|
||||
fprintf(stderr, ERR_STR "Killed %i child process(es)" CLR_STR "\n", kills);
|
||||
}
|
||||
cache_unload();
|
||||
config_unload();
|
||||
exit(2);
|
||||
}
|
||||
|
||||
void terminate() {
|
||||
void terminate(int _) {
|
||||
fprintf(stderr, "\nTerminating gracefully...\n");
|
||||
active = 0;
|
||||
|
||||
@ -109,13 +105,11 @@ void terminate() {
|
||||
if (children[i] != 0) {
|
||||
ret = waitpid(children[i], &status, WNOHANG);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
|
||||
children[i], strerror(errno));
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n", children[i], strerror(errno));
|
||||
} else if (ret == children[i]) {
|
||||
children[i] = 0;
|
||||
if (status != 0) {
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
|
||||
ret, status);
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n", ret, status);
|
||||
}
|
||||
} else {
|
||||
kill(children[i], SIGTERM);
|
||||
@ -132,13 +126,11 @@ void terminate() {
|
||||
if (children[i] != 0) {
|
||||
ret = waitpid(children[i], &status, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
|
||||
children[i], strerror(errno));
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n", children[i], strerror(errno));
|
||||
} else if (ret == children[i]) {
|
||||
children[i] = 0;
|
||||
if (status != 0) {
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
|
||||
ret, status);
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n", ret, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,13 +147,13 @@ void terminate() {
|
||||
fprintf(stderr, "Goodbye\n");
|
||||
}
|
||||
cache_unload();
|
||||
config_unload();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
const int YES = 1;
|
||||
fd_set socket_fds, read_socket_fds;
|
||||
int max_socket_fd = 0;
|
||||
struct pollfd poll_fds[NUM_SOCKETS];
|
||||
int ready_sockets_num;
|
||||
long client_num = 0;
|
||||
char buf[1024];
|
||||
@ -176,79 +168,50 @@ int main(int argc, const char *argv[]) {
|
||||
memset(children, 0, sizeof(children));
|
||||
memset(mmdbs, 0, sizeof(mmdbs));
|
||||
|
||||
struct timeval timeout;
|
||||
|
||||
const struct sockaddr_in6 addresses[2] = {
|
||||
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(80)},
|
||||
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(443)}
|
||||
};
|
||||
|
||||
if (setvbuf(stdout, NULL, _IONBF, 0) != 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to set stdout to unbuffered mode: %s" CLR_STR, strerror(errno));
|
||||
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to set stdout to line-buffered mode: %s" CLR_STR, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
printf("Necronda Web Server\n");
|
||||
printf("Sesimos web server " SERVER_VERSION "\n");
|
||||
|
||||
ret = config_init();
|
||||
if (ret != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
config_file = NULL;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
const char *arg = argv[i];
|
||||
unsigned long len = strlen(arg);
|
||||
if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
|
||||
printf("Usage: necronda-server [-h] -w <PATH> -c <CERT-FILE> -p <KEY-FILE> [-g <DB-DIR>] [-d <DNS-SERVER>]\n"
|
||||
printf("Usage: sesimos [-h] [-c <CONFIG-FILE>]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -c, --cert <CERT-FILE> path to the full chain certificate file\n"
|
||||
" -d, --dns <DNS-SERVER> ip address or hostname of a DNS server for dig\n"
|
||||
" -g, --geoip <DB-DIR> path to a Maxmind GeoIP Database file\n"
|
||||
" -h, --help print this dialogue\n"
|
||||
" -p, --privkey <KEY-FILE> path to the private key file\n"
|
||||
" -w, --webroot <PATH> path to the web root directory\n");
|
||||
" -c, --config <CONFIG-FILE> path to the config file. If not provided, default will be used\n"
|
||||
" -h, --help print this dialogue\n");
|
||||
config_unload();
|
||||
return 0;
|
||||
} else if (strcmp(arg, "-w") == 0 || strcmp(arg, "--webroot") == 0) {
|
||||
} else if (strcmp(arg, "-c") == 0 || strcmp(arg, "--config") == 0) {
|
||||
if (i == argc - 1) {
|
||||
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --webroot <WEBROOT>" CLR_STR "\n", arg);
|
||||
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --config <CONFIG-FILE>" CLR_STR "\n", arg);
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
webroot_base = argv[++i];
|
||||
} else if (strcmp(arg, "-c") == 0 || strcmp(arg, "--cert") == 0) {
|
||||
if (i == argc - 1) {
|
||||
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --cert <CERT-FILE>" CLR_STR "\n", arg);
|
||||
return 1;
|
||||
}
|
||||
cert_file = argv[++i];
|
||||
} else if (strcmp(arg, "-p") == 0 || strcmp(arg, "--privkey") == 0) {
|
||||
if (i == argc - 1) {
|
||||
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --privkey <KEY-FILE>" CLR_STR "\n", arg);
|
||||
return 1;
|
||||
}
|
||||
key_file = argv[++i];
|
||||
} else if (strcmp(arg, "-g") == 0 || strcmp(arg, "--geoip") == 0) {
|
||||
if (i == argc - 1) {
|
||||
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --geoip <DB-DIR>" CLR_STR "\n", arg);
|
||||
return 1;
|
||||
}
|
||||
geoip_dir = argv[++i];
|
||||
} else if (strcmp(arg, "-d") == 0 || strcmp(arg, "--dns") == 0) {
|
||||
if (i == argc - 1) {
|
||||
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --dns <DNS-SERVER>" CLR_STR "\n", arg);
|
||||
return 1;
|
||||
}
|
||||
dns_server = argv[++i];
|
||||
config_file = argv[++i];
|
||||
} else {
|
||||
fprintf(stderr, ERR_STR "Unable to parse argument '%s'" CLR_STR "\n", arg);
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (webroot_base == NULL) {
|
||||
fprintf(stderr, ERR_STR "Error: --webroot is missing" CLR_STR "\n");
|
||||
return 1;
|
||||
}
|
||||
if (cert_file == NULL) {
|
||||
fprintf(stderr, ERR_STR "Error: --cert is missing" CLR_STR "\n");
|
||||
return 1;
|
||||
}
|
||||
if (key_file == NULL) {
|
||||
fprintf(stderr, ERR_STR "Error: --privkey is missing" CLR_STR "\n");
|
||||
ret = config_load(config_file == NULL ? DEFAULT_CONFIG_FILE : config_file);
|
||||
if (ret != 0) {
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -258,12 +221,14 @@ int main(int argc, const char *argv[]) {
|
||||
if (sockets[1] < 0) {
|
||||
socket_err:
|
||||
fprintf(stderr, ERR_STR "Unable to create socket: %s" CLR_STR "\n", strerror(errno));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||
if (setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(YES)) < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to set options for socket %i: %s" CLR_STR "\n", i, strerror(errno));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -272,16 +237,18 @@ int main(int argc, const char *argv[]) {
|
||||
if (bind(sockets[1], (struct sockaddr *) &addresses[1], sizeof(addresses[1])) < 0) {
|
||||
bind_err:
|
||||
fprintf(stderr, ERR_STR "Unable to bind socket to address: %s" CLR_STR "\n", strerror(errno));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
|
||||
signal(SIGINT, terminate);
|
||||
signal(SIGTERM, terminate);
|
||||
|
||||
if (geoip_dir != NULL) {
|
||||
if (geoip_dir[0] != 0) {
|
||||
DIR *geoip = opendir(geoip_dir);
|
||||
if (geoip == NULL) {
|
||||
fprintf(stderr, ERR_STR "Unable to open GeoIP dir: %s" CLR_STR "\n", strerror(errno));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
struct dirent *dir;
|
||||
@ -290,83 +257,97 @@ int main(int argc, const char *argv[]) {
|
||||
if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".mmdb") != 0) continue;
|
||||
if (i >= MAX_MMDB) {
|
||||
fprintf(stderr, ERR_STR "Too many .mmdb files" CLR_STR "\n");
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
sprintf(buf, "%s/%s", geoip_dir, dir->d_name);
|
||||
ret = MMDB_open(buf, 0, &mmdbs[i]);
|
||||
if (ret != MMDB_SUCCESS) {
|
||||
fprintf(stderr, ERR_STR "Unable to open .mmdb file: %s" CLR_STR "\n", MMDB_strerror(ret));
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (i == 0) {
|
||||
fprintf(stderr, ERR_STR "No .mmdb files found in %s" CLR_STR "\n", geoip_dir);
|
||||
config_unload();
|
||||
return 1;
|
||||
}
|
||||
closedir(geoip);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
|
||||
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));
|
||||
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();
|
||||
cache_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();
|
||||
cache_unload();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
FD_ZERO(&socket_fds);
|
||||
client.ctx = contexts[0];
|
||||
|
||||
|
||||
rev_proxy_preload();
|
||||
|
||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||
FD_SET(sockets[i], &socket_fds);
|
||||
if (sockets[i] > max_socket_fd) {
|
||||
max_socket_fd = 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));
|
||||
config_unload();
|
||||
cache_unload();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
ret = cache_init();
|
||||
if (ret < 0) {
|
||||
return 1;
|
||||
} else if (ret != 0) {
|
||||
return 0;
|
||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||
poll_fds[i].fd = sockets[i];
|
||||
poll_fds[i].events = POLLIN;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Ready to accept connections\n");
|
||||
|
||||
while (active) {
|
||||
timeout.tv_sec = 1;
|
||||
timeout.tv_usec = 0;
|
||||
read_socket_fds = socket_fds;
|
||||
ready_sockets_num = select(max_socket_fd + 1, &read_socket_fds, NULL, NULL, &timeout);
|
||||
ready_sockets_num = poll(poll_fds, NUM_SOCKETS, 1000);
|
||||
if (ready_sockets_num < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to select sockets: %s" CLR_STR "\n", strerror(errno));
|
||||
fprintf(stderr, ERR_STR "Unable to poll sockets: %s" CLR_STR "\n", strerror(errno));
|
||||
terminate(0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||
if (FD_ISSET(sockets[i], &read_socket_fds)) {
|
||||
if (poll_fds[i].revents & POLLIN) {
|
||||
client_fd = accept(sockets[i], (struct sockaddr *) &client_addr, &client_addr_len);
|
||||
if (client_fd < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to accept connection: %s" CLR_STR "\n", strerror(errno));
|
||||
@ -380,7 +361,7 @@ int main(int argc, const char *argv[]) {
|
||||
signal(SIGTERM, SIG_IGN);
|
||||
|
||||
client.socket = client_fd;
|
||||
client.enc = i == 1;
|
||||
client.enc = (i == 1);
|
||||
return client_handler(&client, client_num, &client_addr);
|
||||
} else if (pid > 0) {
|
||||
// parent
|
||||
@ -398,23 +379,24 @@ 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) {
|
||||
ret = waitpid(children[i], &status, WNOHANG);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
|
||||
children[i], strerror(errno));
|
||||
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n", children[i], strerror(errno));
|
||||
} else if (ret == children[i]) {
|
||||
children[i] = 0;
|
||||
if (status != 0) {
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
|
||||
ret, status);
|
||||
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n", ret, status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config_unload();
|
||||
cache_unload();
|
||||
return 0;
|
||||
}
|
35
src/server.h
Normal file
35
src/server.h
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* sesimos - secure, simple, modern web server
|
||||
* @brief Main executable (header file)
|
||||
* @file src/server.h
|
||||
* @author Lorenz Stechauner
|
||||
* @date 2020-12-03
|
||||
*/
|
||||
|
||||
#ifndef SESIMOS_SERVER_H
|
||||
#define SESIMOS_SERVER_H
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <maxminddb.h>
|
||||
#include <signal.h>
|
||||
|
||||
#define NUM_SOCKETS 2
|
||||
#define MAX_CHILDREN 1024
|
||||
#define MAX_MMDB 3
|
||||
#define LISTEN_BACKLOG 16
|
||||
#define REQ_PER_CONNECTION 200
|
||||
#define CLIENT_TIMEOUT 3600
|
||||
#define SERVER_TIMEOUT_INIT 4
|
||||
#define SERVER_TIMEOUT 3600
|
||||
|
||||
|
||||
extern int sockets[NUM_SOCKETS];
|
||||
extern pid_t children[MAX_CHILDREN];
|
||||
extern MMDB_s mmdbs[MAX_MMDB];
|
||||
|
||||
extern volatile sig_atomic_t 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 //SESIMOS_SERVER_H
|
157
src/utils.c
157
src/utils.c
@ -1,157 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Utilities
|
||||
* src/utils.c
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
char *format_duration(unsigned long micros, char *buf) {
|
||||
if (micros < 10000) {
|
||||
sprintf(buf, "%.1f ms", (double) micros / 1000);
|
||||
} else if (micros < 1000000) {
|
||||
sprintf(buf, "%li ms", micros / 1000);
|
||||
} else if (micros < 60000000) {
|
||||
sprintf(buf, "%.1f s", (double) micros / 1000000);
|
||||
} else if (micros < 6000000000) {
|
||||
sprintf(buf, "%.1f min", (double) micros / 1000000 / 60);
|
||||
} else {
|
||||
sprintf(buf, "%li min", micros / 1000000 / 60);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
int url_encode(const char *str, char *enc, ssize_t *size) {
|
||||
char *ptr = enc;
|
||||
char ch;
|
||||
memset(enc, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - enc) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@' || ch == '!' ||
|
||||
ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '+' || ch == ',' ||
|
||||
ch == ';' || ch == '=' || ch < ' ' || ch > '~') {
|
||||
if ((ptr - enc + 2) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
sprintf(ptr, "%%%02X", ch);
|
||||
ptr += 2;
|
||||
} else if (ch == ' ') {
|
||||
ptr[0] = '+';
|
||||
} else {
|
||||
ptr[0] = ch;
|
||||
}
|
||||
}
|
||||
*size = ptr - enc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int encode_url(const char *str, char *enc, ssize_t *size) {
|
||||
char *ptr = enc;
|
||||
unsigned char ch;
|
||||
memset(enc, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - enc) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch > 0x7F || ch == ' ') {
|
||||
if ((ptr - enc + 2) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
sprintf(ptr, "%%%02X", ch);
|
||||
ptr += 2;
|
||||
} else {
|
||||
ptr[0] = (char) ch;
|
||||
}
|
||||
}
|
||||
*size = ptr - enc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int url_decode(const char *str, char *dec, ssize_t *size) {
|
||||
char *ptr = dec;
|
||||
char ch, buf[3];
|
||||
memset(dec, 0, *size);
|
||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
||||
if ((ptr - dec) >= *size) {
|
||||
return -1;
|
||||
}
|
||||
ch = str[i];
|
||||
if (ch == '+') {
|
||||
ch = ' ';
|
||||
} else if (ch == '%') {
|
||||
memcpy(buf, str + i + 1, 2);
|
||||
buf[2] = 0;
|
||||
ch = (char) strtol(buf, NULL, 16);
|
||||
i += 2;
|
||||
}
|
||||
ptr[0] = ch;
|
||||
}
|
||||
*size = ptr - dec;
|
||||
return 0;
|
||||
}
|
||||
|
||||
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len) {
|
||||
switch (list->entry_data.type) {
|
||||
case MMDB_DATA_TYPE_MAP:
|
||||
*str_off += sprintf(str + *str_off, "{");
|
||||
break;
|
||||
case MMDB_DATA_TYPE_ARRAY:
|
||||
*str_off += sprintf(str + *str_off, "[");
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UTF8_STRING:
|
||||
*str_off += sprintf(str + *str_off, "\"%.*s\"", list->entry_data.data_size, list->entry_data.utf8_string);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT16:
|
||||
*str_off += sprintf(str + *str_off, "%u", list->entry_data.uint16);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT32:
|
||||
*str_off += sprintf(str + *str_off, "%u", list->entry_data.uint32);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT64:
|
||||
*str_off += sprintf(str + *str_off, "%lu", list->entry_data.uint64);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_UINT128:
|
||||
*str_off += sprintf(str + *str_off, "%llu", list->entry_data.uint128);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_INT32:
|
||||
*str_off += sprintf(str + *str_off, "%i", list->entry_data.uint32);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_BOOLEAN:
|
||||
*str_off += sprintf(str + *str_off, "%s", list->entry_data.boolean ? "true" : "false");
|
||||
break;
|
||||
case MMDB_DATA_TYPE_FLOAT:
|
||||
*str_off += sprintf(str + *str_off, "%f", list->entry_data.float_value);
|
||||
break;
|
||||
case MMDB_DATA_TYPE_DOUBLE:
|
||||
*str_off += sprintf(str + *str_off, "%f", list->entry_data.double_value);
|
||||
break;
|
||||
}
|
||||
if (list->entry_data.type != MMDB_DATA_TYPE_MAP && list->entry_data.type != MMDB_DATA_TYPE_ARRAY) {
|
||||
return list->next;
|
||||
}
|
||||
MMDB_entry_data_list_s *next = list->next;
|
||||
int stat = 0;
|
||||
for (int i = 0; i < list->entry_data.data_size; i++) {
|
||||
next = mmdb_json(next, str, str_off, str_len);
|
||||
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
|
||||
stat = !stat;
|
||||
if (stat) {
|
||||
i--;
|
||||
*str_off += sprintf(str + *str_off, ":");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (i != list->entry_data.data_size - 1) *str_off += sprintf(str + *str_off, ",");
|
||||
}
|
||||
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
|
||||
*str_off += sprintf(str + *str_off, "}");
|
||||
} else {
|
||||
*str_off += sprintf(str + *str_off, "]");
|
||||
}
|
||||
return next;
|
||||
}
|
31
src/utils.h
31
src/utils.h
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* Utilities (header file)
|
||||
* src/utils.h
|
||||
* Lorenz Stechauner, 2020-12-03
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_UTILS_H
|
||||
#define NECRONDA_SERVER_UTILS_H
|
||||
|
||||
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)
|
||||
|
||||
#define out_x(x, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, FUNC, ...) FUNC
|
||||
|
||||
#define print(...) out_x(, ##__VA_ARGS__, out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
|
||||
out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
|
||||
out_2(__VA_ARGS__), out_1(__VA_ARGS__))
|
||||
|
||||
|
||||
char *format_duration(unsigned long micros, char *buf);
|
||||
|
||||
int url_encode(const char *str, char *enc, ssize_t *size);
|
||||
|
||||
int encode_url(const char *str, char *enc, ssize_t *size);
|
||||
|
||||
int url_decode(const char *str, char *dec, ssize_t *size);
|
||||
|
||||
#endif //NECRONDA_SERVER_UTILS_H
|
28
test/mock_socket.c
Normal file
28
test/mock_socket.c
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "mock_socket.h"
|
||||
|
||||
int mock_socket_send_mode;
|
||||
|
||||
static int sockets[256] = {0};
|
||||
static int n_sockets = 0;
|
||||
|
||||
int mock_socket(int domain, int type, int protocol) {
|
||||
printf("SOCKET\n");
|
||||
return (n_sockets++) + 100;
|
||||
}
|
||||
|
||||
ssize_t mock_send(int fd, const void *buf, size_t n, int flags) {
|
||||
printf("SEND\n");
|
||||
if (mock_socket_send_mode == MOCK_SOCKET_MODE_EINTR) {
|
||||
errno = EINTR;
|
||||
return rand() % ((ssize_t) n) - 1;
|
||||
} else if (mock_socket_send_mode == MOCK_SOCKET_MODE_CLOSED) {
|
||||
errno = 0; // TODO
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (ssize_t) n;
|
||||
}
|
20
test/mock_socket.h
Normal file
20
test/mock_socket.h
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
#ifndef SESIMOS_MOCK_SOCKET_H
|
||||
#define SESIMOS_MOCK_SOCKET_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MOCK_SOCKET_MODE_SUCCESS 0
|
||||
#define MOCK_SOCKET_MODE_EINTR 1
|
||||
#define MOCK_SOCKET_MODE_CLOSED 2
|
||||
|
||||
#define socket(args...) mock_socket(args)
|
||||
#define send(args...) mock_send(args)
|
||||
|
||||
extern int mock_socket_send_mode;
|
||||
|
||||
int mock_socket(int domain, int type, int protocol);
|
||||
|
||||
ssize_t mock_send(int fd, const void *buf, size_t n, int flags);
|
||||
|
||||
#endif //SESIMOS_MOCK_SOCKET_H
|
33
test/mock_ssl.c
Normal file
33
test/mock_ssl.c
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
|
||||
int SSL_write(SSL *ssl, const void *buf, int num) {
|
||||
return num;
|
||||
}
|
||||
|
||||
int SSL_read(SSL *ssl, void *buf, int num) {
|
||||
return num;
|
||||
}
|
||||
|
||||
int SSL_peek(SSL *ssl, void *buf, int num) {
|
||||
return num;
|
||||
}
|
||||
|
||||
int SSL_get_error(const SSL *s, int ret_code) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *ERR_reason_error_string(unsigned long e) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int SSL_shutdown(SSL *s) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SSL_free(SSL *ssl) {}
|
||||
|
||||
unsigned long ERR_get_error(void) {
|
||||
return 0;
|
||||
}
|
15
test/test_sock.c
Normal file
15
test/test_sock.c
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
#include <criterion/criterion.h>
|
||||
#include "mock_socket.h"
|
||||
#include "../src/lib/sock.h"
|
||||
|
||||
|
||||
Test(sock, sock_send_1) {
|
||||
int fd = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
sock s;
|
||||
s.enc = 0;
|
||||
s.socket = fd;
|
||||
|
||||
long ret = sock_send(&s, "Hello", 5, 0);
|
||||
cr_assert_eq(ret, 5);
|
||||
}
|
86
test/test_utils.c
Normal file
86
test/test_utils.c
Normal file
@ -0,0 +1,86 @@
|
||||
|
||||
#include <criterion/criterion.h>
|
||||
#include <criterion/parameterized.h>
|
||||
|
||||
#include "../src/lib/utils.h"
|
||||
|
||||
struct url_encode_t {
|
||||
long in_size;
|
||||
char in[256];
|
||||
long exp_size;
|
||||
char exp[256];
|
||||
};
|
||||
|
||||
struct format_duration_t {
|
||||
unsigned long micros;
|
||||
char exp[16];
|
||||
};
|
||||
|
||||
ParameterizedTestParameters(utils, url_encode) {
|
||||
static struct url_encode_t params[] = {
|
||||
{0, "", 0, ""},
|
||||
{9, "Test Text", 11, "Test%20Text"},
|
||||
{21, "Text\0with\0null\0bytes\0", 29, "Text%00with%00null%00bytes%00"},
|
||||
{59, "Text&with+some/strange_symbols-or#something?I%don't|know...", 59, "Text&with+some/strange_symbols-or#something?I%don't|know..."},
|
||||
{33, "Data\x12With\x13Some" "\xFF" "Control" "\xFE" "Characters", 41, "Data%12With%13Some%FFControl%FECharacters"}
|
||||
};
|
||||
return cr_make_param_array(struct url_encode_t, params, sizeof(params) / sizeof(struct url_encode_t));
|
||||
}
|
||||
|
||||
ParameterizedTest(struct url_encode_t *param, utils, url_encode) {
|
||||
char out[256];
|
||||
cr_assert_eq(url_encode(param->in, param->in_size, out, sizeof(out)), param->exp_size);
|
||||
cr_assert_arr_eq(out, param->exp, param->exp_size + 1);
|
||||
}
|
||||
|
||||
Test(utils, url_encode_bytes) {
|
||||
char out[4];
|
||||
char exp[4];
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
unsigned char ch = i;
|
||||
if (ch <= 0x20 || ch >= 0x7F) {
|
||||
cr_assert_eq(url_encode(&ch, 1, out, sizeof(out)), 3);
|
||||
sprintf(exp, "%%%02X", ch);
|
||||
cr_assert_str_eq(out, exp);
|
||||
} else {
|
||||
cr_assert_eq(url_encode(&ch, 1, out, sizeof(out)), 1);
|
||||
sprintf(exp, "%c", ch);
|
||||
cr_assert_str_eq(out, exp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Test(utils, url_encode_invalid) {
|
||||
cr_assert_eq(url_encode("Hello", 5, NULL, 0), 5);
|
||||
}
|
||||
|
||||
ParameterizedTestParameters(utils, format_duration) {
|
||||
static struct format_duration_t params[] = {
|
||||
{0, "0.0 ms"},
|
||||
{1, "0.0 ms"},
|
||||
{90, "0.1 ms"},
|
||||
{100, "0.1 ms"},
|
||||
{110, "0.1 ms"},
|
||||
{900, "0.9 ms"},
|
||||
{1000, "1.0 ms"},
|
||||
{9000, "9.0 ms"},
|
||||
{9899, "9.9 ms"},
|
||||
{9999, "10.0 ms"},
|
||||
{10000, "10 ms"},
|
||||
{11999, "12 ms"},
|
||||
{999999, "1.0 s"},
|
||||
{1000000, "1.0 s"},
|
||||
{3000000, "3.0 s"},
|
||||
{1000000 * 60, "1.0 min"},
|
||||
{1000000 * 60 * 30L - 30000000, "29.5 min"},
|
||||
{1000000 * 60 * 60L, "60.0 min"},
|
||||
{1000000 * 60 * 120L, "120 min"},
|
||||
};
|
||||
return cr_make_param_array(struct format_duration_t, params, sizeof(params) / sizeof(struct format_duration_t));
|
||||
}
|
||||
|
||||
ParameterizedTest(struct format_duration_t *param, utils, format_duration) {
|
||||
char buf[16];
|
||||
cr_assert_str_eq(format_duration(param->micros, buf), param->exp);
|
||||
}
|
Reference in New Issue
Block a user