Compare commits
292 Commits
Author | SHA1 | Date | |
---|---|---|---|
e93c478cc3 | |||
1d0a545610 | |||
fb67f7e9b0 | |||
0dd9a9a843 | |||
6eaf5f5776 | |||
75d36bb5bb | |||
c6da5413d4 | |||
80d7626208 | |||
e97809253a | |||
b26e80f18a | |||
90b20d40d8 | |||
34b860073c | |||
5d6bd07cfd | |||
2a2c1ea442 | |||
fee4cc808a | |||
0232331f99 | |||
62b631c862 | |||
0f526d7b95 | |||
642286a838 | |||
91a8959c8d | |||
197756bf15 | |||
f4697ce0f3 | |||
72c2e24050 | |||
745509cab1 | |||
35d3612d9b | |||
37671546ef | |||
beec199192 | |||
afa0196277 | |||
29a0775bf5 | |||
46d661d5f3 | |||
9ec1c1c3a2 | |||
d6b315c91c | |||
fd2abf9804 | |||
ddb6623651 | |||
ef3e8475fb | |||
0cd63ff5e9 | |||
de3fcf8fc3 | |||
0f40dcb5db | |||
b7c8db01ac | |||
b6c9d7330d | |||
c59977dada | |||
77f0eeda6d | |||
371bff0d07 | |||
2e3146f69a | |||
733b73760c | |||
a9fbd21f80 | |||
52ebad201f | |||
7fe4abd379 | |||
56427e3003 | |||
914aa2d341 | |||
db4bca6f13 | |||
f1ba02756a | |||
0b68c67982 | |||
cb04af739c | |||
cd25120362 | |||
13d6e30d01 | |||
04f13c49af | |||
9aee302f6c | |||
44e3b1332f | |||
808ebdb0a0 | |||
31cd2e7e73 | |||
bd8e71e83d | |||
9aaa28f1ca | |||
73e0cffa78
|
|||
6f0371c46f
|
|||
ad6ffe5425
|
|||
ab7e5cc722
|
|||
40310faa4b
|
|||
7a3adc6ed3
|
|||
bd73061462
|
|||
9ee0e11c86
|
|||
240ed6bc25
|
|||
03dd126ca7
|
|||
17327006db
|
|||
9237c791bb
|
|||
f7a6214dbc
|
|||
8053439212
|
|||
299e726341
|
|||
e721e542f3
|
|||
df7dfb5107
|
|||
5f3ba2b971
|
|||
1e0a7c95da
|
|||
0394c3afb4
|
|||
32ea03079b
|
|||
8b63d2cf32
|
|||
d461ba6f29
|
|||
5b98fd0dab
|
|||
0b1b0bcb56
|
|||
44fc7f2e5c
|
|||
c8b605aa90
|
|||
59b9bfd53e
|
|||
e8655102b9
|
|||
add135b3ad
|
|||
2d250621c8
|
|||
f96dc46ea7
|
|||
5138dafb8e
|
|||
4be5b0d195
|
|||
a84aa591d4
|
|||
d7db1f0be9
|
|||
116f7035ed
|
|||
8abc0b8bfa
|
|||
410d692372
|
|||
f42ac83323
|
|||
7d576b2466
|
|||
5acab8144d
|
|||
4814035b62
|
|||
15f400f376
|
|||
32638fdd91
|
|||
07d6280864
|
|||
820ce8dac9
|
|||
5bd9b3fb33
|
|||
a3dcddf69d
|
|||
487386158d
|
|||
32f00175e4
|
|||
581b4dc5aa
|
|||
611ceabfb2
|
|||
6b295cef95
|
|||
764b754a6f
|
|||
7699583c5f
|
|||
8b92d4ff91
|
|||
e8d8abdc5a
|
|||
15c6b7ddaa
|
|||
e9a065f8aa
|
|||
0730429041
|
|||
af62a3065a
|
|||
c36ad8c113
|
|||
7f7a07c4d2
|
|||
4ff22bd0c6
|
|||
83ca2467de
|
|||
0a1fb977d6
|
|||
1405036cf2
|
|||
5f3cd03a6f
|
|||
819b71f285
|
|||
ceaa384fce
|
|||
99c4eb1c8a
|
|||
fb59b0d8c4
|
|||
946adb54d7
|
|||
b369a1116e
|
|||
950bf19331
|
|||
993cb65724
|
|||
c7be0adc66
|
|||
4782707049
|
|||
3d1451448d
|
|||
1619e01174
|
|||
6112e719e5
|
|||
6f2751f69b
|
|||
925ff2b9e4
|
|||
c67edd4195
|
|||
d8fd552b40
|
|||
ec6457465c
|
|||
490c8a2ae0
|
|||
f87fb74ced
|
|||
7b9844e267
|
|||
308534b883
|
|||
b93d97258c
|
|||
cd80a194dd
|
|||
ffc5c80b65
|
|||
896486b64a
|
|||
c1706edcea
|
|||
f8d3253a9f
|
|||
ec3183e99b
|
|||
27d514ae12
|
|||
e82869e993
|
|||
a54a6af497
|
|||
c13bea5ab4
|
|||
fd1630a554
|
|||
f7f245c931
|
|||
e72cb57b4a
|
|||
1cf9172194
|
|||
f416c2f05c
|
|||
300e3dc0aa
|
|||
26869c0df5
|
|||
c7488b0f59
|
|||
4e6930692e
|
|||
24cb993cb8
|
|||
fc9538fa6d
|
|||
9d8120966d
|
|||
3c5ecacecc
|
|||
606865e5dc
|
|||
b79c9c710b
|
|||
4b4e7bd257
|
|||
8d6b7105f3
|
|||
95e2ddb24c
|
|||
535e1add96
|
|||
51d85cc99f
|
|||
a4c0093fbe
|
|||
6ff6f0c73b
|
|||
555e8f62a1
|
|||
03c2c7b364
|
|||
abb40a659c
|
|||
6eb3b99bfb
|
|||
9cb278eb2a
|
|||
33feabdec0
|
|||
7995a9699e
|
|||
680344fbec
|
|||
e667a79ab2
|
|||
816c8c0bfc
|
|||
1547805e4c
|
|||
96f3225f51
|
|||
9ad1ecf1da
|
|||
28d7cf68df
|
|||
204317f46a
|
|||
a56002a409
|
|||
db966b3a66
|
|||
5d27cf0398
|
|||
7a2acb0e66
|
|||
cff5d558d6
|
|||
665e4ceabc
|
|||
f241913620
|
|||
2969e435d1
|
|||
c4289ac018
|
|||
672745f6df
|
|||
f92c26c350
|
|||
f9b3cc29ab
|
|||
5c72a0cb60
|
|||
cf3cff0746
|
|||
b90ed61e03
|
|||
dfc659dec0
|
|||
096aeae23b
|
|||
cd97eca7d3
|
|||
ce658ac965
|
|||
dd4f768cc4
|
|||
45514f90ca
|
|||
44913c1e0e
|
|||
b244f86c72
|
|||
a3c1ecc0bf
|
|||
3227e615fe
|
|||
3ce72975b8
|
|||
1f20c70772
|
|||
fabb55d94b
|
|||
6d473bfa49
|
|||
8e83d6aa5f
|
|||
b422b37806
|
|||
74c97a512f
|
|||
7653c3117e
|
|||
483b386100
|
|||
bfa9cf4fcd
|
|||
9ac67dbfd3
|
|||
2efe65fc74
|
|||
2937bdaded
|
|||
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
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,8 +1,12 @@
|
|||||||
*
|
*
|
||||||
!src
|
!src
|
||||||
!src/**
|
!src/**
|
||||||
!docs
|
!res
|
||||||
!docs/**
|
!res/**
|
||||||
|
!doc
|
||||||
|
!doc/**
|
||||||
|
!test
|
||||||
|
!test/**
|
||||||
!Makefile
|
!Makefile
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!README.md
|
!*.md
|
||||||
|
146
Makefile
146
Makefile
@ -1,26 +1,134 @@
|
|||||||
|
|
||||||
CC=gcc
|
CC=gcc
|
||||||
CFLAGS=-std=gnu11 -Wall
|
CFLAGS=-std=gnu11 -Wno-unused-but-set-variable -D_DEFAULT_SOURCE -D_GNU_SOURCE -D_BSD_SOURCE -D_SVID_SOURCE -D_POSIX_C_SOURCE=200809L
|
||||||
LIBS=-lssl -lcrypto -lmagic -lz -lmaxminddb -lbrotlienc
|
LDFLAGS=-pthread -lssl -lcrypto -lmagic -lz -lmaxminddb -lbrotlienc
|
||||||
|
|
||||||
DEBIAN_OPTS=-D CACHE_MAGIC_FILE="\"/usr/share/file/magic.mgc\"" -D PHP_FPM_SOCKET="\"/var/run/php/php7.4-fpm.sock\""
|
DEBIAN_OPTS=-D CACHE_MAGIC_FILE="\"/usr/share/file/magic.mgc\"" -D PHP_FPM_SOCKET="\"/var/run/php/php8.2-fpm.sock\""
|
||||||
|
|
||||||
|
.PHONY: all prod debug default debian permit clean test
|
||||||
|
all: prod
|
||||||
|
default: bin bin/lib bin/worker bin/res bin/sesimos
|
||||||
|
|
||||||
|
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/worker:
|
||||||
|
mkdir -p bin/worker
|
||||||
|
|
||||||
|
bin/res:
|
||||||
|
mkdir -p bin/res
|
||||||
|
|
||||||
|
bin/test: test/mock_*.c test/test_*.c \
|
||||||
|
src/lib/utils.c src/lib/sock.c src/lib/list.c src/lib/http.c src/lib/http_static.c src/logger.c src/lib/error.c
|
||||||
|
$(CC) -o $@ $(CFLAGS) $^ -lcriterion
|
||||||
|
|
||||||
|
|
||||||
|
bin/%.o: src/%.c
|
||||||
|
$(CC) -c -o $@ $(CFLAGS) $<
|
||||||
|
|
||||||
|
bin/lib/%.o: src/lib/%.c
|
||||||
|
$(CC) -c -o $@ $(CFLAGS) $<
|
||||||
|
|
||||||
|
bin/worker/%.o: src/worker/%.c
|
||||||
|
$(CC) -c -o $@ $(CFLAGS) $<
|
||||||
|
|
||||||
|
bin/res/%.o: bin/res/%.txt
|
||||||
|
objcopy -I binary --rename-section .data=.rodata -O elf64-x86-64 $^ $@
|
||||||
|
|
||||||
|
bin/res/%.txt: res/%.*
|
||||||
|
cp $^ $@
|
||||||
|
/bin/echo -ne "\x00" >> $@
|
||||||
|
|
||||||
|
bin/sesimos: bin/server.o bin/logger.o bin/cache_handler.o bin/async.o bin/workers.o \
|
||||||
|
bin/worker/request_handler.o bin/worker/tcp_acceptor.o \
|
||||||
|
bin/worker/fastcgi_handler.o bin/worker/local_handler.o bin/worker/proxy_handler.o \
|
||||||
|
bin/worker/proxy_peer_handler.o \
|
||||||
|
bin/worker/ws_frame_handler.o bin/worker/chunk_handler.o bin/worker/fastcgi_frame_handler.o \
|
||||||
|
bin/lib/http_static.o bin/res/default.o bin/res/proxy.o bin/res/style.o \
|
||||||
|
bin/res/icon_error.o bin/res/icon_info.o bin/res/icon_success.o bin/res/icon_warning.o \
|
||||||
|
bin/res/globe.o \
|
||||||
|
bin/lib/compress.o bin/lib/config.o bin/lib/fastcgi.o bin/lib/geoip.o bin/lib/error.o \
|
||||||
|
bin/lib/http.o bin/lib/proxy.o bin/lib/sock.o bin/lib/uri.o \
|
||||||
|
bin/lib/utils.o bin/lib/websocket.o bin/lib/mpmc.o bin/lib/list.o
|
||||||
|
$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
|
||||||
|
|
||||||
|
|
||||||
|
bin/server.o: src/server.h src/defs.h src/cache_handler.h src/lib/config.h src/lib/sock.h \
|
||||||
|
src/lib/proxy.h src/lib/geoip.h src/lib/utils.h src/logger.h
|
||||||
|
|
||||||
|
bin/logger.o: src/logger.h
|
||||||
|
|
||||||
|
bin/cache_handler.o: src/cache_handler.h src/lib/utils.h src/lib/uri.h src/lib/compress.h src/logger.h
|
||||||
|
|
||||||
|
bin/async.o: src/async.h src/logger.h
|
||||||
|
|
||||||
|
bin/workers.o: src/workers.h src/lib/mpmc.h src/worker/func.h
|
||||||
|
|
||||||
|
bin/worker/request_handler.o: src/worker/func.h
|
||||||
|
|
||||||
|
bin/worker/tcp_acceptor.o: src/worker/func.h
|
||||||
|
|
||||||
|
bin/worker/fastcgi_handler.o: src/worker/func.h
|
||||||
|
|
||||||
|
bin/worker/local_handler.o: src/worker/func.h
|
||||||
|
|
||||||
|
bin/worker/proxy_handler.o: src/worker/func.h
|
||||||
|
|
||||||
|
bin/worker/proxy_peer_handler.o: src/worker/func.h
|
||||||
|
|
||||||
|
bin/worker/ws_frame_handler.o: src/worker/func.h
|
||||||
|
|
||||||
|
bin/worker/fastcgi_frame_handler.o: src/worker/func.h
|
||||||
|
|
||||||
|
bin/worker/chunk_handler.o: src/worker/func.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 src/logger.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 src/logger.h
|
||||||
|
|
||||||
|
bin/lib/error.o: src/lib/error.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 src/logger.h
|
||||||
|
|
||||||
|
bin/lib/list.o: src/lib/list.h
|
||||||
|
|
||||||
|
bin/lib/mpmc.o: src/lib/mpmc.h src/logger.h
|
||||||
|
|
||||||
|
bin/lib/proxy.o: src/lib/proxy.h src/defs.h src/server.h src/lib/compress.h src/logger.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 src/logger.h
|
||||||
|
|
||||||
packages:
|
|
||||||
@echo "Installing packages..."
|
|
||||||
sudo apt install gcc php-fpm libmagic-dev libssl-dev libmaxminddb-dev
|
|
||||||
@echo "Finished downloading!"
|
|
||||||
|
|
||||||
permit:
|
permit:
|
||||||
sudo setcap 'cap_net_bind_service=+ep' "$(shell pwd)/bin/necronda-server"
|
sudo setcap 'cap_net_bind_service=+ep' "$(shell pwd)/bin/sesimos"
|
||||||
|
|
||||||
compile:
|
clean:
|
||||||
@mkdir -p bin
|
rm -rf bin/*
|
||||||
$(CC) src/lib/*.c -o bin/libnecrondaserver.so --shared -fPIC $(CFLAGS) $(LIBS)
|
|
||||||
$(CC) src/necronda-server.c -o bin/necronda-server $(CFLAGS) $(LIBS) \
|
|
||||||
-Lbin -lnecrondaserver -Wl,-rpath=$(shell pwd)/bin
|
|
||||||
|
|
||||||
compile-prod:
|
|
||||||
@mkdir -p bin
|
|
||||||
$(CC) src/lib/*.c -o bin/libnecrondaserver.so --shared -fPIC $(CFLAGS) $(LIBS) $(DEBIAN_OPTS) -O3
|
|
||||||
$(CC) src/necronda-server.c -o bin/necronda-server $(CFLAGS) $(LIBS) $(DEBIAN_OPTS) -O3 \
|
|
||||||
-Lbin -lnecrondaserver -Wl,-rpath=$(shell pwd)/bin
|
|
||||||
|
14
README.md
14
README.md
@ -1,15 +1,18 @@
|
|||||||
|
|
||||||
Necronda web server
|
Sesimos – Secure, simple, modern web server
|
||||||
===================
|
===========================================
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Full IPv4 and IPv6 support
|
* Full IPv4 and IPv6 support
|
||||||
* TLS Server Name Inspection (SNI)
|
* TLS Server Name Inspection (SNI)
|
||||||
* Serving local files via HTTP and HTTPS
|
* Serving local files via HTTP and HTTPS
|
||||||
* File compression ([gzip](https://www.gzip.org/), [Brotli](https://www.brotli.org/)) and disk cache for compressed files
|
* 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
|
* Reverse proxy for other HTTP and HTTPS servers
|
||||||
|
* Transparent WebSocket reverse proxy
|
||||||
* FastCGI support (e.g. [PHP-FPM](https://php-fpm.org/))
|
* 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)
|
* Support for [MaxMind's GeoIP Database](https://www.maxmind.com/en/geoip2-services-and-databases)
|
||||||
* Optional DNS reverse lookup for connecting hosts
|
* Optional DNS reverse lookup for connecting hosts
|
||||||
* Automatic URL rewrite (e.g. `/index.html` -> `/`, `/test.php` -> `/test`)
|
* Automatic URL rewrite (e.g. `/index.html` -> `/`, `/test.php` -> `/test`)
|
||||||
@ -18,18 +21,17 @@ Necronda web server
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
See [docs/example.conf](docs/example.conf) for more details.
|
See [doc/example.conf](doc/example.conf) for more details.
|
||||||
|
|
||||||
|
|
||||||
### Global directives
|
### Global directives
|
||||||
|
|
||||||
* `geoip_dir` (optional) - path to a directory containing GeoIP databases
|
* `geoip_dir` (optional) - path to a directory containing GeoIP databases
|
||||||
* `dns_server` (optional) - address of a DNS server
|
|
||||||
|
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
* `[cert <cert-name]` - begins section for a certificate
|
* `[cert <cert-name>]` - begins section for a certificate
|
||||||
* `certificate` - path to SSL certificate (or certificate chain)
|
* `certificate` - path to SSL certificate (or certificate chain)
|
||||||
* `private_key` - path to SSL private key
|
* `private_key` - path to SSL private key
|
||||||
* `[host <host>]` - begins section for the virtual host `<host>`
|
* `[host <host>]` - begins section for the virtual host `<host>`
|
||||||
|
22
architecture.md
Normal file
22
architecture.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
# Architecture
|
||||||
|
|
||||||
|
* logger (1)
|
||||||
|
* listener (2) - 80, 443
|
||||||
|
* cache_handler (1)
|
||||||
|
* connection_initializer
|
||||||
|
* request_handler
|
||||||
|
* local_handler
|
||||||
|
* proxy_handler
|
||||||
|
* ws_handler
|
||||||
|
* fastcgi_handler
|
||||||
|
|
||||||
|
|
||||||
|
* -> logger
|
||||||
|
* main -> listener
|
||||||
|
* listener -> connection_handler
|
||||||
|
* connection_initializer -> request_handler
|
||||||
|
* request_handler -> local_handler -> request_handler
|
||||||
|
* local_handler -> fastcgi_handler -> request_handler
|
||||||
|
* request_handler -> rp_handler -> request_handler
|
||||||
|
* proxy_handler -> ws_handler -> request_handler
|
@ -1,19 +1,24 @@
|
|||||||
|
|
||||||
certificate /var/cert/cert.pem
|
|
||||||
private_key /var/cert/cert.key
|
|
||||||
#geoip_dir /var/dir
|
#geoip_dir /var/dir
|
||||||
#dns_server 192.168.0.1
|
#dns_server 192.168.0.1
|
||||||
|
|
||||||
[localhost]
|
[cert cert1]
|
||||||
|
certificate /var/cert/cert.pem
|
||||||
|
private_key /var/cert/cert.key
|
||||||
|
|
||||||
|
[host localhost]
|
||||||
webroot /var/www/localhost
|
webroot /var/www/localhost
|
||||||
dir_mode forbidden
|
dir_mode forbidden
|
||||||
|
cert cert1
|
||||||
|
|
||||||
[me.local]
|
[host me.local]
|
||||||
hostname www.example.com
|
hostname www.example.com
|
||||||
port 80
|
port 80
|
||||||
|
cert cert1
|
||||||
http
|
http
|
||||||
|
|
||||||
[secure.local]
|
[host secure.local]
|
||||||
hostname www.example.com
|
hostname www.example.com
|
||||||
port 443
|
port 443
|
||||||
|
cert cert1
|
||||||
https
|
https
|
22
res/default.html
Normal file
22
res/default.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>%1$i %2$s - %7$s</title>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="theme-color" content="%6$s"/>
|
||||||
|
<meta name="color-scheme" content="light dark"/>
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/.sesimos/res/style.css"/>
|
||||||
|
<link rel="alternate icon" type="image/svg+xml" sizes="any" href="%5$s"/>
|
||||||
|
<style>html{--color:var(--%4$s);}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
%3$s%9$s <div class="footer"><a href="https://%7$s/">%7$s</a> - %10$s</div>
|
||||||
|
</section>
|
||||||
|
%8$s </main>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
res/globe.svg
Normal file
3
res/globe.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2,32 a30,30,0,1,0,60,0 a30,30,0,1,0,-60,0 L62,32 M6,16 L58,16 M6,48 L58,48 M32,2 L32,62 a15,30,0,1,0,0,-60 a15,30,0,1,0,0,60 Z" stroke="#008000" stroke-width="2" fill="#00000000"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 266 B |
3
res/icon_error.svg
Normal file
3
res/icon_error.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<text x="4" y="12" fill="#C00000" style="font-family:'Arial',sans-serif">:(</text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 158 B |
3
res/icon_info.svg
Normal file
3
res/icon_info.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<text x="4" y="12" fill="#606060" style="font-family:'Arial',sans-serif">:)</text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 158 B |
3
res/icon_success.svg
Normal file
3
res/icon_success.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<text x="4" y="12" fill="#008000" style="font-family:'Arial',sans-serif">:)</text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 158 B |
3
res/icon_warning.svg
Normal file
3
res/icon_warning.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<text x="4" y="12" fill="#E0C000" style="font-family:'Arial',sans-serif">:)</text>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 158 B |
31
res/proxy.html
Normal file
31
res/proxy.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<section class="error-ctx">
|
||||||
|
<div class="box%1$s">
|
||||||
|
<div class="content">
|
||||||
|
<span>Client</span>
|
||||||
|
<img src="/.sesimos/res/globe.svg"/>
|
||||||
|
<span>Your Browser</span>
|
||||||
|
</div>
|
||||||
|
<div class="arrow request%2$s"></div>
|
||||||
|
</div>
|
||||||
|
<div class="border%8$s"></div>
|
||||||
|
<div class="box%3$s">
|
||||||
|
<div class="content">
|
||||||
|
<span>Reverse Proxy</span>
|
||||||
|
<h3>%10$03i</h3>
|
||||||
|
<h4>%11$s</h4>
|
||||||
|
<span>%15$s</span>
|
||||||
|
</div>
|
||||||
|
<div class="arrow request%4$s"></div>
|
||||||
|
<div class="arrow response%5$s"></div>
|
||||||
|
</div>
|
||||||
|
<div class="border%9$s"></div>
|
||||||
|
<div class="box%6$s">
|
||||||
|
<div class="content">
|
||||||
|
<span>Server</span>
|
||||||
|
<h3>%12$s</h3>
|
||||||
|
<h4>%13$s</h4>
|
||||||
|
<span>%14$s</span>
|
||||||
|
</div>
|
||||||
|
<div class="arrow response%7$s"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
66
res/style.css
Normal file
66
res/style.css
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
html{
|
||||||
|
font-family:"Arial",sans-serif;
|
||||||
|
--error:#C00000;
|
||||||
|
--warning:#E0C000;
|
||||||
|
--success:#008000;
|
||||||
|
--info:#606060;
|
||||||
|
--soft:#808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{background-color:#F0F0F0;margin:0;}
|
||||||
|
main{max-width:650px;margin:2em auto;}
|
||||||
|
section{margin:2em 1em;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em;}
|
||||||
|
h1,h2,h3,h4,h5,h6{text-align:center;color:var(--color);font-weight:normal;}
|
||||||
|
h1{font-size:3em;margin:0.125em 0;}
|
||||||
|
h2{font-size:1.5em;margin:0.25em 0 1em 0;}
|
||||||
|
p{text-align:center;font-size:0.875em;}
|
||||||
|
div.footer{color:var(--soft);font-size:0.75em;text-align:center;margin:2em 0 0.5em 0;}
|
||||||
|
div.footer a{color:var(--soft);}
|
||||||
|
ul,ol{width:fit-content;margin:auto;}
|
||||||
|
pre{width:fit-content;margin:2em auto 0 auto;}
|
||||||
|
|
||||||
|
section.error-ctx{display:flex;padding:0;border:none;}
|
||||||
|
div.box{flex:100% 1 1;border:1px solid var(--info);color:var(--info);position:relative;padding:1em;box-sizing:border-box;text-align:center;}
|
||||||
|
div.box.error{border-color:var(--error);color:var(--error);}
|
||||||
|
div.box.success{border-color:var(--success);color:var(--success);}
|
||||||
|
div.arrow{position:absolute;height:20px;width:30px;z-index:10;background-repeat:no-repeat;background-size:contain;}
|
||||||
|
div.arrow.response{left:-17.5px;bottom:calc(33.3333% - 10px);}
|
||||||
|
div.arrow.request{right:-17.5px;top:calc(33.3333% - 10px);}
|
||||||
|
div.border{flex:1px 0 0;background-color:var(--info);}
|
||||||
|
div.border.error{background-color:var(--error);}
|
||||||
|
div.border.success{background-color:var(--success);}
|
||||||
|
div.content>span{display:block;color:var(--soft);font-size:0.75em;}
|
||||||
|
div.content>img{height:3.75rem;margin:0.75rem auto;display:block;}
|
||||||
|
h3{font-size:2.25em;margin:0.75rem 0 0 0;color:unset;height:2.5rem;}
|
||||||
|
h4{font-size:1em;margin:0 0 0.75rem 0;color:unset;height:1.25rem;}
|
||||||
|
|
||||||
|
div.arrow.request.success{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiMwMDgwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}
|
||||||
|
div.arrow.request.error{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiNDMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}
|
||||||
|
div.arrow.response.success{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzAwODAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}
|
||||||
|
div.arrow.response.error{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iI0MwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}
|
||||||
|
|
||||||
|
@media(prefers-color-scheme:dark){
|
||||||
|
html{color:#FFFFFF;--soft:#404040;}
|
||||||
|
body{background-color:#101010;}
|
||||||
|
section{background-color:#181818;}
|
||||||
|
|
||||||
|
div.arrow.request.success{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjMTgxODE4IiBzdHJva2U9IiMwMDgwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}
|
||||||
|
div.arrow.request.error{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjMTgxODE4IiBzdHJva2U9IiNDMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}
|
||||||
|
div.arrow.response.success{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiMxODE4MTgiIHN0cm9rZT0iIzAwODAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}
|
||||||
|
div.arrow.response.error{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiMxODE4MTgiIHN0cm9rZT0iI0MwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(min-width:650px){
|
||||||
|
div.box:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px;border-right:none;}
|
||||||
|
div.box:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px;border-left:none;}
|
||||||
|
div.box:not(:last-child):not(:first-child){border-left:none;border-right:none;}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width:650px){
|
||||||
|
section.error-ctx{flex-direction:column;height:unset;}
|
||||||
|
div.box:first-child{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom:none;padding-top:1em;}
|
||||||
|
div.box:last-child{border-bottom-right-radius:4px;border-bottom-left-radius:4px;border-top:none;padding-bottom:1em;}
|
||||||
|
div.box:not(:last-child):not(:first-child){border-top:none;border-bottom:none;}
|
||||||
|
div.arrow.response{transform:rotate(90deg);top:-10px;left:calc(33.3333% - 22.5px);right:unset;}
|
||||||
|
div.arrow.request{transform:rotate(90deg);bottom:-10px;right:calc(33.3333% - 22.5px);top:unset;}
|
||||||
|
}
|
407
src/async.c
Normal file
407
src/async.c
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
/**
|
||||||
|
* Sesimos - secure, simple, modern web server
|
||||||
|
* @brief Async handler
|
||||||
|
* @file src/async.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-28
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "async.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "lib/list.h"
|
||||||
|
#include "lib/utils.h"
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
#define ASYNC_MAX_EVENTS 16
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int fd;
|
||||||
|
sock *socket;
|
||||||
|
async_evt_t events;
|
||||||
|
int flags;
|
||||||
|
void *arg;
|
||||||
|
void (*cb)(void *);
|
||||||
|
void (*to_cb)(void *);
|
||||||
|
void (*err_cb)(void *);
|
||||||
|
} evt_listen_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int n;
|
||||||
|
evt_listen_t *q[ASYNC_MAX_EVENTS];
|
||||||
|
} listen_queue_t;
|
||||||
|
|
||||||
|
static volatile listen_queue_t listen1, listen2, *listen_q = &listen1;
|
||||||
|
static volatile sig_atomic_t alive = 1;
|
||||||
|
static pthread_t thread = -1;
|
||||||
|
static sem_t lock;
|
||||||
|
static int epoll_fd;
|
||||||
|
|
||||||
|
static short async_a2p(async_evt_t events) {
|
||||||
|
short ret = 0;
|
||||||
|
if (events & ASYNC_IN) ret |= POLLIN;
|
||||||
|
if (events & ASYNC_PRI) ret |= POLLPRI;
|
||||||
|
if (events & ASYNC_OUT) ret |= POLLOUT;
|
||||||
|
if (events & ASYNC_ERR) ret |= POLLERR;
|
||||||
|
if (events & ASYNC_HUP) ret |= POLLHUP;
|
||||||
|
if (events & ASYNC_RDNORM) ret |= POLLRDNORM;
|
||||||
|
if (events & ASYNC_RDBAND) ret |= POLLRDBAND;
|
||||||
|
if (events & ASYNC_WRNORM) ret |= POLLWRNORM;
|
||||||
|
if (events & ASYNC_WRBAND) ret |= POLLWRBAND;
|
||||||
|
if (events & ASYNC_MSG) ret |= POLLMSG;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int async_a2e(async_evt_t events) {
|
||||||
|
unsigned int ret = 0;
|
||||||
|
if (events & ASYNC_IN) ret |= EPOLLIN;
|
||||||
|
if (events & ASYNC_PRI) ret |= EPOLLPRI;
|
||||||
|
if (events & ASYNC_OUT) ret |= EPOLLOUT;
|
||||||
|
if (events & ASYNC_ERR) ret |= EPOLLERR;
|
||||||
|
if (events & ASYNC_HUP) ret |= EPOLLHUP;
|
||||||
|
if (events & ASYNC_RDNORM) ret |= EPOLLRDNORM;
|
||||||
|
if (events & ASYNC_RDBAND) ret |= EPOLLRDBAND;
|
||||||
|
if (events & ASYNC_WRNORM) ret |= EPOLLWRNORM;
|
||||||
|
if (events & ASYNC_WRBAND) ret |= EPOLLWRBAND;
|
||||||
|
if (events & ASYNC_MSG) ret |= EPOLLMSG;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async_evt_t async_p2a(short events) {
|
||||||
|
async_evt_t ret = 0;
|
||||||
|
if (events & POLLIN) ret |= ASYNC_IN;
|
||||||
|
if (events & POLLPRI) ret |= ASYNC_PRI;
|
||||||
|
if (events & POLLOUT) ret |= ASYNC_OUT;
|
||||||
|
if (events & POLLERR) ret |= ASYNC_ERR;
|
||||||
|
if (events & POLLHUP) ret |= ASYNC_HUP;
|
||||||
|
if (events & POLLRDNORM) ret |= ASYNC_RDNORM;
|
||||||
|
if (events & POLLRDBAND) ret |= ASYNC_RDBAND;
|
||||||
|
if (events & POLLWRNORM) ret |= ASYNC_WRNORM;
|
||||||
|
if (events & POLLWRBAND) ret |= ASYNC_WRBAND;
|
||||||
|
if (events & POLLMSG) ret |= ASYNC_MSG;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async_evt_t async_e2a(unsigned int events) {
|
||||||
|
async_evt_t ret = 0;
|
||||||
|
if (events & EPOLLIN) ret |= ASYNC_IN;
|
||||||
|
if (events & EPOLLPRI) ret |= ASYNC_PRI;
|
||||||
|
if (events & EPOLLOUT) ret |= ASYNC_OUT;
|
||||||
|
if (events & EPOLLERR) ret |= ASYNC_ERR;
|
||||||
|
if (events & EPOLLHUP) ret |= ASYNC_HUP;
|
||||||
|
if (events & EPOLLRDNORM) ret |= ASYNC_RDNORM;
|
||||||
|
if (events & EPOLLRDBAND) ret |= ASYNC_RDBAND;
|
||||||
|
if (events & EPOLLWRNORM) ret |= ASYNC_WRNORM;
|
||||||
|
if (events & EPOLLWRBAND) ret |= ASYNC_WRBAND;
|
||||||
|
if (events & EPOLLMSG) ret |= ASYNC_MSG;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static short async_e2p(unsigned int events) {
|
||||||
|
return async_a2p(async_e2a(events));
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int async_p2e(short events) {
|
||||||
|
return async_a2e(async_p2a(events));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int async_add_to_queue(evt_listen_t *evt) {
|
||||||
|
while (sem_wait(&lock) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
evt_listen_t *ptr = malloc(sizeof(evt_listen_t));
|
||||||
|
if (ptr == NULL) {
|
||||||
|
sem_post(&lock);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(ptr, evt, sizeof(*evt));
|
||||||
|
listen_q->q[listen_q->n++] = ptr;
|
||||||
|
|
||||||
|
sem_post(&lock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int async_exec(evt_listen_t *evt, async_evt_t r_events) {
|
||||||
|
int ret, e = errno;
|
||||||
|
if (r_events & evt->events) {
|
||||||
|
// specified event(s) occurred
|
||||||
|
if (!(evt->flags & ASYNC_IGNORE_PENDING) && evt->socket && !sock_has_pending(evt->socket, 0)) {
|
||||||
|
evt->err_cb(evt->arg);
|
||||||
|
ret = 0;
|
||||||
|
} else {
|
||||||
|
evt->cb(evt->arg);
|
||||||
|
ret = (evt->flags & ASYNC_KEEP) ? 1 : 0;
|
||||||
|
}
|
||||||
|
} else if (r_events & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||||
|
// error occurred
|
||||||
|
evt->err_cb(evt->arg);
|
||||||
|
ret = 0;
|
||||||
|
} else {
|
||||||
|
// no event occurred
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = e;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int async_check(evt_listen_t *evt) {
|
||||||
|
struct pollfd fds[1] = {{
|
||||||
|
.fd = evt->fd,
|
||||||
|
.events = async_a2p(evt->events)
|
||||||
|
}};
|
||||||
|
|
||||||
|
// check, if fd is already ready
|
||||||
|
if (evt->events & ASYNC_IN && evt->socket && evt->socket->enc && SSL_pending(evt->socket->ssl) > 0) {
|
||||||
|
// ssl layer already ready
|
||||||
|
if (async_exec(evt, ASYNC_IN) == 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
switch (poll(fds, 1, 0)) {
|
||||||
|
case 1:
|
||||||
|
// fd already ready
|
||||||
|
if (async_exec(evt, async_p2a(fds[0].revents)) == 0)
|
||||||
|
return 1;
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
error("Unable to poll");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int async_add(evt_listen_t *evt) {
|
||||||
|
if (async_check(evt) == 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int ret = async_add_to_queue(evt);
|
||||||
|
if (ret == 0 && thread != -1)
|
||||||
|
pthread_kill(thread, SIGUSR1);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int async_fd(int fd, async_evt_t events, int flags, void *arg, void cb(void *), void to_cb(void *), void err_cb(void *)) {
|
||||||
|
evt_listen_t evt = {
|
||||||
|
.fd = fd,
|
||||||
|
.socket = NULL,
|
||||||
|
.events = events,
|
||||||
|
.flags = flags,
|
||||||
|
.arg = arg,
|
||||||
|
.cb = cb,
|
||||||
|
.to_cb = to_cb,
|
||||||
|
.err_cb = err_cb,
|
||||||
|
};
|
||||||
|
return async_add(&evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
int async(sock *s, async_evt_t events, int flags, void *arg, void cb(void *), void to_cb(void *), void err_cb(void *)) {
|
||||||
|
evt_listen_t evt = {
|
||||||
|
.fd = s->socket,
|
||||||
|
.socket = s,
|
||||||
|
.events = events,
|
||||||
|
.flags = flags,
|
||||||
|
.arg = arg,
|
||||||
|
.cb = cb,
|
||||||
|
.to_cb = to_cb,
|
||||||
|
.err_cb = err_cb,
|
||||||
|
};
|
||||||
|
return async_add(&evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
int async_init(void) {
|
||||||
|
if (sem_init(&lock, 0, 1) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen1.n = 0;
|
||||||
|
listen2.n = 0;
|
||||||
|
|
||||||
|
if ((epoll_fd = epoll_create1(0)) == -1) {
|
||||||
|
async_free();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void async_free(void) {
|
||||||
|
int e = errno;
|
||||||
|
sem_destroy(&lock);
|
||||||
|
close(epoll_fd);
|
||||||
|
errno = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
void async_thread(void) {
|
||||||
|
struct epoll_event ev, events[ASYNC_MAX_EVENTS];
|
||||||
|
int num_fds;
|
||||||
|
long ts, min_ts, cur_ts;
|
||||||
|
volatile listen_queue_t *l;
|
||||||
|
evt_listen_t **local;
|
||||||
|
|
||||||
|
if ((local = list_create(sizeof(evt_listen_t *), 16)) == NULL) {
|
||||||
|
critical("Unable to create async local list");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread = pthread_self();
|
||||||
|
|
||||||
|
// main event loop
|
||||||
|
while (alive) {
|
||||||
|
// swap listen queue
|
||||||
|
while (sem_wait(&lock) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
critical("Unable to lock async queue");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l = listen_q;
|
||||||
|
listen_q = (listen_q == &listen1) ? &listen2 : &listen1;
|
||||||
|
sem_post(&lock);
|
||||||
|
|
||||||
|
// fill local list and epoll instance with previously added queue entries
|
||||||
|
for (int i = 0; i < l->n; i++) {
|
||||||
|
evt_listen_t *evt = l->q[i];
|
||||||
|
local = list_append(local, &evt);
|
||||||
|
if (local == NULL) {
|
||||||
|
critical("Unable to resize async local list");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.events = async_a2e(evt->events);
|
||||||
|
ev.data.ptr = evt;
|
||||||
|
|
||||||
|
while (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, evt->fd, &ev) == -1) {
|
||||||
|
if (errno == EEXIST) {
|
||||||
|
// fd already exists, delete old one
|
||||||
|
warning("Unable to add file descriptor to epoll instance");
|
||||||
|
errno = 0;
|
||||||
|
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, evt->fd, NULL) != -1)
|
||||||
|
continue;
|
||||||
|
} else if (errno == EBADF) {
|
||||||
|
// fd probably already closed
|
||||||
|
warning("Unable to add file descriptor to epoll instance");
|
||||||
|
errno = 0;
|
||||||
|
local = list_delete(local, &evt);
|
||||||
|
if (local == NULL) {
|
||||||
|
critical("Unable to resize async local list");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
critical("Unable to add file descriptor to epoll instance");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// reset size of queue
|
||||||
|
l->n = 0;
|
||||||
|
|
||||||
|
// TODO timeout calculation = O(n)
|
||||||
|
// calculate wait timeout
|
||||||
|
min_ts = -1000, cur_ts = clock_micros();
|
||||||
|
for (int i = 0; i < list_size(local); i++) {
|
||||||
|
evt_listen_t *evt = local[i];
|
||||||
|
if (!evt->socket || evt->socket->timeout_us < 0) continue;
|
||||||
|
|
||||||
|
ts = evt->socket->ts_last + evt->socket->timeout_us - cur_ts;
|
||||||
|
if (min_ts == -1000 || ts < min_ts) min_ts = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// epoll is used in level-triggered mode, so buffers are taken into account
|
||||||
|
if ((num_fds = epoll_wait(epoll_fd, events, ASYNC_MAX_EVENTS, (int) (min_ts / 1000))) == -1) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
// interrupt
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
// other error
|
||||||
|
critical("Unable to poll for events");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < num_fds; i++) {
|
||||||
|
evt_listen_t *evt = events[i].data.ptr;
|
||||||
|
if (!list_contains(local, &evt)) continue;
|
||||||
|
|
||||||
|
if (async_exec(evt, async_e2a(events[i].events)) == 0) {
|
||||||
|
logger_set_prefix("");
|
||||||
|
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, evt->fd, NULL) == -1) {
|
||||||
|
if (errno == EBADF || errno == ENOENT) {
|
||||||
|
// already closed fd or not found, do not die
|
||||||
|
warning("Unable to remove file descriptor from epoll instance");
|
||||||
|
errno = 0;
|
||||||
|
} else {
|
||||||
|
critical("Unable to remove file descriptor from epoll instance");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local = list_delete(local, &evt);
|
||||||
|
if (local == NULL) {
|
||||||
|
critical("Unable to resize async local list");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(evt);
|
||||||
|
}
|
||||||
|
logger_set_prefix("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// check, if some socket ran into a timeout
|
||||||
|
cur_ts = clock_micros();
|
||||||
|
for (int i = 0; i < list_size(local); i++) {
|
||||||
|
evt_listen_t *evt = local[i];
|
||||||
|
if (!evt->socket) continue;
|
||||||
|
|
||||||
|
if (evt->socket->timeout_us >= 0 && (cur_ts - evt->socket->ts_last) >= evt->socket->timeout_us) {
|
||||||
|
evt->to_cb(evt->arg);
|
||||||
|
|
||||||
|
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, evt->fd, NULL) == -1) {
|
||||||
|
if (errno == EBADF || errno == ENOENT) {
|
||||||
|
// already closed fd or not found, do not die
|
||||||
|
warning("Unable to remove file descriptor from epoll instance");
|
||||||
|
errno = 0;
|
||||||
|
} else {
|
||||||
|
critical("Unable to remove file descriptor from epoll instance");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local = list_remove(local, i--);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger_set_prefix("");
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
for (int i = 0; i < list_size(local); i++) {
|
||||||
|
free(local[i]);
|
||||||
|
}
|
||||||
|
list_free(local);
|
||||||
|
}
|
||||||
|
|
||||||
|
void async_stop(void) {
|
||||||
|
alive = 0;
|
||||||
|
}
|
45
src/async.h
Normal file
45
src/async.h
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Sesimos - secure, simple, modern web server
|
||||||
|
* @brief Async handler (header file)
|
||||||
|
* @file src/async.h
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-28
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SESIMOS_ASYNC_H
|
||||||
|
#define SESIMOS_ASYNC_H
|
||||||
|
|
||||||
|
#include "lib/sock.h"
|
||||||
|
|
||||||
|
#define ASYNC_KEEP 1
|
||||||
|
#define ASYNC_IGNORE_PENDING 2
|
||||||
|
|
||||||
|
#define ASYNC_IN 0x001
|
||||||
|
#define ASYNC_PRI 0x002
|
||||||
|
#define ASYNC_OUT 0x004
|
||||||
|
#define ASYNC_ERR 0x008
|
||||||
|
#define ASYNC_HUP 0x010
|
||||||
|
#define ASYNC_RDNORM 0x040
|
||||||
|
#define ASYNC_RDBAND 0x080
|
||||||
|
#define ASYNC_WRNORM 0x100
|
||||||
|
#define ASYNC_WRBAND 0x200
|
||||||
|
#define ASYNC_MSG 0x400
|
||||||
|
|
||||||
|
#define ASYNC_WAIT_READ ASYNC_IN
|
||||||
|
#define ASYNC_WAIT_WRITE ASYNC_OUT
|
||||||
|
|
||||||
|
typedef unsigned int async_evt_t;
|
||||||
|
|
||||||
|
int async(sock *s, async_evt_t events, int flags, void *arg, void cb(void *), void to_cb(void *), void err_cb(void *));
|
||||||
|
|
||||||
|
int async_fd(int fd, async_evt_t events, int flags, void *arg, void cb(void *), void to_cb(void *), void err_cb(void *));
|
||||||
|
|
||||||
|
int async_init(void);
|
||||||
|
|
||||||
|
void async_free(void);
|
||||||
|
|
||||||
|
void async_thread(void);
|
||||||
|
|
||||||
|
void async_stop(void);
|
||||||
|
|
||||||
|
#endif //SESIMOS_ASYNC_H
|
455
src/cache_handler.c
Normal file
455
src/cache_handler.c
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief File cache implementation
|
||||||
|
* @file src/cache_handler.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-19
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "cache_handler.h"
|
||||||
|
#include "lib/utils.h"
|
||||||
|
#include "lib/compress.h"
|
||||||
|
#include "lib/config.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <magic.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#define CACHE_BUF_SIZE 16
|
||||||
|
|
||||||
|
static magic_t magic;
|
||||||
|
static pthread_t thread;
|
||||||
|
static sem_t sem_free, sem_used, sem_lock, sem_cache, sem_magic;
|
||||||
|
volatile sig_atomic_t alive = 1;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int rd;
|
||||||
|
int wr;
|
||||||
|
cache_entry_t *msgs[CACHE_BUF_SIZE];
|
||||||
|
} buf_t;
|
||||||
|
|
||||||
|
static buf_t buffer;
|
||||||
|
|
||||||
|
static int magic_init(void) {
|
||||||
|
if ((magic = magic_open(MAGIC_MIME)) == NULL) {
|
||||||
|
critical("Unable to open magic cookie");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (magic_load(magic, CACHE_MAGIC_FILE) != 0) {
|
||||||
|
critical("Unable to load magic cookie: %s", magic_error(magic));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void magic_mime_type(const char *restrict filename, char *buf) {
|
||||||
|
while (sem_wait(&sem_magic) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
critical("Unable to lock magic semaphore");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
magic_setflags(magic, MAGIC_MIME_TYPE);
|
||||||
|
const char *type = magic_file(magic, filename);
|
||||||
|
|
||||||
|
if (strstarts(type, "text/")) {
|
||||||
|
if (strends(filename, ".css")) {
|
||||||
|
strcpy(buf, "text/css");
|
||||||
|
sem_post(&sem_magic);
|
||||||
|
return;
|
||||||
|
} else if (strends(filename, ".js")) {
|
||||||
|
strcpy(buf, "application/javascript");
|
||||||
|
sem_post(&sem_magic);
|
||||||
|
return;
|
||||||
|
} else if (strends(filename, ".xhtml")) {
|
||||||
|
strcpy(buf, "application/xhtml+xml");
|
||||||
|
sem_post(&sem_magic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(buf, type);
|
||||||
|
sem_post(&sem_magic);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void magic_charset(const char *restrict filename, char *buf) {
|
||||||
|
while (sem_wait(&sem_magic) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
critical("Unable to lock magic semaphore");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
magic_setflags(magic, MAGIC_MIME_ENCODING);
|
||||||
|
strcpy(buf, magic_file(magic, filename));
|
||||||
|
sem_post(&sem_magic);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cache_free(void) {
|
||||||
|
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
|
||||||
|
host_config_t *hc = &config.hosts[i];
|
||||||
|
if (hc->type == CONFIG_TYPE_UNSET) break;
|
||||||
|
if (hc->type != CONFIG_TYPE_LOCAL) continue;
|
||||||
|
|
||||||
|
munmap(hc->cache, sizeof(cache_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
magic_close(magic);
|
||||||
|
|
||||||
|
sem_destroy(&sem_lock);
|
||||||
|
sem_destroy(&sem_free);
|
||||||
|
sem_destroy(&sem_used);
|
||||||
|
sem_destroy(&sem_cache);
|
||||||
|
sem_destroy(&sem_magic);
|
||||||
|
}
|
||||||
|
|
||||||
|
static cache_entry_t *cache_get_entry(cache_t *cache, const char *filename) {
|
||||||
|
// search entry
|
||||||
|
cache_entry_t *entry;
|
||||||
|
for (int i = 0; i < CACHE_ENTRIES; i++) {
|
||||||
|
entry = &cache->entries[i];
|
||||||
|
if (entry->filename[0] == 0) break;
|
||||||
|
if (streq(entry->filename, filename)) {
|
||||||
|
// found
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static cache_entry_t *cache_get_new_entry(cache_t *cache) {
|
||||||
|
// globally lock cache
|
||||||
|
while (sem_wait(&sem_cache) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// search empty slot
|
||||||
|
cache_entry_t *entry;
|
||||||
|
for (int i = 0; i < CACHE_ENTRIES; i++) {
|
||||||
|
entry = &cache->entries[i];
|
||||||
|
if (entry->filename[0] == 0) {
|
||||||
|
// unlock cache
|
||||||
|
sem_post(&sem_cache);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlock cache
|
||||||
|
sem_post(&sem_cache);
|
||||||
|
|
||||||
|
// not found
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cache_process_entry(cache_entry_t *entry) {
|
||||||
|
char buf[16384], comp_buf[16384], filename_comp_gz[256], filename_comp_br[256];
|
||||||
|
|
||||||
|
info("Hashing file %s", entry->filename);
|
||||||
|
|
||||||
|
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||||
|
EVP_DigestInit(ctx, EVP_sha256());
|
||||||
|
FILE *file = fopen(entry->filename, "rb");
|
||||||
|
int compress = mime_is_compressible(entry->meta.type);
|
||||||
|
|
||||||
|
compress_ctx comp_ctx;
|
||||||
|
FILE *comp_file_gz = NULL, *comp_file_br = NULL;
|
||||||
|
if (compress) {
|
||||||
|
sprintf(buf, "%.*s/.sesimos", entry->webroot_len, entry->filename);
|
||||||
|
if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
|
||||||
|
error("Unable to create directory %s", buf);
|
||||||
|
goto comp_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(buf, "%.*s/.sesimos/cache", entry->webroot_len, entry->filename);
|
||||||
|
if (mkdir(buf, 0700) != 0 && errno != EEXIST) {
|
||||||
|
error("Unable to create directory %s", buf);
|
||||||
|
goto comp_err;
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
|
char *rel_path = entry->filename + entry->webroot_len + 1;
|
||||||
|
for (int j = 0; j < strlen(rel_path); j++) {
|
||||||
|
buf[j] = (char) ((rel_path[j] == '/') ? '_' : rel_path[j]);
|
||||||
|
}
|
||||||
|
buf[strlen(rel_path)] = 0;
|
||||||
|
|
||||||
|
int p_len_gz = snprintf(filename_comp_gz, sizeof(filename_comp_gz),
|
||||||
|
"%.*s/.sesimos/cache/%s.gz",
|
||||||
|
entry->webroot_len, entry->filename, buf);
|
||||||
|
int p_len_br = snprintf(filename_comp_br, sizeof(filename_comp_br),
|
||||||
|
"%.*s/.sesimos/cache/%s.br",
|
||||||
|
entry->webroot_len, entry->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)) {
|
||||||
|
error("Unable to open cached file: File name for compressed file too long");
|
||||||
|
goto comp_err;
|
||||||
|
}
|
||||||
|
|
||||||
|
info("Compressing file %s", entry->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) {
|
||||||
|
error("Unable to open cached file");
|
||||||
|
comp_err:
|
||||||
|
compress = 0;
|
||||||
|
} else {
|
||||||
|
if ((compress_init(&comp_ctx, COMPRESS_GZ | COMPRESS_BR | (mime_is_text(entry->meta.type) ? COMPRESS_UTF8 : 0))) != 0) {
|
||||||
|
error("Unable to init compression");
|
||||||
|
compress = 0;
|
||||||
|
fclose(comp_file_gz);
|
||||||
|
fclose(comp_file_br);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned long read, avail_in, avail_out; (read = fread(buf, 1, sizeof(buf), file)) > 0;) {
|
||||||
|
EVP_DigestUpdate(ctx, buf, read);
|
||||||
|
if (compress) {
|
||||||
|
avail_in = read;
|
||||||
|
do {
|
||||||
|
avail_out = sizeof(comp_buf);
|
||||||
|
compress_compress_mode(&comp_ctx, COMPRESS_GZ, buf + read - avail_in, &avail_in, comp_buf, &avail_out, feof(file));
|
||||||
|
fwrite(comp_buf, 1, sizeof(comp_buf) - avail_out, comp_file_gz);
|
||||||
|
} while (avail_in != 0 || avail_out != sizeof(comp_buf));
|
||||||
|
avail_in = read;
|
||||||
|
do {
|
||||||
|
avail_out = sizeof(comp_buf);
|
||||||
|
compress_compress_mode(&comp_ctx, COMPRESS_BR, buf + read - avail_in, &avail_in, comp_buf, &avail_out, feof(file));
|
||||||
|
fwrite(comp_buf, 1, sizeof(comp_buf) - avail_out, comp_file_br);
|
||||||
|
} while (avail_in != 0 || avail_out != sizeof(comp_buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compress) {
|
||||||
|
compress_free(&comp_ctx);
|
||||||
|
fclose(comp_file_gz);
|
||||||
|
fclose(comp_file_br);
|
||||||
|
info("Finished compressing file %s", entry->filename);
|
||||||
|
strcpy(entry->meta.filename_comp_gz, filename_comp_gz);
|
||||||
|
strcpy(entry->meta.filename_comp_br, filename_comp_br);
|
||||||
|
} else {
|
||||||
|
memset(entry->meta.filename_comp_gz, 0, sizeof(entry->meta.filename_comp_gz));
|
||||||
|
memset(entry->meta.filename_comp_br, 0, sizeof(entry->meta.filename_comp_br));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int md_len;
|
||||||
|
unsigned char hash[EVP_MAX_MD_SIZE];
|
||||||
|
EVP_DigestFinal(ctx, hash, &md_len);
|
||||||
|
EVP_MD_CTX_free(ctx);
|
||||||
|
|
||||||
|
memset(entry->meta.etag, 0, sizeof(entry->meta.etag));
|
||||||
|
for (int j = 0; j < md_len; j++) {
|
||||||
|
sprintf(entry->meta.etag + j * 2, "%02x", hash[j]);
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
entry->flags &= !CACHE_DIRTY;
|
||||||
|
|
||||||
|
info("Finished hashing file %s", entry->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *cache_thread(void *arg) {
|
||||||
|
logger_set_name("cache");
|
||||||
|
|
||||||
|
while (alive) {
|
||||||
|
pthread_testcancel();
|
||||||
|
if (sem_wait(&sem_used) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
error("Unable to lock semaphore");
|
||||||
|
errno = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_entry_t *entry = buffer.msgs[buffer.wr];
|
||||||
|
buffer.wr = (buffer.wr + 1) % CACHE_BUF_SIZE;
|
||||||
|
|
||||||
|
cache_process_entry(entry);
|
||||||
|
|
||||||
|
// unlock slot in buffer
|
||||||
|
sem_post(&sem_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_free();
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cache_init(void) {
|
||||||
|
char buf[512];
|
||||||
|
int ret, fd;
|
||||||
|
if ((ret = magic_init()) != 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
|
||||||
|
host_config_t *hc = &config.hosts[i];
|
||||||
|
if (hc->type == CONFIG_TYPE_UNSET) break;
|
||||||
|
if (hc->type != CONFIG_TYPE_LOCAL) continue;
|
||||||
|
|
||||||
|
sprintf(buf, "%s/.sesimos", hc->local.webroot);
|
||||||
|
if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
|
||||||
|
critical("Unable to create directory %s", buf);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
|
sprintf(buf, "%s/.sesimos/metadata", hc->local.webroot);
|
||||||
|
if ((fd = open(buf, O_CREAT | O_RDWR, 0600)) == -1) {
|
||||||
|
critical("Unable to open file %s", buf);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ftruncate(fd, sizeof(cache_t)) == -1) {
|
||||||
|
critical("Unable to truncate file %s", buf);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((hc->cache = mmap(NULL, sizeof(cache_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == NULL) {
|
||||||
|
critical("Unable to map file %s", buf);
|
||||||
|
close(fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to initialize all three semaphores
|
||||||
|
if (sem_init(&sem_lock, 0, 1) != 0 ||
|
||||||
|
sem_init(&sem_free, 0, 1) != 0 ||
|
||||||
|
sem_init(&sem_used, 0, 0) != 0 ||
|
||||||
|
sem_init(&sem_cache, 0, 1) != 0 ||
|
||||||
|
sem_init(&sem_magic, 0, 1) != 0)
|
||||||
|
{
|
||||||
|
critical("Unable to initialize semaphore");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize read/write heads
|
||||||
|
buffer.rd = 0;
|
||||||
|
buffer.wr = 0;
|
||||||
|
|
||||||
|
pthread_create(&thread, NULL, cache_thread, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cache_stop(void) {
|
||||||
|
alive = 0;
|
||||||
|
pthread_kill(thread, SIGUSR1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cache_join(void) {
|
||||||
|
return pthread_join(thread, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cache_mark_entry_dirty(cache_entry_t *entry) {
|
||||||
|
if (entry->flags & CACHE_DIRTY)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entry->flags |= CACHE_DIRTY;
|
||||||
|
memset(entry->meta.etag, 0, sizeof(entry->meta.etag));
|
||||||
|
memset(entry->meta.filename_comp_gz, 0, sizeof(entry->meta.filename_comp_gz));
|
||||||
|
memset(entry->meta.filename_comp_br, 0, sizeof(entry->meta.filename_comp_br));
|
||||||
|
|
||||||
|
while (sem_wait(&sem_free) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
error("Unable to lock semaphore");
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to lock buffer
|
||||||
|
while (sem_wait(&sem_lock) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
error("Unable to lock semaphore");
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write to buffer
|
||||||
|
buffer.msgs[buffer.rd] = entry;
|
||||||
|
buffer.rd = (buffer.rd + 1) % CACHE_BUF_SIZE;
|
||||||
|
|
||||||
|
// unlock buffer
|
||||||
|
sem_post(&sem_lock);
|
||||||
|
|
||||||
|
// unlock slot in buffer for logger
|
||||||
|
sem_post(&sem_used);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cache_update_entry(cache_entry_t *entry, const char *filename, const char *webroot) {
|
||||||
|
entry->meta.mtime = stat_mtime(filename);
|
||||||
|
entry->webroot_len = (unsigned char) strlen(webroot);
|
||||||
|
strcpy(entry->filename, filename);
|
||||||
|
|
||||||
|
magic_mime_type(filename, entry->meta.type);
|
||||||
|
magic_charset(filename, entry->meta.charset);
|
||||||
|
|
||||||
|
cache_mark_entry_dirty(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cache_mark_dirty(cache_t *cache, const char *filename) {
|
||||||
|
cache_entry_t *entry = cache_get_entry(cache, filename);
|
||||||
|
if (entry) cache_mark_entry_dirty(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cache_init_uri(cache_t *cache, http_uri *uri) {
|
||||||
|
if (!uri->filename)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cache_entry_t *entry = cache_get_entry(cache, uri->filename);
|
||||||
|
if (!entry) {
|
||||||
|
// no entry found -> create new entry
|
||||||
|
if ((entry = cache_get_new_entry(cache))) {
|
||||||
|
cache_update_entry(entry, uri->filename, uri->webroot);
|
||||||
|
uri->meta = &entry->meta;
|
||||||
|
} else {
|
||||||
|
warning("No empty cache entry slot found");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uri->meta = &entry->meta;
|
||||||
|
if (entry->flags & CACHE_DIRTY)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// check, if file has changed
|
||||||
|
if (uri->meta->mtime != stat_mtime(uri->filename)) {
|
||||||
|
// modify time has changed
|
||||||
|
cache_update_entry(entry, uri->filename, uri->webroot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/cache_handler.h
Normal file
46
src/cache_handler.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief File cache implementation (header file)
|
||||||
|
* @file src/cache_handler.h
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-19
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SESIMOS_CACHE_HANDLER_H
|
||||||
|
#define SESIMOS_CACHE_HANDLER_H
|
||||||
|
|
||||||
|
#include "lib/uri.h"
|
||||||
|
|
||||||
|
#define CACHE_ENTRIES 1024
|
||||||
|
|
||||||
|
#define CACHE_DIRTY 1
|
||||||
|
|
||||||
|
#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 flags;
|
||||||
|
metadata_t meta;
|
||||||
|
} cache_entry_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char sig[6];
|
||||||
|
unsigned char ver;
|
||||||
|
cache_entry_t entries[CACHE_ENTRIES];
|
||||||
|
} cache_t;
|
||||||
|
|
||||||
|
int cache_init(void);
|
||||||
|
|
||||||
|
void cache_stop(void);
|
||||||
|
|
||||||
|
int cache_join(void);
|
||||||
|
|
||||||
|
void cache_mark_dirty(cache_t *cache, const char *filename);
|
||||||
|
|
||||||
|
void cache_init_uri(cache_t *cache, http_uri *uri);
|
||||||
|
|
||||||
|
#endif //SESIMOS_CACHE_HANDLER_H
|
898
src/client.c
898
src/client.c
@ -1,898 +0,0 @@
|
|||||||
/**
|
|
||||||
* Necronda Web Server
|
|
||||||
* Client connection and request handlers
|
|
||||||
* src/client.c
|
|
||||||
* Lorenz Stechauner, 2020-12-03
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "lib/utils.h"
|
|
||||||
#include "lib/config.h"
|
|
||||||
#include "lib/sock.h"
|
|
||||||
#include "lib/http.h"
|
|
||||||
#include "lib/rev_proxy.h"
|
|
||||||
#include "lib/fastcgi.h"
|
|
||||||
#include "lib/cache.h"
|
|
||||||
#include "lib/geoip.h"
|
|
||||||
#include "lib/compress.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
|
|
||||||
int server_keep_alive = 1;
|
|
||||||
struct timeval client_timeout = {.tv_sec = CLIENT_TIMEOUT, .tv_usec = 0};
|
|
||||||
|
|
||||||
int server_keep_alive;
|
|
||||||
char *log_client_prefix, *log_conn_prefix, *log_req_prefix, *client_geoip;
|
|
||||||
char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr, *client_host_str;
|
|
||||||
struct timeval client_timeout;
|
|
||||||
|
|
||||||
host_config *get_host_config(const char *host) {
|
|
||||||
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
|
|
||||||
host_config *hc = &config->hosts[i];
|
|
||||||
if (hc->type == CONFIG_TYPE_UNSET) break;
|
|
||||||
if (strcmp(hc->name, host) == 0) return hc;
|
|
||||||
if (hc->name[0] == '*' && hc->name[1] == '.') {
|
|
||||||
const char *pos = strstr(host, hc->name + 1);
|
|
||||||
if (pos != NULL && strlen(pos) == strlen(hc->name + 1)) return hc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_terminate() {
|
|
||||||
server_keep_alive = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int client_websocket_handler() {
|
|
||||||
// TODO implement client_websocket_handler
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int client_request_handler(sock *client, unsigned long client_num, unsigned int req_num) {
|
|
||||||
struct timespec begin, end;
|
|
||||||
long ret;
|
|
||||||
int client_keep_alive;
|
|
||||||
|
|
||||||
char buf0[1024], buf1[1024];
|
|
||||||
char msg_buf[8192], msg_pre_buf_1[4096], msg_pre_buf_2[4096], err_msg[256];
|
|
||||||
char msg_content[1024];
|
|
||||||
char buffer[CHUNK_SIZE];
|
|
||||||
char host[256], *host_ptr, *hdr_connection;
|
|
||||||
|
|
||||||
msg_buf[0] = 0;
|
|
||||||
err_msg[0] = 0;
|
|
||||||
msg_content[0] = 0;
|
|
||||||
|
|
||||||
host_config *conf = NULL;
|
|
||||||
FILE *file = NULL;
|
|
||||||
|
|
||||||
long content_length = 0;
|
|
||||||
int accept_if_modified_since = 0;
|
|
||||||
int use_fastcgi = 0;
|
|
||||||
int use_rev_proxy = 0;
|
|
||||||
int p_len;
|
|
||||||
|
|
||||||
fastcgi_conn fcgi_conn = {.socket = 0, .req_id = 0};
|
|
||||||
http_status custom_status;
|
|
||||||
|
|
||||||
http_res res = {.version = "1.1", .status = http_get_status(501), .hdr.field_num = 0};
|
|
||||||
http_status_ctx ctx = {.status = 0, .origin = NONE};
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &begin);
|
|
||||||
|
|
||||||
fd_set socket_fds;
|
|
||||||
FD_ZERO(&socket_fds);
|
|
||||||
FD_SET(client->socket, &socket_fds);
|
|
||||||
client_timeout.tv_sec = CLIENT_TIMEOUT;
|
|
||||||
client_timeout.tv_usec = 0;
|
|
||||||
ret = select(client->socket + 1, &socket_fds, NULL, NULL, &client_timeout);
|
|
||||||
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
|
|
||||||
http_add_header_field(&res.hdr, "Server", SERVER_STR);
|
|
||||||
if (ret <= 0) {
|
|
||||||
if (errno != 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
client_keep_alive = 0;
|
|
||||||
res.status = http_get_status(408);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &begin);
|
|
||||||
|
|
||||||
http_req req;
|
|
||||||
ret = http_receive_request(client, &req);
|
|
||||||
if (ret != 0) {
|
|
||||||
client_keep_alive = 0;
|
|
||||||
if (ret < 0) {
|
|
||||||
goto abort;
|
|
||||||
} else if (ret == 1) {
|
|
||||||
sprintf(err_msg, "Unable to parse http header: Invalid header format.");
|
|
||||||
} else if (ret == 2) {
|
|
||||||
sprintf(err_msg, "Unable to parse http header: Invalid method.");
|
|
||||||
} else if (ret == 3) {
|
|
||||||
sprintf(err_msg, "Unable to parse http header: Invalid version.");
|
|
||||||
} else if (ret == 4) {
|
|
||||||
sprintf(err_msg, "Unable to parse http header: Header contains illegal characters.");
|
|
||||||
} else if (ret == 5) {
|
|
||||||
sprintf(err_msg, "Unable to parse http header: End of header not found.");
|
|
||||||
}
|
|
||||||
res.status = http_get_status(400);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr_connection = http_get_header_field(&req.hdr, "Connection");
|
|
||||||
client_keep_alive = hdr_connection != NULL &&
|
|
||||||
(strcmp(hdr_connection, "keep-alive") == 0 || strcmp(hdr_connection, "Keep-Alive") == 0);
|
|
||||||
host_ptr = http_get_header_field(&req.hdr, "Host");
|
|
||||||
if (host_ptr != NULL && strlen(host_ptr) > 255) {
|
|
||||||
host[0] = 0;
|
|
||||||
res.status = http_get_status(400);
|
|
||||||
sprintf(err_msg, "Host header field is too long.");
|
|
||||||
goto respond;
|
|
||||||
} else if (host_ptr == NULL || strchr(host_ptr, '/') != NULL) {
|
|
||||||
if (strchr(client_addr_str, ':') == NULL) {
|
|
||||||
strcpy(host, client_addr_str);
|
|
||||||
} else {
|
|
||||||
sprintf(host, "[%s]", client_addr_str);
|
|
||||||
}
|
|
||||||
res.status = http_get_status(400);
|
|
||||||
sprintf(err_msg, "The client provided no or an invalid Host header field.");
|
|
||||||
goto respond;
|
|
||||||
} else {
|
|
||||||
strcpy(host, host_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
sprintf(log_req_prefix, "[%6i][%s%*s%s]%s ", getpid(), BLD_STR, INET6_ADDRSTRLEN, host, CLR_STR, log_client_prefix);
|
|
||||||
log_prefix = log_req_prefix;
|
|
||||||
print(BLD_STR "%s %s" CLR_STR, req.method, req.uri);
|
|
||||||
|
|
||||||
conf = get_host_config(host);
|
|
||||||
if (conf == NULL) {
|
|
||||||
print("Host unknown, redirecting to default");
|
|
||||||
res.status = http_get_status(307);
|
|
||||||
sprintf(buf0, "https://%s%s", DEFAULT_HOST, req.uri);
|
|
||||||
http_add_header_field(&res.hdr, "Location", buf0);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
http_uri uri;
|
|
||||||
unsigned char dir_mode = conf->type == CONFIG_TYPE_LOCAL ? conf->local.dir_mode : URI_DIR_MODE_NO_VALIDATION;
|
|
||||||
ret = uri_init(&uri, conf->local.webroot, req.uri, dir_mode);
|
|
||||||
if (ret != 0) {
|
|
||||||
if (ret == 1) {
|
|
||||||
sprintf(err_msg, "Invalid URI: has to start with slash.");
|
|
||||||
res.status = http_get_status(400);
|
|
||||||
} else if (ret == 2) {
|
|
||||||
sprintf(err_msg, "Invalid URI: contains relative path change (/../).");
|
|
||||||
res.status = http_get_status(400);
|
|
||||||
} else if (ret == 3) {
|
|
||||||
sprintf(err_msg, "The specified webroot directory does not exist.");
|
|
||||||
res.status = http_get_status(404);
|
|
||||||
} else {
|
|
||||||
res.status = http_get_status(500);
|
|
||||||
}
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dir_mode != URI_DIR_MODE_NO_VALIDATION) {
|
|
||||||
ssize_t size = sizeof(buf0);
|
|
||||||
url_decode(req.uri, buf0, &size);
|
|
||||||
int change_proto = strncmp(uri.uri, "/.well-known/", 13) != 0 && !client->enc;
|
|
||||||
if (strcmp(uri.uri, buf0) != 0 || change_proto) {
|
|
||||||
res.status = http_get_status(308);
|
|
||||||
size = sizeof(buf0);
|
|
||||||
url_encode(uri.uri, buf0, &size);
|
|
||||||
if (change_proto) {
|
|
||||||
p_len = snprintf(buf1, sizeof(buf1), "https://%s%s", host, buf0);
|
|
||||||
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
|
||||||
res.status = http_get_status(500);
|
|
||||||
print(ERR_STR "Header field 'Location' too long" CLR_STR);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
http_add_header_field(&res.hdr, "Location", buf1);
|
|
||||||
} else {
|
|
||||||
http_add_header_field(&res.hdr, "Location", buf0);
|
|
||||||
}
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
} else if (!client->enc) {
|
|
||||||
res.status = http_get_status(308);
|
|
||||||
sprintf(buf0, "https://%s%s", host, req.uri);
|
|
||||||
http_add_header_field(&res.hdr, "Location", buf0);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (http_get_header_field(&req.hdr, "Transfer-Encoding") != NULL) {
|
|
||||||
sprintf(err_msg, "This server is unable to process requests with the Transfer-Encoding header field.");
|
|
||||||
res.status = http_get_status(501);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conf->type == CONFIG_TYPE_LOCAL) {
|
|
||||||
if (strcmp(req.method, "TRACE") == 0) {
|
|
||||||
res.status = http_get_status(200);
|
|
||||||
http_add_header_field(&res.hdr, "Content-Type", "message/http");
|
|
||||||
|
|
||||||
content_length = snprintf(msg_buf, sizeof(msg_buf) - content_length, "%s %s HTTP/%s\r\n",
|
|
||||||
req.method, req.uri, req.version);
|
|
||||||
for (int i = 0; i < req.hdr.field_num; i++) {
|
|
||||||
content_length += snprintf(msg_buf + content_length, sizeof(msg_buf) - content_length, "%s: %s\r\n",
|
|
||||||
req.hdr.fields[i][0], req.hdr.fields[i][1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strncmp(uri.req_path, "/.well-known/", 13) == 0) {
|
|
||||||
http_add_header_field(&res.hdr, "Access-Control-Allow-Origin", "*");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strncmp(uri.req_path, "/.well-known/", 13) != 0 && strstr(uri.path, "/.") != NULL) {
|
|
||||||
res.status = http_get_status(403);
|
|
||||||
sprintf(err_msg, "Parts of this URI are hidden.");
|
|
||||||
goto respond;
|
|
||||||
} else if (uri.filename == NULL && (int) uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
|
|
||||||
res.status = http_get_status(403);
|
|
||||||
sprintf(err_msg, "It is not allowed to list the contents of this directory.");
|
|
||||||
goto respond;
|
|
||||||
} else if (uri.filename == NULL && (int) !uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
|
|
||||||
// TODO list directory contents
|
|
||||||
res.status = http_get_status(501);
|
|
||||||
sprintf(err_msg, "Listing contents of an directory is currently not implemented.");
|
|
||||||
goto respond;
|
|
||||||
} else if (uri.filename == NULL || (strlen(uri.pathinfo) > 0 && (int) uri.is_static)) {
|
|
||||||
res.status = http_get_status(404);
|
|
||||||
goto respond;
|
|
||||||
} else if (strlen(uri.pathinfo) != 0 && conf->local.dir_mode != URI_DIR_MODE_INFO) {
|
|
||||||
res.status = http_get_status(404);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.is_static) {
|
|
||||||
res.status = http_get_status(200);
|
|
||||||
http_add_header_field(&res.hdr, "Accept-Ranges", "bytes");
|
|
||||||
if (strcmp(req.method, "GET") != 0 && strcmp(req.method, "HEAD") != 0) {
|
|
||||||
res.status = http_get_status(405);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (http_get_header_field(&req.hdr, "Content-Length") != NULL) {
|
|
||||||
res.status = http_get_status(400);
|
|
||||||
sprintf(err_msg, "A GET request must not contain a payload");
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = uri_cache_init(&uri);
|
|
||||||
if (ret != 0) {
|
|
||||||
res.status = http_get_status(500);
|
|
||||||
sprintf(err_msg, "Unable to communicate with internal file cache.");
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
char *last_modified = http_format_date(uri.meta->stat.st_mtime, buf0, sizeof(buf0));
|
|
||||||
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
|
|
||||||
sprintf(buf1, "%s; charset=%s", uri.meta->type, uri.meta->charset);
|
|
||||||
http_add_header_field(&res.hdr, "Content-Type", buf1);
|
|
||||||
|
|
||||||
|
|
||||||
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
|
|
||||||
int enc = 0;
|
|
||||||
if (accept_encoding != NULL) {
|
|
||||||
if (uri.meta->filename_comp_br[0] != 0 && strstr(accept_encoding, "br") != NULL) {
|
|
||||||
file = fopen(uri.meta->filename_comp_br, "rb");
|
|
||||||
if (file == NULL) {
|
|
||||||
cache_filename_comp_invalid(uri.filename);
|
|
||||||
} else {
|
|
||||||
http_add_header_field(&res.hdr, "Content-Encoding", "br");
|
|
||||||
enc = COMPRESS_BR;
|
|
||||||
}
|
|
||||||
} else if (uri.meta->filename_comp_gz[0] != 0 && strstr(accept_encoding, "gzip") != NULL) {
|
|
||||||
file = fopen(uri.meta->filename_comp_gz, "rb");
|
|
||||||
if (file == NULL) {
|
|
||||||
cache_filename_comp_invalid(uri.filename);
|
|
||||||
} else {
|
|
||||||
http_add_header_field(&res.hdr, "Content-Encoding", "gzip");
|
|
||||||
enc = COMPRESS_GZ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (enc != 0) {
|
|
||||||
http_add_header_field(&res.hdr, "Vary", "Accept-Encoding");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.meta->etag[0] != 0) {
|
|
||||||
if (enc) {
|
|
||||||
sprintf(buf0, "%s-%s", uri.meta->etag,
|
|
||||||
(enc & COMPRESS_BR) ? "br" : (enc & COMPRESS_GZ) ? "gzip" : "");
|
|
||||||
http_add_header_field(&res.hdr, "ETag", buf0);
|
|
||||||
} else {
|
|
||||||
http_add_header_field(&res.hdr, "ETag", uri.meta->etag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strncmp(uri.meta->type, "text/", 5) == 0) {
|
|
||||||
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=3600");
|
|
||||||
} else {
|
|
||||||
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=86400");
|
|
||||||
}
|
|
||||||
|
|
||||||
char *if_modified_since = http_get_header_field(&req.hdr, "If-Modified-Since");
|
|
||||||
char *if_none_match = http_get_header_field(&req.hdr, "If-None-Match");
|
|
||||||
if ((if_none_match != NULL && strstr(if_none_match, uri.meta->etag) == NULL) ||
|
|
||||||
(accept_if_modified_since && if_modified_since != NULL &&
|
|
||||||
strcmp(if_modified_since, last_modified) == 0)) {
|
|
||||||
res.status = http_get_status(304);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *range = http_get_header_field(&req.hdr, "Range");
|
|
||||||
if (range != NULL) {
|
|
||||||
if (strlen(range) <= 6 || strncmp(range, "bytes=", 6) != 0) {
|
|
||||||
res.status = http_get_status(416);
|
|
||||||
http_remove_header_field(&res.hdr, "Content-Type", HTTP_REMOVE_ALL);
|
|
||||||
http_remove_header_field(&res.hdr, "Last-Modified", HTTP_REMOVE_ALL);
|
|
||||||
http_remove_header_field(&res.hdr, "ETag", HTTP_REMOVE_ALL);
|
|
||||||
http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
range += 6;
|
|
||||||
char *ptr = strchr(range, '-');
|
|
||||||
if (ptr == NULL) {
|
|
||||||
res.status = http_get_status(416);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
file = fopen(uri.filename, "rb");
|
|
||||||
fseek(file, 0, SEEK_END);
|
|
||||||
unsigned long file_len = ftell(file);
|
|
||||||
fseek(file, 0, SEEK_SET);
|
|
||||||
if (file_len == 0) {
|
|
||||||
content_length = 0;
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
long num1 = 0;
|
|
||||||
long num2 = (long) file_len - 1;
|
|
||||||
|
|
||||||
if (ptr != range) num1 = (long) strtoul(range, NULL, 10);
|
|
||||||
if (ptr[1] != 0) num2 = (long) strtoul(ptr + 1, NULL, 10);
|
|
||||||
|
|
||||||
if (num1 >= file_len || num2 >= file_len || num1 > num2) {
|
|
||||||
res.status = http_get_status(416);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
sprintf(buf0, "bytes %li-%li/%li", num1, num2, file_len);
|
|
||||||
http_add_header_field(&res.hdr, "Content-Range", buf0);
|
|
||||||
|
|
||||||
res.status = http_get_status(206);
|
|
||||||
fseek(file, num1, SEEK_SET);
|
|
||||||
content_length = num2 - num1 + 1;
|
|
||||||
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file == NULL) {
|
|
||||||
file = fopen(uri.filename, "rb");
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek(file, 0, SEEK_END);
|
|
||||||
content_length = ftell(file);
|
|
||||||
fseek(file, 0, SEEK_SET);
|
|
||||||
} else {
|
|
||||||
int mode;
|
|
||||||
if (strcmp(uri.filename + strlen(uri.filename) - 4, ".ncr") == 0) {
|
|
||||||
mode = FASTCGI_NECRONDA;
|
|
||||||
} else if (strcmp(uri.filename + strlen(uri.filename) - 4, ".php") == 0) {
|
|
||||||
mode = FASTCGI_PHP;
|
|
||||||
} else {
|
|
||||||
res.status = http_get_status(500);
|
|
||||||
print(ERR_STR "Invalid FastCGI extension: %s" CLR_STR, uri.filename);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct stat statbuf;
|
|
||||||
stat(uri.filename, &statbuf);
|
|
||||||
char *last_modified = http_format_date(statbuf.st_mtime, buf0, sizeof(buf0));
|
|
||||||
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
|
|
||||||
|
|
||||||
res.status = http_get_status(200);
|
|
||||||
if (fastcgi_init(&fcgi_conn, mode, client_num, req_num, client, &req, &uri) != 0) {
|
|
||||||
res.status = http_get_status(503);
|
|
||||||
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *client_content_length = http_get_header_field(&req.hdr, "Content-Length");
|
|
||||||
if (client_content_length != NULL) {
|
|
||||||
unsigned long client_content_len = strtoul(client_content_length, NULL, 10);
|
|
||||||
ret = fastcgi_receive(&fcgi_conn, client, client_content_len);
|
|
||||||
if (ret != 0) {
|
|
||||||
if (ret < 0) {
|
|
||||||
goto abort;
|
|
||||||
} else {
|
|
||||||
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
|
|
||||||
}
|
|
||||||
res.status = http_get_status(502);
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fastcgi_close_stdin(&fcgi_conn);
|
|
||||||
|
|
||||||
ret = fastcgi_header(&fcgi_conn, &res, err_msg);
|
|
||||||
if (ret != 0) {
|
|
||||||
if (ret < 0) goto abort;
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *status = http_get_header_field(&res.hdr, "Status");
|
|
||||||
if (status != NULL) {
|
|
||||||
int status_code = (int) strtoul(status, NULL, 10);
|
|
||||||
res.status = http_get_status(status_code);
|
|
||||||
http_remove_header_field(&res.hdr, "Status", HTTP_REMOVE_ALL);
|
|
||||||
if (res.status == NULL && status_code >= 100 && status_code <= 999) {
|
|
||||||
custom_status.code = status_code;
|
|
||||||
strcpy(custom_status.type, "");
|
|
||||||
strcpy(custom_status.msg, status + 4);
|
|
||||||
res.status = &custom_status;
|
|
||||||
} else if (res.status == NULL) {
|
|
||||||
res.status = http_get_status(500);
|
|
||||||
sprintf(err_msg, "The status code was set to an invalid or unknown value.");
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *content_length_f = http_get_header_field(&res.hdr, "Content-Length");
|
|
||||||
content_length = (content_length_f == NULL) ? -1 : strtol(content_length_f, NULL, 10);
|
|
||||||
|
|
||||||
const char *content_type = http_get_header_field(&res.hdr, "Content-Type");
|
|
||||||
const char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
|
|
||||||
if (content_encoding == NULL &&
|
|
||||||
content_type != NULL &&
|
|
||||||
strncmp(content_type, "text/html", 9) == 0 &&
|
|
||||||
content_length != -1 &&
|
|
||||||
content_length <= sizeof(msg_content) - 1)
|
|
||||||
{
|
|
||||||
fastcgi_dump(&fcgi_conn, msg_content, sizeof(msg_content));
|
|
||||||
goto respond;
|
|
||||||
}
|
|
||||||
|
|
||||||
use_fastcgi = 1;
|
|
||||||
|
|
||||||
if (content_length != -1 && content_length < 1024000) {
|
|
||||||
use_fastcgi |= FASTCGI_COMPRESS_HOLD;
|
|
||||||
}
|
|
||||||
|
|
||||||
content_length = -1;
|
|
||||||
|
|
||||||
int http_comp = http_get_compression(&req, &res);
|
|
||||||
if (http_comp & COMPRESS) {
|
|
||||||
if (http_comp & COMPRESS_BR) {
|
|
||||||
use_fastcgi |= FASTCGI_COMPRESS_BR;
|
|
||||||
sprintf(buf0, "br");
|
|
||||||
} else if (http_comp & COMPRESS_GZ) {
|
|
||||||
use_fastcgi |= FASTCGI_COMPRESS_GZ;
|
|
||||||
sprintf(buf0, "gzip");
|
|
||||||
}
|
|
||||||
http_add_header_field(&res.hdr, "Vary", "Accept-Encoding");
|
|
||||||
http_add_header_field(&res.hdr, "Content-Encoding", buf0);
|
|
||||||
http_remove_header_field(&res.hdr, "Content-Length", HTTP_REMOVE_ALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (http_get_header_field(&res.hdr, "Content-Length") == NULL) {
|
|
||||||
http_add_header_field(&res.hdr, "Transfer-Encoding", "chunked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (conf->type == CONFIG_TYPE_REVERSE_PROXY) {
|
|
||||||
print("Reverse proxy for " BLD_STR "%s:%i" CLR_STR, conf->rev_proxy.hostname, conf->rev_proxy.port);
|
|
||||||
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
|
|
||||||
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
|
|
||||||
|
|
||||||
ret = rev_proxy_init(&req, &res, &ctx, conf, client, &custom_status, err_msg);
|
|
||||||
use_rev_proxy = (ret == 0);
|
|
||||||
|
|
||||||
// Let 300 be formatted by origin server
|
|
||||||
if (use_rev_proxy && res.status->code >= 301 && res.status->code < 600) {
|
|
||||||
const char *content_type = http_get_header_field(&res.hdr, "Content-Type");
|
|
||||||
const char *content_length_f = http_get_header_field(&res.hdr, "Content-Length");
|
|
||||||
const char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
|
|
||||||
if (content_encoding == NULL && content_type != NULL && content_length_f != NULL &&
|
|
||||||
strncmp(content_type, "text/html", 9) == 0)
|
|
||||||
{
|
|
||||||
long content_len = strtol(content_length_f, NULL, 10);
|
|
||||||
if (content_len <= sizeof(msg_content) - 1) {
|
|
||||||
ctx.status = res.status->code;
|
|
||||||
ctx.origin = res.status->code >= 400 ? SERVER : NONE;
|
|
||||||
use_rev_proxy = 0;
|
|
||||||
rev_proxy_dump(msg_content, content_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
|
|
||||||
if (use_rev_proxy && content_encoding == NULL) {
|
|
||||||
int http_comp = http_get_compression(&req, &res);
|
|
||||||
if (http_comp & COMPRESS_BR) {
|
|
||||||
use_rev_proxy |= REV_PROXY_COMPRESS_BR;
|
|
||||||
} else if (http_comp & COMPRESS_GZ) {
|
|
||||||
use_rev_proxy |= REV_PROXY_COMPRESS_GZ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
|
|
||||||
int chunked = transfer_encoding != NULL && strcmp(transfer_encoding, "chunked") == 0;
|
|
||||||
http_remove_header_field(&res.hdr, "Transfer-Encoding", HTTP_REMOVE_ALL);
|
|
||||||
ret = sprintf(buf0, "%s%s%s",
|
|
||||||
(use_rev_proxy & REV_PROXY_COMPRESS_BR) ? "br" :
|
|
||||||
((use_rev_proxy & REV_PROXY_COMPRESS_GZ) ? "gzip" : ""),
|
|
||||||
((use_rev_proxy & REV_PROXY_COMPRESS) && chunked) ? ", " : "",
|
|
||||||
chunked ? "chunked" : "");
|
|
||||||
if (ret > 0) {
|
|
||||||
http_add_header_field(&res.hdr, "Transfer-Encoding", buf0);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
} else {
|
|
||||||
print(ERR_STR "Unknown host type: %i" CLR_STR, conf->type);
|
|
||||||
res.status = http_get_status(501);
|
|
||||||
}
|
|
||||||
|
|
||||||
respond:
|
|
||||||
if (!use_rev_proxy) {
|
|
||||||
if (conf != NULL && conf->type == CONFIG_TYPE_LOCAL && uri.is_static && res.status->code == 405) {
|
|
||||||
http_add_header_field(&res.hdr, "Allow", "GET, HEAD, TRACE");
|
|
||||||
}
|
|
||||||
if (http_get_header_field(&res.hdr, "Accept-Ranges") == NULL) {
|
|
||||||
http_add_header_field(&res.hdr, "Accept-Ranges", "none");
|
|
||||||
}
|
|
||||||
if (!use_fastcgi && file == NULL) {
|
|
||||||
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
|
|
||||||
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
|
|
||||||
http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
|
|
||||||
http_remove_header_field(&res.hdr, "Content-Type", HTTP_REMOVE_ALL);
|
|
||||||
http_remove_header_field(&res.hdr, "Content-Encoding", HTTP_REMOVE_ALL);
|
|
||||||
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
|
|
||||||
http_add_header_field(&res.hdr, "Server", SERVER_STR);
|
|
||||||
http_add_header_field(&res.hdr, "Cache-Control", "no-cache");
|
|
||||||
http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8");
|
|
||||||
|
|
||||||
// TODO list Locations on 3xx Redirects
|
|
||||||
const http_doc_info *info = http_get_status_info(res.status);
|
|
||||||
const http_status_msg *http_msg = http_get_error_msg(res.status);
|
|
||||||
|
|
||||||
if (res.status->code >= 300 && res.status->code < 400 && msg_content[0] == 0) {
|
|
||||||
const char *location = http_get_header_field(&res.hdr, "Location");
|
|
||||||
if (location != NULL) {
|
|
||||||
snprintf(msg_content, sizeof(msg_content),
|
|
||||||
"<ul>\n\t<li><a href=\"%1$s\">%1$s</a></li>\n</ul>\n", location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *rev_proxy_doc = "";
|
|
||||||
if (conf != NULL && conf->type == CONFIG_TYPE_REVERSE_PROXY) {
|
|
||||||
const http_status *status = http_get_status(ctx.status);
|
|
||||||
char stat_str[8];
|
|
||||||
sprintf(stat_str, "%03i", ctx.status);
|
|
||||||
sprintf(msg_pre_buf_2, http_rev_proxy_document,
|
|
||||||
" success",
|
|
||||||
(ctx.origin == CLIENT_REQ) ? " error" : " success",
|
|
||||||
(ctx.origin == INTERNAL) ? " error" : " success",
|
|
||||||
(ctx.origin == SERVER_REQ) ? " error" : (ctx.status == 0 ? "" : " success"),
|
|
||||||
(ctx.origin == CLIENT_RES) ? " error" : " success",
|
|
||||||
(ctx.origin == SERVER) ? " error" : (ctx.status == 0 ? "" : " success"),
|
|
||||||
(ctx.origin == SERVER_RES) ? " error" : (ctx.status == 0 ? "" : " success"),
|
|
||||||
(ctx.origin == INTERNAL) ? " error" : " success",
|
|
||||||
(ctx.origin == INTERNAL || ctx.origin == SERVER) ? " error" : " success",
|
|
||||||
res.status->code,
|
|
||||||
res.status->msg,
|
|
||||||
(ctx.status == 0) ? "???" : stat_str,
|
|
||||||
(status != NULL) ? status->msg : "",
|
|
||||||
host);
|
|
||||||
rev_proxy_doc = msg_pre_buf_2;
|
|
||||||
}
|
|
||||||
|
|
||||||
sprintf(msg_pre_buf_1, info->doc, res.status->code, res.status->msg,
|
|
||||||
http_msg != NULL ? http_msg->msg : "", err_msg[0] != 0 ? err_msg : "");
|
|
||||||
content_length = snprintf(msg_buf, sizeof(msg_buf), http_default_document, res.status->code,
|
|
||||||
res.status->msg, msg_pre_buf_1, info->mode, info->icon, info->color, host,
|
|
||||||
rev_proxy_doc, msg_content[0] != 0 ? msg_content : "");
|
|
||||||
}
|
|
||||||
if (content_length >= 0) {
|
|
||||||
sprintf(buf0, "%li", content_length);
|
|
||||||
http_remove_header_field(&res.hdr, "Content-Length", HTTP_REMOVE_ALL);
|
|
||||||
http_add_header_field(&res.hdr, "Content-Length", buf0);
|
|
||||||
} else if (http_get_header_field(&res.hdr, "Transfer-Encoding") == NULL) {
|
|
||||||
server_keep_alive = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *conn = http_get_header_field(&res.hdr, "Connection");
|
|
||||||
int close_proxy = conn == NULL || (strcmp(conn, "keep-alive") != 0 && strcmp(conn, "Keep-Alive") != 0);
|
|
||||||
http_remove_header_field(&res.hdr, "Connection", HTTP_REMOVE_ALL);
|
|
||||||
http_remove_header_field(&res.hdr, "Keep-Alive", HTTP_REMOVE_ALL);
|
|
||||||
if (server_keep_alive && client_keep_alive) {
|
|
||||||
http_add_header_field(&res.hdr, "Connection", "keep-alive");
|
|
||||||
sprintf(buf0, "timeout=%i, max=%i", CLIENT_TIMEOUT, REQ_PER_CONNECTION);
|
|
||||||
http_add_header_field(&res.hdr, "Keep-Alive", buf0);
|
|
||||||
} else {
|
|
||||||
http_add_header_field(&res.hdr, "Connection", "close");
|
|
||||||
}
|
|
||||||
|
|
||||||
http_send_response(client, &res);
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
|
||||||
char *location = http_get_header_field(&res.hdr, "Location");
|
|
||||||
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
|
|
||||||
print("%s%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), use_rev_proxy ? "-> " : "", res.status->code,
|
|
||||||
res.status->msg, location != NULL ? " -> " : "", location != NULL ? location : "",
|
|
||||||
format_duration(micros, buf0), CLR_STR);
|
|
||||||
|
|
||||||
// TODO access/error log file
|
|
||||||
|
|
||||||
if (strcmp(req.method, "HEAD") != 0) {
|
|
||||||
unsigned long snd_len = 0;
|
|
||||||
unsigned long len;
|
|
||||||
if (msg_buf[0] != 0) {
|
|
||||||
ret = sock_send(client, msg_buf, content_length, 0);
|
|
||||||
if (ret <= 0) {
|
|
||||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
|
||||||
}
|
|
||||||
snd_len += ret;
|
|
||||||
} else if (file != NULL) {
|
|
||||||
while (snd_len < content_length) {
|
|
||||||
len = fread(buffer, 1, CHUNK_SIZE, file);
|
|
||||||
if (snd_len + len > content_length) {
|
|
||||||
len = content_length - snd_len;
|
|
||||||
}
|
|
||||||
ret = sock_send(client, buffer, len, feof(file) ? 0 : MSG_MORE);
|
|
||||||
if (ret <= 0) {
|
|
||||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
snd_len += ret;
|
|
||||||
}
|
|
||||||
} else if (use_fastcgi) {
|
|
||||||
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
|
|
||||||
int chunked = transfer_encoding != NULL && strcmp(transfer_encoding, "chunked") == 0;
|
|
||||||
|
|
||||||
int flags = (chunked ? FASTCGI_CHUNKED : 0) | (use_fastcgi & (FASTCGI_COMPRESS | FASTCGI_COMPRESS_HOLD));
|
|
||||||
ret = fastcgi_send(&fcgi_conn, client, flags);
|
|
||||||
} else if (use_rev_proxy) {
|
|
||||||
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
|
|
||||||
int chunked = transfer_encoding != NULL && strstr(transfer_encoding, "chunked") != NULL;
|
|
||||||
|
|
||||||
char *content_len = http_get_header_field(&res.hdr, "Content-Length");
|
|
||||||
unsigned long len_to_send = 0;
|
|
||||||
if (content_len != NULL) {
|
|
||||||
len_to_send = strtol(content_len, NULL, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
int flags = (chunked ? REV_PROXY_CHUNKED : 0) | (use_rev_proxy & REV_PROXY_COMPRESS);
|
|
||||||
ret = rev_proxy_send(client, len_to_send, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret < 0) {
|
|
||||||
client_keep_alive = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (close_proxy && rev_proxy.socket != 0) {
|
|
||||||
print(BLUE_STR "Closing proxy connection" CLR_STR);
|
|
||||||
sock_close(&rev_proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
|
||||||
micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
|
|
||||||
print("Transfer complete: %s", format_duration(micros, buf0));
|
|
||||||
|
|
||||||
uri_free(&uri);
|
|
||||||
abort:
|
|
||||||
if (fcgi_conn.socket != 0) {
|
|
||||||
shutdown(fcgi_conn.socket, SHUT_RDWR);
|
|
||||||
close(fcgi_conn.socket);
|
|
||||||
fcgi_conn.socket = 0;
|
|
||||||
}
|
|
||||||
http_free_req(&req);
|
|
||||||
http_free_res(&res);
|
|
||||||
if (client->buf != NULL) {
|
|
||||||
free(client->buf);
|
|
||||||
client->buf = NULL;
|
|
||||||
client->buf_off = 0;
|
|
||||||
client->buf_len = 0;
|
|
||||||
}
|
|
||||||
return !client_keep_alive;
|
|
||||||
}
|
|
||||||
|
|
||||||
int client_connection_handler(sock *client, unsigned long client_num) {
|
|
||||||
struct timespec begin, end;
|
|
||||||
int ret, req_num;
|
|
||||||
char buf[1024];
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &begin);
|
|
||||||
|
|
||||||
if (dns_server[0] != 0) {
|
|
||||||
sprintf(buf, "dig @%s +short +time=1 -x %s", dns_server, client_addr_str);
|
|
||||||
FILE *dig = popen(buf, "r");
|
|
||||||
if (dig == NULL) {
|
|
||||||
print(ERR_STR "Unable to start dig: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
goto dig_err;
|
|
||||||
}
|
|
||||||
unsigned long read = fread(buf, 1, sizeof(buf), dig);
|
|
||||||
ret = pclose(dig);
|
|
||||||
if (ret != 0) {
|
|
||||||
print(ERR_STR "Dig terminated with exit code %i" CLR_STR "\n", ret);
|
|
||||||
goto dig_err;
|
|
||||||
}
|
|
||||||
char *ptr = memchr(buf, '\n', read);
|
|
||||||
if (ptr == buf || ptr == NULL) {
|
|
||||||
goto dig_err;
|
|
||||||
}
|
|
||||||
ptr[-1] = 0;
|
|
||||||
client_host_str = malloc(strlen(buf) + 1);
|
|
||||||
strcpy(client_host_str, buf);
|
|
||||||
} else {
|
|
||||||
dig_err:
|
|
||||||
client_host_str = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_geoip = malloc(GEOIP_MAX_SIZE);
|
|
||||||
long str_off = 0;
|
|
||||||
for (int i = 0; i < MAX_MMDB && mmdbs[i].filename != NULL; i++) {
|
|
||||||
int gai_error, mmdb_res;
|
|
||||||
MMDB_lookup_result_s result = MMDB_lookup_string(&mmdbs[i], client_addr_str, &gai_error, &mmdb_res);
|
|
||||||
if (mmdb_res != MMDB_SUCCESS) {
|
|
||||||
print(ERR_STR "Unable to lookup geoip info: %s" CLR_STR "\n", MMDB_strerror(mmdb_res));
|
|
||||||
continue;
|
|
||||||
} else if (gai_error != 0) {
|
|
||||||
print(ERR_STR "Unable to lookup geoip info" CLR_STR "\n");
|
|
||||||
continue;
|
|
||||||
} else if (!result.found_entry) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
MMDB_entry_data_list_s *list;
|
|
||||||
mmdb_res = MMDB_get_entry_data_list(&result.entry, &list);
|
|
||||||
if (mmdb_res != MMDB_SUCCESS) {
|
|
||||||
print(ERR_STR "Unable to lookup geoip info: %s" CLR_STR "\n", MMDB_strerror(mmdb_res));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
long prev = str_off;
|
|
||||||
if (str_off != 0) {
|
|
||||||
str_off--;
|
|
||||||
}
|
|
||||||
mmdb_json(list, client_geoip, &str_off, GEOIP_MAX_SIZE);
|
|
||||||
if (prev != 0) {
|
|
||||||
client_geoip[prev - 1] = ',';
|
|
||||||
}
|
|
||||||
|
|
||||||
MMDB_free_entry_data_list(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
char client_cc[3];
|
|
||||||
client_cc[0] = 0;
|
|
||||||
if (str_off == 0) {
|
|
||||||
free(client_geoip);
|
|
||||||
client_geoip = NULL;
|
|
||||||
} else {
|
|
||||||
char *pos = client_geoip;
|
|
||||||
pos = strstr(pos, "\"country\":");
|
|
||||||
if (pos != NULL) {
|
|
||||||
pos = strstr(pos, "\"iso_code\":");
|
|
||||||
pos += 12;
|
|
||||||
snprintf(client_cc, sizeof(client_cc), "%s", pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print("Connection accepted from %s %s%s%s[%s]", client_addr_str, client_host_str != NULL ? "(" : "",
|
|
||||||
client_host_str != NULL ? client_host_str : "", client_host_str != NULL ? ") " : "",
|
|
||||||
client_cc[0] != 0 ? client_cc : "N/A");
|
|
||||||
|
|
||||||
client_timeout.tv_sec = CLIENT_TIMEOUT;
|
|
||||||
client_timeout.tv_usec = 0;
|
|
||||||
if (setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, &client_timeout, sizeof(client_timeout)) < 0)
|
|
||||||
goto set_timeout_err;
|
|
||||||
if (setsockopt(client->socket, SOL_SOCKET, SO_SNDTIMEO, &client_timeout, sizeof(client_timeout)) < 0) {
|
|
||||||
set_timeout_err:
|
|
||||||
print(ERR_STR "Unable to set timeout for socket: %s" CLR_STR, strerror(errno));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->enc) {
|
|
||||||
client->ssl = SSL_new(client->ctx);
|
|
||||||
SSL_set_fd(client->ssl, client->socket);
|
|
||||||
SSL_set_accept_state(client->ssl);
|
|
||||||
|
|
||||||
ret = SSL_accept(client->ssl);
|
|
||||||
client->_last_ret = ret;
|
|
||||||
client->_errno = errno;
|
|
||||||
client->_ssl_error = ERR_get_error();
|
|
||||||
if (ret <= 0) {
|
|
||||||
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(client));
|
|
||||||
ret = -1;
|
|
||||||
goto close;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req_num = 0;
|
|
||||||
ret = 0;
|
|
||||||
while (ret == 0 && server_keep_alive && req_num < REQ_PER_CONNECTION) {
|
|
||||||
ret = client_request_handler(client, client_num, req_num++);
|
|
||||||
log_prefix = log_conn_prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
close:
|
|
||||||
sock_close(client);
|
|
||||||
|
|
||||||
if (rev_proxy.socket != 0) {
|
|
||||||
print(BLUE_STR "Closing proxy connection" CLR_STR);
|
|
||||||
sock_close(&rev_proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &end);
|
|
||||||
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
|
|
||||||
|
|
||||||
print("Connection closed (%s)", format_duration(micros, buf));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int client_handler(sock *client, unsigned long client_num, struct sockaddr_in6 *client_addr) {
|
|
||||||
int ret;
|
|
||||||
struct sockaddr_in6 *server_addr;
|
|
||||||
struct sockaddr_storage server_addr_storage;
|
|
||||||
|
|
||||||
char *color_table[] = {"\x1B[31m", "\x1B[32m", "\x1B[33m", "\x1B[34m", "\x1B[35m", "\x1B[36m"};
|
|
||||||
|
|
||||||
signal(SIGINT, client_terminate);
|
|
||||||
signal(SIGTERM, client_terminate);
|
|
||||||
|
|
||||||
client_addr_str_ptr = malloc(INET6_ADDRSTRLEN);
|
|
||||||
inet_ntop(client_addr->sin6_family, (void *) &client_addr->sin6_addr, client_addr_str_ptr, INET6_ADDRSTRLEN);
|
|
||||||
if (strncmp(client_addr_str_ptr, "::ffff:", 7) == 0) {
|
|
||||||
client_addr_str = client_addr_str_ptr + 7;
|
|
||||||
} else {
|
|
||||||
client_addr_str = client_addr_str_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
socklen_t len = sizeof(server_addr_storage);
|
|
||||||
getsockname(client->socket, (struct sockaddr *) &server_addr_storage, &len);
|
|
||||||
server_addr = (struct sockaddr_in6 *) &server_addr_storage;
|
|
||||||
server_addr_str_ptr = malloc(INET6_ADDRSTRLEN);
|
|
||||||
inet_ntop(server_addr->sin6_family, (void *) &server_addr->sin6_addr, server_addr_str_ptr, INET6_ADDRSTRLEN);
|
|
||||||
if (strncmp(server_addr_str_ptr, "::ffff:", 7) == 0) {
|
|
||||||
server_addr_str = server_addr_str_ptr + 7;
|
|
||||||
} else {
|
|
||||||
server_addr_str = server_addr_str_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
log_req_prefix = malloc(256);
|
|
||||||
log_client_prefix = malloc(256);
|
|
||||||
sprintf(log_client_prefix, "[%s%4i%s]%s[%*s][%5i]%s", (int) client->enc ? HTTPS_STR : HTTP_STR,
|
|
||||||
ntohs(server_addr->sin6_port), CLR_STR, color_table[client_num % 6], INET6_ADDRSTRLEN, client_addr_str,
|
|
||||||
ntohs(client_addr->sin6_port), CLR_STR);
|
|
||||||
|
|
||||||
log_conn_prefix = malloc(256);
|
|
||||||
sprintf(log_conn_prefix, "[%6i][%*s]%s ", getpid(), INET6_ADDRSTRLEN, server_addr_str, log_client_prefix);
|
|
||||||
log_prefix = log_conn_prefix;
|
|
||||||
|
|
||||||
print("Started child process with PID %i", getpid());
|
|
||||||
|
|
||||||
ret = client_connection_handler(client, client_num);
|
|
||||||
|
|
||||||
free(client_addr_str_ptr);
|
|
||||||
client_addr_str_ptr = NULL;
|
|
||||||
free(server_addr_str_ptr);
|
|
||||||
server_addr_str_ptr = NULL;
|
|
||||||
if (client_host_str != NULL) {
|
|
||||||
free(client_host_str);
|
|
||||||
client_host_str = NULL;
|
|
||||||
}
|
|
||||||
free(log_conn_prefix);
|
|
||||||
log_conn_prefix = NULL;
|
|
||||||
free(log_req_prefix);
|
|
||||||
log_req_prefix = NULL;
|
|
||||||
free(log_client_prefix);
|
|
||||||
log_client_prefix = NULL;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
28
src/defs.h
Normal file
28
src/defs.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* 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 "5.0-wip"
|
||||||
|
#define SERVER_STR "sesimos/" SERVER_VERSION
|
||||||
|
#define SERVER_STR_HTML "sesimos web server " SERVER_VERSION
|
||||||
|
|
||||||
|
#define CHUNK_SIZE 8192
|
||||||
|
#define MAX_PROXY_CNX_PER_HOST 16
|
||||||
|
#define ADDRSTRLEN 39
|
||||||
|
|
||||||
|
#ifndef DEFAULT_HOST
|
||||||
|
# define DEFAULT_HOST "www.necronda.net"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SERVER_NAME
|
||||||
|
# define SERVER_NAME DEFAULT_HOST
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //SESIMOS_DEF_H
|
397
src/lib/cache.c
397
src/lib/cache.c
@ -1,397 +0,0 @@
|
|||||||
/**
|
|
||||||
* Necronda Web Server
|
|
||||||
* File cache implementation
|
|
||||||
* src/lib/cache.c
|
|
||||||
* Lorenz Stechauner, 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/sha.h>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
int cache_continue = 1;
|
|
||||||
magic_t magic;
|
|
||||||
cache_entry *cache;
|
|
||||||
|
|
||||||
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, 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() {
|
|
||||||
cache_continue = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cache_process() {
|
|
||||||
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 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/", 0755) < 0 && errno != EEXIST) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda/': %s" CLR_STR "\n", strerror(errno));
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mkdir("/var/necronda/server/", 0755) < 0 && errno != EEXIST) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda/server/': %s" CLR_STR "\n", strerror(errno));
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *cache_file = fopen("/var/necronda/server/cache", "rb");
|
|
||||||
if (cache_file != NULL) {
|
|
||||||
fread(cache, sizeof(cache_entry), CACHE_ENTRIES, cache_file);
|
|
||||||
fclose(cache_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < CACHE_ENTRIES; i++) {
|
|
||||||
cache[i].is_updating = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *file;
|
|
||||||
char *buf = malloc(CACHE_BUF_SIZE);
|
|
||||||
char *comp_buf = malloc(CACHE_BUF_SIZE);
|
|
||||||
char filename_comp_gz[256];
|
|
||||||
char filename_comp_br[256];
|
|
||||||
unsigned long read;
|
|
||||||
int compress;
|
|
||||||
SHA_CTX ctx;
|
|
||||||
unsigned char hash[SHA_DIGEST_LENGTH];
|
|
||||||
int cache_changed = 0;
|
|
||||||
int p_len_gz, p_len_br;
|
|
||||||
int ret;
|
|
||||||
while (cache_continue) {
|
|
||||||
for (int i = 0; i < CACHE_ENTRIES; i++) {
|
|
||||||
if (cache[i].filename[0] != 0 && cache[i].meta.etag[0] == 0 && !cache[i].is_updating) {
|
|
||||||
cache[i].is_updating = 1;
|
|
||||||
fprintf(stdout, "[cache] Hashing file %s\n", cache[i].filename);
|
|
||||||
SHA1_Init(&ctx);
|
|
||||||
file = fopen(cache[i].filename, "rb");
|
|
||||||
compress = 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/.necronda-server", 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/.necronda-server/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/.necronda-server/cache/%s.gz",
|
|
||||||
cache[i].webroot_len, cache[i].filename, buf);
|
|
||||||
p_len_br = snprintf(filename_comp_br, sizeof(filename_comp_br),
|
|
||||||
"%.*s/.necronda-server/cache/%s.br",
|
|
||||||
cache[i].webroot_len, cache[i].filename, buf);
|
|
||||||
if (p_len_gz < 0 || p_len_gz >= sizeof(filename_comp_gz) ||
|
|
||||||
p_len_br < 0 || p_len_br >= sizeof(filename_comp_br))
|
|
||||||
{
|
|
||||||
fprintf(stderr, ERR_STR "Unable to open cached file: "
|
|
||||||
"File name for compressed file too long" CLR_STR "\n");
|
|
||||||
goto comp_err;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stdout, "[cache] Compressing file %s\n", cache[i].filename);
|
|
||||||
|
|
||||||
comp_file_gz = fopen(filename_comp_gz, "wb");
|
|
||||||
comp_file_br = fopen(filename_comp_br, "wb");
|
|
||||||
if (comp_file_gz == NULL || comp_file_br == NULL) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to open cached file: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
comp_err:
|
|
||||||
compress = 0;
|
|
||||||
} 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) {
|
|
||||||
SHA1_Update(&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));
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
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/necronda/server/cache", "wb");
|
|
||||||
if (cache_file == NULL) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to open cache file: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
free(buf);
|
|
||||||
free(comp_buf);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
fwrite(cache, sizeof(cache_entry), CACHE_ENTRIES, cache_file);
|
|
||||||
fclose(cache_file);
|
|
||||||
} else {
|
|
||||||
sleep(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
free(comp_buf);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cache_init() {
|
|
||||||
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 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, 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() {
|
|
||||||
int shm_id = shmget(CACHE_SHM_KEY, 0, 0);
|
|
||||||
if (shm_id < 0) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
shmdt(cache);
|
|
||||||
return -1;
|
|
||||||
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
shmdt(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 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 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;
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
/**
|
|
||||||
* Necronda Web Server
|
|
||||||
* File cache implementation (header file)
|
|
||||||
* src/lib/cache.h
|
|
||||||
* Lorenz Stechauner, 2020-12-19
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_CACHE_H
|
|
||||||
#define NECRONDA_SERVER_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 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
|
|
@ -1,35 +1,37 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* Compression interface
|
* @brief Compression interface
|
||||||
* src/lib/compress.c
|
* @file src/lib/compress.c
|
||||||
* Lorenz Stechauner, 2021-05-05
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-05-05
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "compress.h"
|
#include "compress.h"
|
||||||
#include <malloc.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
|
||||||
int compress_init(compress_ctx *ctx, int mode) {
|
int compress_init(compress_ctx *ctx, int mode) {
|
||||||
ctx->gzip = NULL;
|
|
||||||
ctx->brotli = NULL;
|
ctx->brotli = NULL;
|
||||||
ctx->mode = 0;
|
|
||||||
int ret;
|
|
||||||
if (mode & COMPRESS_GZ) {
|
if (mode & COMPRESS_GZ) {
|
||||||
ctx->mode |= COMPRESS_GZ;
|
ctx->mode |= COMPRESS_GZ;
|
||||||
ctx->gzip = malloc(sizeof(z_stream));
|
ctx->gzip.zalloc = Z_NULL;
|
||||||
ctx->gzip->zalloc = Z_NULL;
|
ctx->gzip.zfree = Z_NULL;
|
||||||
ctx->gzip->zfree = Z_NULL;
|
ctx->gzip.opaque = Z_NULL;
|
||||||
ctx->gzip->opaque = Z_NULL;
|
ctx->gzip.data_type = (mode & COMPRESS_UTF8) ? Z_TEXT : Z_UNKNOWN;
|
||||||
ret = deflateInit2(ctx->gzip, COMPRESS_LEVEL_GZIP, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY);
|
if (deflateInit2(&ctx->gzip, COMPRESS_LEVEL_GZIP, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY) != Z_OK)
|
||||||
if (ret != Z_OK) return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode & COMPRESS_BR) {
|
if (mode & COMPRESS_BR) {
|
||||||
ctx->mode |= COMPRESS_BR;
|
ctx->mode |= COMPRESS_BR;
|
||||||
ctx->brotli = BrotliEncoderCreateInstance(NULL, NULL, NULL);
|
if ((ctx->brotli = BrotliEncoderCreateInstance(NULL, NULL, NULL)) == NULL)
|
||||||
if (ctx->brotli == NULL) return -1;
|
return -1;
|
||||||
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_MODE, BROTLI_MODE_GENERIC);
|
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_MODE, (mode & COMPRESS_UTF8) ? BROTLI_MODE_TEXT : ((mode & COMPRESS_WOFF) ? BROTLI_MODE_FONT : BROTLI_MODE_GENERIC));
|
||||||
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_QUALITY, COMPRESS_LEVEL_BROTLI);
|
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_QUALITY, COMPRESS_LEVEL_BROTLI);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,39 +43,46 @@ int compress_compress(compress_ctx *ctx, const char *in, unsigned long *in_len,
|
|||||||
return compress_compress_mode(ctx, ctx->mode, in, in_len, out, out_len, finish);
|
return compress_compress_mode(ctx, ctx->mode, in, in_len, out, out_len, finish);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int compress_brotli(BrotliEncoderState *ctx, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish) {
|
||||||
|
int ret = BrotliEncoderCompressStream(
|
||||||
|
ctx, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compress_gzip(z_stream *gzip, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish) {
|
||||||
|
gzip->next_in = (unsigned char*) in;
|
||||||
|
gzip->avail_in = *in_len;
|
||||||
|
gzip->next_out = (unsigned char*) out;
|
||||||
|
gzip->avail_out = *out_len;
|
||||||
|
int ret = deflate(gzip, finish ? Z_FINISH : Z_NO_FLUSH);
|
||||||
|
*in_len = gzip->avail_in;
|
||||||
|
*out_len = gzip->avail_out;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
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_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)) {
|
if ((mode & COMPRESS_GZ) && (mode & COMPRESS_BR)) {
|
||||||
errno = EINVAL;
|
errno = EINVAL;
|
||||||
return -1;
|
return -1;
|
||||||
} else if (mode & COMPRESS_GZ) {
|
} else if (mode & COMPRESS_GZ) {
|
||||||
ctx->gzip->next_in = (unsigned char*) in;
|
return compress_gzip(&ctx->gzip, in, in_len, out, out_len, finish);
|
||||||
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) {
|
} else if (mode & COMPRESS_BR) {
|
||||||
int ret = BrotliEncoderCompressStream(ctx->brotli, finish ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
|
return compress_brotli(ctx->brotli, in, in_len, out, out_len, finish);
|
||||||
in_len, (const unsigned char**) &in, out_len, (unsigned char **) &out, NULL);
|
|
||||||
return (ret == BROTLI_TRUE) ? 0 : -1;
|
|
||||||
} else {
|
} else {
|
||||||
errno = EINVAL;
|
errno = EINVAL;
|
||||||
return -2;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int compress_free(compress_ctx *ctx) {
|
int compress_free(compress_ctx *ctx) {
|
||||||
if (ctx->gzip != NULL) {
|
|
||||||
deflateEnd(ctx->gzip);
|
|
||||||
free(ctx->gzip);
|
|
||||||
ctx->gzip = NULL;
|
|
||||||
}
|
|
||||||
if (ctx->brotli != NULL) {
|
if (ctx->brotli != NULL) {
|
||||||
BrotliEncoderDestroyInstance(ctx->brotli);
|
BrotliEncoderDestroyInstance(ctx->brotli);
|
||||||
ctx->brotli = NULL;
|
ctx->brotli = NULL;
|
||||||
}
|
}
|
||||||
|
if (ctx->mode & COMPRESS_GZ) {
|
||||||
|
deflateEnd(&ctx->gzip);
|
||||||
|
}
|
||||||
ctx->mode = 0;
|
ctx->mode = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* Compression interface (header file)
|
* @brief Compression interface (header file)
|
||||||
* src/lib/compress.h
|
* @file src/lib/compress.h
|
||||||
* Lorenz Stechauner, 2021-05-05
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-05-05
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_COMPRESS_H
|
#ifndef SESIMOS_COMPRESS_H
|
||||||
#define NECRONDA_SERVER_COMPRESS_H
|
#define SESIMOS_COMPRESS_H
|
||||||
|
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
#include <brotli/encode.h>
|
#include <brotli/encode.h>
|
||||||
@ -17,21 +18,21 @@
|
|||||||
#define COMPRESS_GZ 1
|
#define COMPRESS_GZ 1
|
||||||
#define COMPRESS_BR 2
|
#define COMPRESS_BR 2
|
||||||
#define COMPRESS 3
|
#define COMPRESS 3
|
||||||
|
#define COMPRESS_UTF8 4
|
||||||
|
#define COMPRESS_WOFF 8
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int mode;
|
int mode;
|
||||||
z_stream *gzip;
|
z_stream gzip;
|
||||||
BrotliEncoderState *brotli;
|
BrotliEncoderState *brotli;
|
||||||
} compress_ctx;
|
} compress_ctx;
|
||||||
|
|
||||||
int compress_init(compress_ctx *ctx, int mode);
|
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 compress_compress(compress_ctx *ctx, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish);
|
||||||
int finish);
|
|
||||||
|
|
||||||
int compress_compress_mode(compress_ctx *ctx, int mode, const char *in, unsigned long *in_len, char *out,
|
int compress_compress_mode(compress_ctx *ctx, int mode, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish);
|
||||||
unsigned long *out_len, int finish);
|
|
||||||
|
|
||||||
int compress_free(compress_ctx *ctx);
|
int compress_free(compress_ctx *ctx);
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_COMPRESS_H
|
#endif //SESIMOS_COMPRESS_H
|
||||||
|
404
src/lib/config.c
404
src/lib/config.c
@ -1,274 +1,228 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* Configuration file loader
|
* @brief Configuration file loader
|
||||||
* src/lib/config.c
|
* @file src/lib/config.c
|
||||||
* Lorenz Stechauner, 2021-01-05
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-01-05
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "../logger.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/ipc.h>
|
|
||||||
#include <sys/shm.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
t_config *config;
|
config_t config;
|
||||||
char geoip_dir[256], dns_server[256];
|
|
||||||
|
|
||||||
int config_init() {
|
static int config_parse_line(char *line, char *section, int *i, int *j) {
|
||||||
int shm_id = shmget(CONFIG_SHM_KEY, sizeof(t_config), IPC_CREAT | IPC_EXCL | 0640);
|
int mode = 0;
|
||||||
if (shm_id < 0) {
|
char *source, *target;
|
||||||
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
|
char *ptr = line;
|
||||||
|
char *comment = strpbrk(ptr, "#\r\n");
|
||||||
|
if (comment != NULL) comment[0] = 0;
|
||||||
|
|
||||||
|
unsigned long len = strlen(ptr);
|
||||||
|
char *end_ptr = (len > 0) ? ptr + len - 1 : ptr;
|
||||||
|
while (end_ptr[0] == ' ' || end_ptr[0] == '\t') {
|
||||||
|
end_ptr[0] = 0;
|
||||||
|
end_ptr--;
|
||||||
|
}
|
||||||
|
len = strlen(ptr);
|
||||||
|
if (len == 0) return 0;
|
||||||
|
|
||||||
|
if (ptr[0] == '[') {
|
||||||
|
if (ptr[len - 1] != ']') return -1;
|
||||||
|
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) return -1;
|
||||||
|
snprintf(config.hosts[*i].name, sizeof(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) return -1;
|
||||||
|
snprintf(config.certs[*j].name, sizeof(config.certs[*j].name), "%.*s", l, ptr);
|
||||||
|
++(*j);
|
||||||
|
*section = 'c';
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} else if (*section == 0) {
|
||||||
|
if (len > 10 && strncmp(ptr, "geoip_dir", 9) == 0 && (ptr[9] == ' ' || ptr[9] == '\t')) {
|
||||||
|
source = ptr + 9;
|
||||||
|
target = config.geoip_dir;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (*section == 'c') {
|
||||||
|
cert_config_t *cc = &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 {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (*section == 'h') {
|
||||||
|
host_config_t *hc = &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) {
|
||||||
|
return -1;
|
||||||
|
} 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) {
|
||||||
|
return -1;
|
||||||
|
} 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->proxy.hostname;
|
||||||
|
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||||
|
return -1;
|
||||||
|
} 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) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||||
|
}
|
||||||
|
} else if (streq(ptr, "http")) {
|
||||||
|
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||||
|
hc->proxy.enc = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} else if (streq(ptr, "https")) {
|
||||||
|
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
hc->type = CONFIG_TYPE_REVERSE_PROXY;
|
||||||
|
hc->proxy.enc = 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
|
while (source[0] == ' ' || source[0] == '\t') source++;
|
||||||
if (shm == (void *) -1) {
|
if (strlen(source) == 0) return -1;
|
||||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
config = shm;
|
|
||||||
|
|
||||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
if (target != NULL) {
|
||||||
if (shm_rw == (void *) -1) {
|
strcpy(target, source);
|
||||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
} else if (mode == 1) {
|
||||||
return -3;
|
if (streq(source, "forbidden")) {
|
||||||
|
config.hosts[*i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN;
|
||||||
|
} else if (streq(source, "info")) {
|
||||||
|
config.hosts[*i - 1].local.dir_mode = URI_DIR_MODE_INFO;
|
||||||
|
} else if (streq(source, "list")) {
|
||||||
|
config.hosts[*i - 1].local.dir_mode = URI_DIR_MODE_LIST;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (mode == 2) {
|
||||||
|
config.hosts[*i - 1].proxy.port = (unsigned short) strtoul(source, NULL, 10);
|
||||||
}
|
}
|
||||||
config = shm_rw;
|
|
||||||
memset(config, 0, sizeof(t_config));
|
|
||||||
shmdt(shm_rw);
|
|
||||||
config = shm;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int config_unload() {
|
|
||||||
int shm_id = shmget(CONFIG_SHM_KEY, 0, 0);
|
|
||||||
if (shm_id < 0) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
shmdt(config);
|
|
||||||
return -1;
|
|
||||||
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
shmdt(config);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
shmdt(config);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int config_load(const char *filename) {
|
int config_load(const char *filename) {
|
||||||
FILE *file = fopen(filename, "r");
|
FILE *file = fopen(filename, "r");
|
||||||
if (file == NULL) {
|
if (file == NULL) {
|
||||||
fprintf(stderr, ERR_STR "Unable to open config file: %s" CLR_STR "\n", strerror(errno));
|
critical("Unable to open config file");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fseek(file, 0, SEEK_END);
|
memset(&config, 0, sizeof(config));
|
||||||
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));
|
int i = 0, j = 0;
|
||||||
memset(tmp_config, 0, sizeof(t_config));
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
int j = 0;
|
|
||||||
int line = 0;
|
|
||||||
int mode = 0;
|
|
||||||
char section = 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 *line = NULL;
|
||||||
char *end_ptr = ptr + len - 1;
|
size_t line_len = 0;
|
||||||
while (end_ptr[0] == ' ' || end_ptr[0] == '\t') {
|
for (int line_num = 1; getline(&line, &line_len, file) != -1; line_num++) {
|
||||||
end_ptr[0] = 0;
|
if (config_parse_line(line, §ion, &i, &j) != 0) {
|
||||||
end_ptr--;
|
critical("Unable to parse config file (line %i)", line_num);
|
||||||
}
|
free(line);
|
||||||
len = strlen(ptr);
|
fclose(file);
|
||||||
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;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(line);
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
for (int k = 0; k < i; k++) {
|
for (int k = 0; k < i; k++) {
|
||||||
host_config *hc = &tmp_config->hosts[k];
|
host_config_t *hc = &config.hosts[k];
|
||||||
if (hc->type == CONFIG_TYPE_LOCAL) {
|
if (hc->type == CONFIG_TYPE_LOCAL) {
|
||||||
char *webroot = tmp_config->hosts[k].local.webroot;
|
char *webroot = config.hosts[k].local.webroot;
|
||||||
if (webroot[strlen(webroot) - 1] == '/') {
|
while (webroot[strlen(webroot) - 1] == '/') webroot[strlen(webroot) - 1] = 0;
|
||||||
webroot[strlen(webroot) - 1] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (hc->cert_name[0] == 0) goto err2;
|
|
||||||
|
if (hc->cert_name[0] == 0) {
|
||||||
|
critical("Unable to parse config file: host config (%s) does not contain a certificate", hc->name);
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
int found = 0;
|
int found = 0;
|
||||||
for (int m = 0; m < j; m++) {
|
for (int m = 0; m < j; m++) {
|
||||||
if (strcmp(tmp_config->certs[m].name, hc->cert_name) == 0) {
|
if (streq(config.certs[m].name, hc->cert_name)) {
|
||||||
hc->cert = m;
|
hc->cert = m;
|
||||||
found = 1;
|
found = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
err2:
|
critical("Unable to parse config file: certificate (%s) for host config (%s) not found", hc->cert_name, hc->name);
|
||||||
free(tmp_config);
|
|
||||||
fprintf(stderr, ERR_STR "Unable to parse config file" CLR_STR "\n");
|
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int shm_id = shmget(CONFIG_SHM_KEY, 0, 0);
|
|
||||||
if (shm_id < 0) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
shmdt(config);
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *shm_rw = shmat(shm_id, NULL, 0);
|
|
||||||
if (shm_rw == (void *) -1) {
|
|
||||||
free(tmp_config);
|
|
||||||
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
|
|
||||||
return -4;
|
|
||||||
}
|
|
||||||
memcpy(shm_rw, tmp_config, sizeof(t_config));
|
|
||||||
free(tmp_config);
|
|
||||||
shmdt(shm_rw);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
host_config_t *get_host_config(const char *host) {
|
||||||
|
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
|
||||||
|
host_config_t *hc = &config.hosts[i];
|
||||||
|
if (hc->type == CONFIG_TYPE_UNSET) break;
|
||||||
|
if (streq(hc->name, host)) return hc;
|
||||||
|
if (hc->name[0] == '*' && hc->name[1] == '.') {
|
||||||
|
const char *pos = strstr(host, hc->name + 1);
|
||||||
|
if (pos != NULL && strlen(pos) == strlen(hc->name + 1)) return hc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* Configuration file loader (header file)
|
* @brief Configuration file loader (header file)
|
||||||
* src/lib/config.h
|
* @file src/lib/config.h
|
||||||
* Lorenz Stechauner, 2021-01-05
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-01-05
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_CONFIG_H
|
#ifndef SESIMOS_CONFIG_H
|
||||||
#define NECRONDA_SERVER_CONFIG_H
|
#define SESIMOS_CONFIG_H
|
||||||
|
|
||||||
#include "uri.h"
|
#include "uri.h"
|
||||||
|
#include "../cache_handler.h"
|
||||||
|
|
||||||
#define CONFIG_SHM_KEY 255642
|
|
||||||
#define CONFIG_MAX_HOST_CONFIG 64
|
#define CONFIG_MAX_HOST_CONFIG 64
|
||||||
#define CONFIG_MAX_CERT_CONFIG 64
|
#define CONFIG_MAX_CERT_CONFIG 64
|
||||||
|
|
||||||
@ -19,7 +20,7 @@
|
|||||||
#define CONFIG_TYPE_REVERSE_PROXY 2
|
#define CONFIG_TYPE_REVERSE_PROXY 2
|
||||||
|
|
||||||
#ifndef DEFAULT_CONFIG_FILE
|
#ifndef DEFAULT_CONFIG_FILE
|
||||||
# define DEFAULT_CONFIG_FILE "/etc/necronda/server.conf"
|
# define DEFAULT_CONFIG_FILE "/etc/sesimos/server.conf"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -28,37 +29,36 @@ typedef struct {
|
|||||||
char name[256];
|
char name[256];
|
||||||
char cert_name[256];
|
char cert_name[256];
|
||||||
int cert;
|
int cert;
|
||||||
|
cache_t *cache;
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
char hostname[256];
|
char hostname[256];
|
||||||
unsigned short port;
|
unsigned short port;
|
||||||
unsigned char enc:1;
|
unsigned char enc:1;
|
||||||
} rev_proxy;
|
} proxy;
|
||||||
struct {
|
struct {
|
||||||
char webroot[256];
|
char webroot[256];
|
||||||
unsigned char dir_mode:2;
|
unsigned char dir_mode:2;
|
||||||
} local;
|
} local;
|
||||||
};
|
};
|
||||||
} host_config;
|
} host_config_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char name[256];
|
char name[256];
|
||||||
char full_chain[256];
|
char full_chain[256];
|
||||||
char priv_key[256];
|
char priv_key[256];
|
||||||
} cert_config;
|
} cert_config_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
host_config hosts[CONFIG_MAX_HOST_CONFIG];
|
host_config_t hosts[CONFIG_MAX_HOST_CONFIG];
|
||||||
cert_config certs[CONFIG_MAX_CERT_CONFIG];
|
cert_config_t certs[CONFIG_MAX_CERT_CONFIG];
|
||||||
} t_config;
|
char geoip_dir[256];
|
||||||
|
} config_t;
|
||||||
|
|
||||||
extern t_config *config;
|
extern config_t config;
|
||||||
extern char geoip_dir[256], dns_server[256];
|
|
||||||
|
|
||||||
int config_init();
|
|
||||||
|
|
||||||
int config_load(const char *filename);
|
int config_load(const char *filename);
|
||||||
|
|
||||||
int config_unload();
|
host_config_t *get_host_config(const char *host);
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_CONFIG_H
|
#endif //SESIMOS_CONFIG_H
|
||||||
|
87
src/lib/error.c
Normal file
87
src/lib/error.c
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Error interface
|
||||||
|
* @file src/lib/error.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2023-01-08
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "error.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
|
extern const char *sock_error_str(unsigned long err);
|
||||||
|
extern const char *http_error_str(int err);
|
||||||
|
extern const char *MMDB_strerror(int err);
|
||||||
|
extern const char *ERR_reason_error_string(unsigned long err);
|
||||||
|
|
||||||
|
static int error_compress(unsigned long err) {
|
||||||
|
int comp = ((int) err & 0xFFFF) | (((int) err >> 8) & 0xFF0000);
|
||||||
|
if (err & 0xFF0000) warning("Lossy error code compression! (%08lX -> %08X)", err, comp);
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long error_decompress(int err) {
|
||||||
|
return (err & 0xFFFF) | ((err << 8) & 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *error_str(int err_no, char *buf, int buf_len) {
|
||||||
|
buf[0] = 0;
|
||||||
|
int e = err_no & 0x00FFFFFF;
|
||||||
|
switch (err_no >> 24) {
|
||||||
|
case 0x00: return strerror_r(e, buf, buf_len);
|
||||||
|
case 0x01: return sock_error_str(error_decompress(e));
|
||||||
|
case 0x02: return ERR_reason_error_string(error_decompress(e));
|
||||||
|
case 0x03: return MMDB_strerror(e);
|
||||||
|
case 0x04: return http_error_str(e);
|
||||||
|
case 0x05: return gai_strerror(e);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_ssl(unsigned long err) {
|
||||||
|
errno = 0x01000000 | error_compress(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_ssl_err(unsigned long err) {
|
||||||
|
errno = 0x02000000 | error_compress(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_mmdb(int err) {
|
||||||
|
errno = 0x03000000 | err;
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_http(int err) {
|
||||||
|
errno = 0x04000000 | err;
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_gai(int err) {
|
||||||
|
errno = 0x05000000 | err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int error_get(unsigned char prefix) {
|
||||||
|
return (errno >> 24 != prefix) ? 0 : errno & 0x00FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
int error_get_sys() {
|
||||||
|
return error_get(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
int error_get_ssl() {
|
||||||
|
return error_get(0x01);
|
||||||
|
}
|
||||||
|
|
||||||
|
int error_get_ssl_err() {
|
||||||
|
return error_get(0x02);
|
||||||
|
}
|
||||||
|
|
||||||
|
int error_get_mmdb() {
|
||||||
|
return error_get(0x03);
|
||||||
|
}
|
||||||
|
|
||||||
|
int error_get_http() {
|
||||||
|
return error_get(0x04);
|
||||||
|
}
|
32
src/lib/error.h
Normal file
32
src/lib/error.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Error interface (header fie)
|
||||||
|
* @file src/lib/error.h
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2023-01-08
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SESIMOS_ERROR_H
|
||||||
|
#define SESIMOS_ERROR_H
|
||||||
|
|
||||||
|
const char *error_str(int err_no, char *buf, int buf_len);
|
||||||
|
|
||||||
|
void error_ssl(unsigned long err);
|
||||||
|
|
||||||
|
void error_ssl_err(unsigned long err);
|
||||||
|
|
||||||
|
void error_mmdb(int err);
|
||||||
|
|
||||||
|
void error_http(int err);
|
||||||
|
|
||||||
|
void error_gai(int err);
|
||||||
|
|
||||||
|
int error_get_sys();
|
||||||
|
|
||||||
|
int error_get_ssl();
|
||||||
|
|
||||||
|
int error_get_mmdb();
|
||||||
|
|
||||||
|
int error_get_http();
|
||||||
|
|
||||||
|
#endif //SESIMOS_ERROR_H
|
@ -1,43 +1,42 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* FastCGI interface implementation
|
* @brief FastCGI interface implementation
|
||||||
* src/lib/fastcgi.c
|
* @file src/lib/fastcgi.c
|
||||||
* Lorenz Stechauner, 2020-12-26
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-26
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "../defs.h"
|
||||||
#include "fastcgi.h"
|
#include "fastcgi.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "compress.h"
|
#include "../logger.h"
|
||||||
#include "../necronda-server.h"
|
#include "list.h"
|
||||||
|
#include "../workers.h"
|
||||||
|
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <errno.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
char *fastcgi_add_param(char *buf, const char *key, const char *value) {
|
char *fastcgi_add_param(char *buf, const char *key, const char *value) {
|
||||||
char *ptr = buf;
|
char *ptr = buf;
|
||||||
unsigned long key_len = strlen(key);
|
unsigned long key_len = strlen(key);
|
||||||
unsigned long val_len = strlen(value);
|
unsigned long val_len = strlen(value);
|
||||||
|
|
||||||
|
|
||||||
if (key_len <= 127) {
|
if (key_len <= 127) {
|
||||||
ptr[0] = (char) (key_len & 0x7F);
|
ptr[0] = (char) (key_len & 0x7F);
|
||||||
ptr++;
|
ptr++;
|
||||||
} else {
|
} else {
|
||||||
ptr[0] = (char) (0x80 | (key_len >> 24));
|
*((int *) ptr) = htonl(0x80000000 | key_len);
|
||||||
ptr[1] = (char) ((key_len >> 16) & 0xFF);
|
|
||||||
ptr[2] = (char) ((key_len >> 8) & 0xFF);
|
|
||||||
ptr[3] = (char) (key_len & 0xFF);
|
|
||||||
ptr += 4;
|
ptr += 4;
|
||||||
}
|
}
|
||||||
if (val_len <= 127) {
|
if (val_len <= 127) {
|
||||||
ptr[0] = (char) (val_len & 0x7F);
|
ptr[0] = (char) (val_len & 0x7F);
|
||||||
ptr++;
|
ptr++;
|
||||||
} else {
|
} else {
|
||||||
ptr[0] = (char) (0x80 | (val_len >> 24));
|
*((int *) ptr) = htonl(0x80000000 | val_len);
|
||||||
ptr[1] = (char) ((val_len >> 16) & 0xFF);
|
|
||||||
ptr[2] = (char) ((val_len >> 8) & 0xFF);
|
|
||||||
ptr[3] = (char) (val_len & 0xFF);
|
|
||||||
ptr += 4;
|
ptr += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,63 +48,70 @@ char *fastcgi_add_param(char *buf, const char *key, const char *value) {
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned int req_num, const sock *client,
|
int fastcgi_send_data(fastcgi_cnx_t *cnx, unsigned char type, unsigned short len, void *data) {
|
||||||
const http_req *req, const http_uri *uri) {
|
// build header
|
||||||
unsigned short req_id = (client_num & 0xFFF) << 4;
|
|
||||||
if (client_num == 0) {
|
|
||||||
req_id |= (req_num + 1) & 0xF;
|
|
||||||
} 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 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 = fcgi_sock;
|
|
||||||
|
|
||||||
struct sockaddr_un sock_addr = {AF_UNIX};
|
|
||||||
if (conn->mode == FASTCGI_NECRONDA) {
|
|
||||||
snprintf(sock_addr.sun_path, sizeof(sock_addr.sun_path) - 1, "%s", NECRONDA_BACKEND_SOCKET);
|
|
||||||
} else if (conn->mode == FASTCGI_PHP) {
|
|
||||||
snprintf(sock_addr.sun_path, sizeof(sock_addr.sun_path) - 1, "%s", PHP_FPM_SOCKET);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connect(conn->socket, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) < 0) {
|
|
||||||
print(ERR_STR "Unable to connect to unix socket of FastCGI socket: %s" CLR_STR, strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
FCGI_Header header = {
|
FCGI_Header header = {
|
||||||
.version = FCGI_VERSION_1,
|
.version = FCGI_VERSION_1,
|
||||||
.requestIdB1 = req_id >> 8,
|
.type = type,
|
||||||
.requestIdB0 = req_id & 0xFF,
|
.requestId = htons(cnx->req_id),
|
||||||
|
.contentLength = htons(len),
|
||||||
.paddingLength = 0,
|
.paddingLength = 0,
|
||||||
.reserved = 0
|
.reserved = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
header.type = FCGI_BEGIN_REQUEST;
|
// send FastCGI header with MSG_MORE flag
|
||||||
header.contentLengthB1 = 0;
|
if (sock_send_x(&cnx->socket, &header, sizeof(header), (len != 0) ? MSG_MORE : 0) == -1) {
|
||||||
header.contentLengthB0 = sizeof(FCGI_BeginRequestBody);
|
error("Unable to send to FastCGI socket");
|
||||||
FCGI_BeginRequestRecord begin = {
|
return -1;
|
||||||
header,
|
|
||||||
{.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 FastCGI socket: %s" CLR_STR, strerror(errno));
|
|
||||||
return -2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char param_buf[4096];
|
// send data (if available)
|
||||||
char buf0[256];
|
if (sock_send_x(&cnx->socket, data, len, 0) == -1) {
|
||||||
char *param_ptr = param_buf + sizeof(header);
|
error("Unable to send to FastCGI socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return bytes sent totally
|
||||||
|
return len + (int) sizeof(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fastcgi_init(fastcgi_cnx_t *conn, int mode, unsigned int req_num, const sock *client, const http_req *req, const http_uri *uri) {
|
||||||
|
conn->mode = mode;
|
||||||
|
conn->header_sent = 0;
|
||||||
|
conn->req_id = (req_num + 1) & 0xFFFF;
|
||||||
|
conn->webroot = uri->webroot;
|
||||||
|
conn->err = NULL;
|
||||||
|
conn->fd_err_bytes = 0;
|
||||||
|
conn->fd_out = -1;
|
||||||
|
conn->fd_err = -1;
|
||||||
|
sock_init(&conn->out, -1, SOCK_PIPE);
|
||||||
|
|
||||||
|
conn->socket.enc = 0;
|
||||||
|
if ((conn->socket.socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
||||||
|
error("Unable to create unix socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_un sock_addr = { AF_UNIX };
|
||||||
|
if (conn->mode == FASTCGI_BACKEND_PHP) {
|
||||||
|
strcpy(sock_addr.sun_path, PHP_FPM_SOCKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(conn->socket.socket, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) != 0) {
|
||||||
|
error("Unable to connect to FastCGI (unix) socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FCGI_BeginRequestBody begin = {
|
||||||
|
.role = htons(FCGI_RESPONDER),
|
||||||
|
.flags = 0,
|
||||||
|
.reserved = {0},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fastcgi_send_data(conn, FCGI_BEGIN_REQUEST, sizeof(begin), &begin) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
char param_buf[4096], buf0[256], *param_ptr = param_buf;
|
||||||
param_ptr = fastcgi_add_param(param_ptr, "REDIRECT_STATUS", "CGI");
|
param_ptr = fastcgi_add_param(param_ptr, "REDIRECT_STATUS", "CGI");
|
||||||
param_ptr = fastcgi_add_param(param_ptr, "DOCUMENT_ROOT", uri->webroot);
|
param_ptr = fastcgi_add_param(param_ptr, "DOCUMENT_ROOT", uri->webroot);
|
||||||
param_ptr = fastcgi_add_param(param_ptr, "GATEWAY_INTERFACE", "CGI/1.1");
|
param_ptr = fastcgi_add_param(param_ptr, "GATEWAY_INTERFACE", "CGI/1.1");
|
||||||
@ -121,16 +127,16 @@ int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned
|
|||||||
socklen_t len = sizeof(addr_storage);
|
socklen_t len = sizeof(addr_storage);
|
||||||
getsockname(client->socket, (struct sockaddr *) &addr_storage, &len);
|
getsockname(client->socket, (struct sockaddr *) &addr_storage, &len);
|
||||||
addr = (struct sockaddr_in6 *) &addr_storage;
|
addr = (struct sockaddr_in6 *) &addr_storage;
|
||||||
sprintf(buf0, "%i", addr->sin6_port);
|
sprintf(buf0, "%i", ntohs(addr->sin6_port));
|
||||||
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PORT", buf0);
|
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PORT", buf0);
|
||||||
|
|
||||||
len = sizeof(addr_storage);
|
len = sizeof(addr_storage);
|
||||||
getpeername(client->socket, (struct sockaddr *) &addr_storage, &len);
|
getpeername(client->socket, (struct sockaddr *) &addr_storage, &len);
|
||||||
addr = (struct sockaddr_in6 *) &addr_storage;
|
addr = (struct sockaddr_in6 *) &addr_storage;
|
||||||
sprintf(buf0, "%i", addr->sin6_port);
|
sprintf(buf0, "%i", ntohs(addr->sin6_port));
|
||||||
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_PORT", buf0);
|
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_PORT", buf0);
|
||||||
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_ADDR", client_addr_str);
|
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_ADDR", conn->r_addr);
|
||||||
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_HOST", client_host_str != NULL ? client_host_str : client_addr_str);
|
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_HOST", conn->r_host != NULL ? conn->r_host : conn->r_addr);
|
||||||
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_IDENT", "");
|
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_IDENT", "");
|
||||||
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_USER", "");
|
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_USER", "");
|
||||||
|
|
||||||
@ -149,19 +155,21 @@ int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned
|
|||||||
param_ptr = fastcgi_add_param(param_ptr, "PATH_INFO", buf0);
|
param_ptr = fastcgi_add_param(param_ptr, "PATH_INFO", buf0);
|
||||||
|
|
||||||
//param_ptr = fastcgi_add_param(param_ptr, "AUTH_TYPE", "");
|
//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 : "");
|
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 : "");
|
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_TYPE", content_type != NULL ? content_type : "");
|
||||||
if (client_geoip != NULL) {
|
//if (conn->ctx->geoip[0] != 0) {
|
||||||
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", client_geoip);
|
// param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", conn->ctx->geoip);
|
||||||
}
|
//}
|
||||||
|
|
||||||
for (int i = 0; i < req->hdr.field_num; i++) {
|
for (int i = 0; i < list_size(req->hdr.fields); i++) {
|
||||||
|
const http_field *f = &req->hdr.fields[i];
|
||||||
|
const char *name = http_field_get_name(f);
|
||||||
char *ptr = buf0;
|
char *ptr = buf0;
|
||||||
ptr += sprintf(ptr, "HTTP_");
|
ptr += sprintf(ptr, "HTTP_");
|
||||||
for (int j = 0; j < strlen(req->hdr.fields[i][0]); j++, ptr++) {
|
for (int j = 0; j < strlen(name); j++, ptr++) {
|
||||||
char ch = req->hdr.fields[i][0][j];
|
char ch = name[j];
|
||||||
if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
|
if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
|
||||||
ch = ch;
|
ch = ch;
|
||||||
} else if (ch >= 'a' && ch <= 'z') {
|
} else if (ch >= 'a' && ch <= 'z') {
|
||||||
@ -172,218 +180,233 @@ int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned
|
|||||||
ptr[0] = ch;
|
ptr[0] = ch;
|
||||||
ptr[1] = 0;
|
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);
|
if (fastcgi_send_data(conn, FCGI_PARAMS, param_ptr - param_buf, param_buf) == -1)
|
||||||
header.type = FCGI_PARAMS;
|
return -1;
|
||||||
header.contentLengthB1 = param_len >> 8;
|
|
||||||
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 FastCGI socket: %s" CLR_STR, strerror(errno));
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
header.type = FCGI_PARAMS;
|
if (fastcgi_send_data(conn, FCGI_PARAMS, 0, NULL) == -1)
|
||||||
header.contentLengthB1 = 0;
|
return -1;
|
||||||
header.contentLengthB0 = 0;
|
|
||||||
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
|
int pipes[2][2];
|
||||||
print(ERR_STR "Unable to send to FastCGI socket: %s" CLR_STR, strerror(errno));
|
if (pipe(pipes[0]) == -1 || pipe(pipes[1]) == -1)
|
||||||
return -2;
|
return -1;
|
||||||
}
|
|
||||||
|
conn->fd_out = pipes[1][1];
|
||||||
|
conn->out.socket = pipes[1][0];
|
||||||
|
sock_set_timeout(&conn->out, FASTCGI_TIMEOUT);
|
||||||
|
|
||||||
|
conn->fd_err = pipes[0][1];
|
||||||
|
conn->err = fdopen(pipes[0][0], "r");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fastcgi_close_stdin(fastcgi_conn *conn) {
|
int fastcgi_close_cnx(fastcgi_cnx_t *cnx) {
|
||||||
FCGI_Header header = {
|
int e = errno;
|
||||||
.version = FCGI_VERSION_1,
|
|
||||||
.type = FCGI_STDIN,
|
|
||||||
.requestIdB1 = conn->req_id >> 8,
|
|
||||||
.requestIdB0 = conn->req_id & 0xFF,
|
|
||||||
.contentLengthB1 = 0,
|
|
||||||
.contentLengthB0 = 0,
|
|
||||||
.paddingLength = 0,
|
|
||||||
.reserved = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
|
if (cnx->err) fclose(cnx->err);
|
||||||
print(ERR_STR "Unable to send to FastCGI socket: %s" CLR_STR, strerror(errno));
|
cnx->err = NULL;
|
||||||
return -2;
|
sock_close(&cnx->socket);
|
||||||
}
|
|
||||||
|
|
||||||
|
sock_close(&cnx->out);
|
||||||
|
if (cnx->fd_err != -1) close(cnx->fd_err);
|
||||||
|
if (cnx->fd_out != -1) close(cnx->fd_out);
|
||||||
|
cnx->fd_err = -1;
|
||||||
|
cnx->fd_out = -1;
|
||||||
|
|
||||||
|
errno = e;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fastcgi_php_error(const fastcgi_conn *conn, const char *msg, int msg_len, char *err_msg) {
|
int fastcgi_close_stdin(fastcgi_cnx_t *cnx) {
|
||||||
char *msg_str = malloc(msg_len + 1);
|
return (fastcgi_send_data(cnx, FCGI_STDIN, 0, NULL) == -1) ? -1 : 0;
|
||||||
char *ptr0 = msg_str;
|
}
|
||||||
strncpy(msg_str, msg, msg_len);
|
|
||||||
char *ptr1 = NULL;
|
int fastcgi_php_error(fastcgi_cnx_t *cnx, char *err_msg) {
|
||||||
int len;
|
char *line = NULL, *line_ptr = NULL;
|
||||||
|
size_t line_len = 0;
|
||||||
int err = 0;
|
int err = 0;
|
||||||
// FIXME *msg is part of a stream, handle fragmented lines
|
|
||||||
while (1) {
|
log_lvl_t msg_type = LOG_INFO;
|
||||||
int msg_type = 0;
|
|
||||||
int msg_pre_len = 0;
|
// FIXME php fastcgi sends multiple calls with '; ' as delimiter
|
||||||
ptr1 = strstr(ptr0, "PHP message: ");
|
for (long ret; cnx->fd_err_bytes > 0 && (ret = getline(&line, &line_len, cnx->err)) != -1; cnx->fd_err_bytes -= ret) {
|
||||||
if (ptr1 == NULL) {
|
if (ret > 0) line[ret - 1] = 0;
|
||||||
len = (int) (msg_len - (ptr0 - msg_str));
|
line_ptr = line;
|
||||||
if (ptr0 == msg_str) msg_type = 2;
|
|
||||||
} else {
|
if (strstarts(line_ptr, "PHP message: ")) {
|
||||||
len = (int) (ptr1 - ptr0);
|
line_ptr += 13;
|
||||||
}
|
if (strstarts(line_ptr, "PHP Warning: ")) {
|
||||||
if (len == 0) {
|
msg_type = LOG_WARNING;
|
||||||
goto next;
|
} else if (strstarts(line_ptr, "PHP Fatal error: ")) {
|
||||||
|
msg_type = LOG_ERROR;
|
||||||
|
} else if (strstarts(line_ptr, "PHP Parse error: ")) {
|
||||||
|
msg_type = LOG_ERROR;
|
||||||
|
} else if (strstarts(line_ptr, "PHP Notice: ")) {
|
||||||
|
msg_type = LOG_NOTICE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len >= 14 && strncmp(ptr0, "PHP Warning: ", 14) == 0) {
|
logmsgf(msg_type, "%s", line_ptr);
|
||||||
msg_type = 1;
|
|
||||||
msg_pre_len = 14;
|
|
||||||
} else if (len >= 18 && strncmp(ptr0, "PHP Fatal error: ", 18) == 0) {
|
|
||||||
msg_type = 2;
|
|
||||||
msg_pre_len = 18;
|
|
||||||
} else if (len >= 18 && strncmp(ptr0, "PHP Parse error: ", 18) == 0) {
|
|
||||||
msg_type = 2;
|
|
||||||
msg_pre_len = 18;
|
|
||||||
} else if (len >= 18 && strncmp(ptr0, "PHP Notice: ", 13) == 0) {
|
|
||||||
msg_type = 1;
|
|
||||||
msg_pre_len = 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *ptr2 = ptr0;
|
if (err_msg && msg_type <= LOG_ERROR && line_ptr != line) {
|
||||||
char *ptr3;
|
strcpy_rem_webroot(err_msg, line_ptr, cnx->webroot);
|
||||||
int len2;
|
err = 1;
|
||||||
while (ptr2 - ptr0 < len) {
|
|
||||||
ptr3 = strchr(ptr2, '\n');
|
|
||||||
len2 = (int) (len - (ptr2 - ptr0));
|
|
||||||
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, CLR_STR);
|
|
||||||
if (msg_type == 2 && ptr2 == ptr0) {
|
|
||||||
strcpy_rem_webroot(err_msg, ptr2, len2, conn->webroot);
|
|
||||||
err = 1;
|
|
||||||
}
|
|
||||||
if (ptr3 == NULL) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ptr2 = ptr3 + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next:
|
|
||||||
if (ptr1 == NULL) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ptr0 = ptr1 + 13;
|
|
||||||
}
|
}
|
||||||
free(msg_str);
|
|
||||||
|
// cleanup
|
||||||
|
free(line);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
|
int fastcgi_recv_frame(fastcgi_cnx_t *cnx) {
|
||||||
FCGI_Header header;
|
FCGI_Header header;
|
||||||
char *content;
|
unsigned short req_id, content_len;
|
||||||
unsigned short content_len, req_id;
|
|
||||||
long ret;
|
|
||||||
int err = 0;
|
|
||||||
|
|
||||||
while (1) {
|
if (sock_recv_x(&cnx->socket, &header, sizeof(header), 0) == -1)
|
||||||
ret = recv(conn->socket, &header, sizeof(header), 0);
|
return -1;
|
||||||
if (ret < 0) {
|
|
||||||
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(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;
|
|
||||||
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
|
|
||||||
content = malloc(content_len + header.paddingLength);
|
|
||||||
ret = recv(conn->socket, content, content_len + header.paddingLength, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
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(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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req_id != conn->req_id) {
|
req_id = ntohs(header.requestId);
|
||||||
continue;
|
content_len = ntohs(header.contentLength);
|
||||||
}
|
|
||||||
|
|
||||||
if (header.type == FCGI_END_REQUEST) {
|
if (req_id != cnx->req_id) {
|
||||||
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
|
warning("Invalid request id from FastCGI socket");
|
||||||
int app_status = (body->appStatusB3 << 24) | (body->appStatusB2 << 16) | (body->appStatusB1 << 8) |
|
char content[256 * 256];
|
||||||
body->appStatusB0;
|
sock_recv_x(&cnx->socket, content, content_len + header.paddingLength, 0);
|
||||||
if (body->protocolStatus != FCGI_REQUEST_COMPLETE) {
|
return -1;
|
||||||
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
|
}
|
||||||
}
|
|
||||||
if (app_status != 0) {
|
if (header.type == FCGI_STDOUT || header.type == FCGI_STDERR) {
|
||||||
print(ERR_STR "FastCGI app terminated with exit code %i" CLR_STR, app_status);
|
char buf[256];
|
||||||
}
|
|
||||||
close(conn->socket);
|
if (header.type == FCGI_STDOUT && !cnx->header_sent) {
|
||||||
conn->socket = 0;
|
char content[256 * 256];
|
||||||
free(content);
|
|
||||||
return 1;
|
if (sock_recv_x(&cnx->socket, content, content_len + header.paddingLength, 0) == -1)
|
||||||
} else if (header.type == FCGI_STDERR) {
|
return -1;
|
||||||
// TODO implement Necronda backend error handling
|
|
||||||
if (conn->mode == FASTCGI_PHP) {
|
char *h_pos = strstr(content, "\r\n\r\n");
|
||||||
err = err || fastcgi_php_error(conn, content, content_len, err_msg);
|
long header_len = h_pos - content + 4;
|
||||||
|
if (h_pos != NULL) {
|
||||||
|
uint64_t len;
|
||||||
|
|
||||||
|
len = header_len;
|
||||||
|
if (write(cnx->fd_out, &len, sizeof(len)) == -1)
|
||||||
|
return -1;
|
||||||
|
if (write(cnx->fd_out, content, len) == -1)
|
||||||
|
return -1;
|
||||||
|
cnx->header_sent = 1;
|
||||||
|
|
||||||
|
len = content_len - header_len;
|
||||||
|
if (len > 0) {
|
||||||
|
if (write(cnx->fd_out, &len, sizeof(len)) == -1)
|
||||||
|
return -1;
|
||||||
|
if (write(cnx->fd_out, content + header_len, len) == -1)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return header.type;
|
||||||
}
|
}
|
||||||
} else if (header.type == FCGI_STDOUT) {
|
} else if (header.type == FCGI_STDOUT) {
|
||||||
break;
|
uint64_t len = content_len;
|
||||||
} else {
|
if (write(cnx->fd_out, &len, sizeof(len)) == -1)
|
||||||
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(content);
|
int fd = cnx->fd_out;
|
||||||
}
|
if (header.type == FCGI_STDERR) {
|
||||||
if (err) {
|
fd = cnx->fd_err;
|
||||||
res->status = http_get_status(500);
|
cnx->fd_err_bytes += content_len + 1;
|
||||||
return 2;
|
}
|
||||||
|
for (long ret, sent = 0; sent < content_len; sent += ret) {
|
||||||
|
// FIXME if pipe is full thread gets stuck
|
||||||
|
if ((ret = splice(cnx->socket.socket, 0, fd, 0, content_len - sent, 0)) == -1) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0, ret = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (header.type == FCGI_STDERR) write(fd, "\n", 1);
|
||||||
|
|
||||||
|
if (sock_recv_x(&cnx->socket, buf, header.paddingLength, 0) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return header.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn->out_buf = content;
|
char content[256 * 256];
|
||||||
conn->out_len = content_len;
|
if (sock_recv_x(&cnx->socket, content, content_len + header.paddingLength, 0) == -1)
|
||||||
conn->out_off = (unsigned short) (strstr(content, "\r\n\r\n") - content + 4);
|
return -1;
|
||||||
|
|
||||||
|
if (header.type == FCGI_END_REQUEST) {
|
||||||
|
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
|
||||||
|
cnx->app_status = ntohl(body->appStatus);
|
||||||
|
if (body->protocolStatus != FCGI_REQUEST_COMPLETE)
|
||||||
|
error("FastCGI protocol error: %i", body->protocolStatus);
|
||||||
|
} else {
|
||||||
|
warning("Unknown FastCGI type: %i", header.type);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return header.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fastcgi_header(fastcgi_cnx_t *cnx, http_res *res, char *err_msg) {
|
||||||
|
long ret, len;
|
||||||
|
char content[CLIENT_MAX_HEADER_SIZE];
|
||||||
|
|
||||||
|
if ((len = sock_recv_chunk_header(&cnx->out)) == -1) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
|
||||||
|
error("Unable to receive from FastCGI socket (1)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ret = sock_recv_x(&cnx->out, content, len, 0)) == -1) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
|
||||||
|
error("Unable to receive from FastCGI socket (2)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
content[ret] = 0;
|
||||||
|
|
||||||
char *buf = content;
|
char *buf = content;
|
||||||
unsigned short header_len = conn->out_off;
|
char *h_pos = strstr(content, "\r\n\r\n");
|
||||||
if (header_len <= 0) {
|
if (h_pos == NULL) {
|
||||||
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
|
error("Unable to parse header: End of header not found");
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
long header_len = h_pos - content + 4;
|
||||||
|
|
||||||
for (int i = 0; i < header_len; i++) {
|
for (int i = 0; i < header_len; i++) {
|
||||||
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
|
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);
|
error("Unable to parse header: Header contains illegal characters");
|
||||||
return 2;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fastcgi_php_error(cnx, err_msg) != 0) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
char *ptr = buf;
|
char *ptr = buf;
|
||||||
while (header_len != (ptr - buf)) {
|
while (header_len != (ptr - buf)) {
|
||||||
char *pos0 = strstr(ptr, "\r\n");
|
char *pos0 = strstr(ptr, "\r\n");
|
||||||
if (pos0 == NULL) {
|
if (pos0 == NULL) {
|
||||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
error("Unable to parse header: Invalid header format");
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = http_parse_header_field(&res->hdr, ptr, pos0);
|
ret = http_parse_header_field(&res->hdr, ptr, pos0, 0);
|
||||||
if (ret != 0) return (int) ret;
|
if (ret != 0) return (int) ret;
|
||||||
|
|
||||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -393,230 +416,37 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
|
int fastcgi_dump(fastcgi_cnx_t *cnx, char *buf, long len) {
|
||||||
FCGI_Header header;
|
return sock_recv_x(&cnx->socket, buf, len, 0) == -1 ? -1 : 0;
|
||||||
long ret;
|
|
||||||
char buf0[256];
|
|
||||||
int len;
|
|
||||||
char *content, *ptr;
|
|
||||||
unsigned short req_id, content_len;
|
|
||||||
char comp_out[4096];
|
|
||||||
int finish_comp = 0;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conn->out_buf != NULL && conn->out_len > conn->out_off) {
|
|
||||||
content = conn->out_buf;
|
|
||||||
ptr = content + conn->out_off;
|
|
||||||
content_len = conn->out_len - conn->out_off;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
ptr = content;
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (flags & FASTCGI_COMPRESS) {
|
|
||||||
finish_comp = 1;
|
|
||||||
content_len = 0;
|
|
||||||
goto out;
|
|
||||||
finish:
|
|
||||||
compress_free(&comp_ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & FASTCGI_CHUNKED) {
|
|
||||||
sock_send(client, "0\r\n\r\n", 5, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
} else if (header.type == FCGI_STDERR) {
|
|
||||||
// TODO implement Necronda backend error handling
|
|
||||||
if (conn->mode == FASTCGI_PHP) {
|
|
||||||
fastcgi_php_error(conn, content, content_len, buf0);
|
|
||||||
}
|
|
||||||
} else if (header.type == FCGI_STDOUT) {
|
|
||||||
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) {
|
|
||||||
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) - avail_out);
|
|
||||||
}
|
|
||||||
if (buf_len != 0) {
|
|
||||||
len = sprintf(buf0, "%X\r\n", buf_len);
|
|
||||||
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) && (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);
|
|
||||||
}
|
|
||||||
free(content);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int fastcgi_dump(fastcgi_conn *conn, char *buf, long len) {
|
int fastcgi_receive(fastcgi_cnx_t *cnx, sock *client, unsigned long len) {
|
||||||
FCGI_Header header;
|
char buf[CHUNK_SIZE];
|
||||||
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) {
|
for (long to_send = (long) len, ret; to_send > 0; to_send -= ret) {
|
||||||
ptr += snprintf(ptr, len, "%.*s", conn->out_len - conn->out_off, conn->out_buf + conn->out_off);
|
if ((ret = sock_recv(client, buf, (to_send > sizeof(buf)) ? sizeof(buf) : to_send, 0)) <= 0) {
|
||||||
}
|
error("Unable to receive");
|
||||||
|
|
||||||
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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
|
if (fastcgi_send_data(cnx, FCGI_STDIN, ret, buf) == -1)
|
||||||
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
|
|
||||||
content = malloc(content_len + header.paddingLength);
|
|
||||||
|
|
||||||
long rcv_len = 0;
|
|
||||||
while (rcv_len < content_len + header.paddingLength) {
|
|
||||||
ret = recv(conn->socket, content + rcv_len, content_len + header.paddingLength - rcv_len, 0);
|
|
||||||
if (ret < 0) {
|
|
||||||
print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
|
|
||||||
free(content);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
rcv_len += ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header.type == FCGI_END_REQUEST) {
|
|
||||||
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
|
|
||||||
int app_status = (body->appStatusB3 << 24) | (body->appStatusB2 << 16) | (body->appStatusB1 << 8) |
|
|
||||||
body->appStatusB0;
|
|
||||||
if (body->protocolStatus != FCGI_REQUEST_COMPLETE) {
|
|
||||||
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
|
|
||||||
}
|
|
||||||
if (app_status != 0) {
|
|
||||||
print(ERR_STR "FastCGI app terminated with exit code %i" CLR_STR, app_status);
|
|
||||||
}
|
|
||||||
close(conn->socket);
|
|
||||||
conn->socket = 0;
|
|
||||||
free(content);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
} else if (header.type == FCGI_STDERR) {
|
|
||||||
// TODO implement Necronda backend error handling
|
|
||||||
if (conn->mode == FASTCGI_PHP) {
|
|
||||||
fastcgi_php_error(conn, content, content_len, buf0);
|
|
||||||
}
|
|
||||||
} else if (header.type == FCGI_STDOUT) {
|
|
||||||
ptr += snprintf(ptr, len - (ptr - buf), "%.*s", content_len, content);
|
|
||||||
} else {
|
|
||||||
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
|
|
||||||
}
|
|
||||||
free(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
|
|
||||||
unsigned long rcv_len = 0;
|
|
||||||
char *buf[16384];
|
|
||||||
long ret;
|
|
||||||
FCGI_Header header = {
|
|
||||||
.version = FCGI_VERSION_1,
|
|
||||||
.type = FCGI_STDIN,
|
|
||||||
.requestIdB1 = conn->req_id >> 8,
|
|
||||||
.requestIdB0 = conn->req_id & 0xFF,
|
|
||||||
.contentLengthB1 = 0,
|
|
||||||
.contentLengthB0 = 0,
|
|
||||||
.paddingLength = 0,
|
|
||||||
.reserved = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
if (client->buf != NULL && client->buf_len - client->buf_off > 0) {
|
|
||||||
ret = (int) (client->buf_len - client->buf_off);
|
|
||||||
memcpy(buf, client->buf + client->buf_off, ret);
|
|
||||||
goto send;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (rcv_len < len) {
|
|
||||||
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;
|
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 FastCGI socket: %s" CLR_STR, strerror(errno));
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fastcgi_receive_chunked(fastcgi_cnx_t *cnx, sock *client) {
|
||||||
|
for (long ret;;) {
|
||||||
|
if ((ret = sock_recv_chunk_header(client)) < 0) {
|
||||||
|
return (int) ret;
|
||||||
|
} else if (ret == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ret = fastcgi_receive(cnx, client, ret)) < 0)
|
||||||
|
return (int) ret;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1,57 +1,63 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* FastCGI interface implementation (header file)
|
* @brief FastCGI interface implementation (header file)
|
||||||
* src/lib/fastcgi.h
|
* @file src/lib/fastcgi.h
|
||||||
* Lorenz Stechauner, 2020-12-26
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-26
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_FASTCGI_H
|
#ifndef SESIMOS_FASTCGI_H
|
||||||
#define NECRONDA_SERVER_FASTCGI_H
|
#define SESIMOS_FASTCGI_H
|
||||||
|
|
||||||
#include "include/fastcgi.h"
|
#include "include/fastcgi.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "uri.h"
|
#include "uri.h"
|
||||||
|
|
||||||
#define FASTCGI_CHUNKED 1
|
#define FASTCGI_SOCKET_TIMEOUT 1
|
||||||
#define FASTCGI_COMPRESS_GZ 2
|
#define FASTCGI_TIMEOUT 3600
|
||||||
#define FASTCGI_COMPRESS_BR 4
|
|
||||||
#define FASTCGI_COMPRESS 6
|
|
||||||
#define FASTCGI_COMPRESS_HOLD 8
|
|
||||||
|
|
||||||
#define FASTCGI_PHP 1
|
#define FASTCGI_BACKEND_PHP 1
|
||||||
#define FASTCGI_NECRONDA 2
|
|
||||||
|
|
||||||
#ifndef PHP_FPM_SOCKET
|
#ifndef PHP_FPM_SOCKET
|
||||||
# define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock"
|
# define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define NECRONDA_BACKEND_SOCKET "/var/run/necronda/necronda-backend.sock"
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int mode;
|
int mode;
|
||||||
int socket;
|
unsigned char header_sent:1;
|
||||||
|
sock socket, out;
|
||||||
|
int fd_err, fd_out;
|
||||||
|
long fd_err_bytes;
|
||||||
|
FILE *err;
|
||||||
unsigned short req_id;
|
unsigned short req_id;
|
||||||
char *out_buf;
|
int app_status;
|
||||||
const char *webroot;
|
const char *webroot;
|
||||||
unsigned short out_len;
|
char *r_addr;
|
||||||
unsigned short out_off;
|
char *r_host;
|
||||||
} fastcgi_conn;
|
} fastcgi_cnx_t;
|
||||||
|
|
||||||
char *fastcgi_add_param(char *buf, const char *key, const char *value);
|
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,
|
int fastcgi_init(fastcgi_cnx_t *conn, int mode, unsigned int req_num, const sock *client, const http_req *req, const http_uri *uri);
|
||||||
const http_req *req, const http_uri *uri);
|
|
||||||
|
|
||||||
int fastcgi_close_stdin(fastcgi_conn *conn);
|
int fastcgi_close_cnx(fastcgi_cnx_t *cnx);
|
||||||
|
|
||||||
int fastcgi_php_error(const fastcgi_conn *conn, const char *msg, int msg_len, char *err_msg);
|
int fastcgi_close_stdin(fastcgi_cnx_t *cnx);
|
||||||
|
|
||||||
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg);
|
int fastcgi_php_error(fastcgi_cnx_t *cnx, char *err_msg);
|
||||||
|
|
||||||
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags);
|
int fastcgi_recv_frame(fastcgi_cnx_t *cnx);
|
||||||
|
|
||||||
int fastcgi_dump(fastcgi_conn *conn, char *buf, long len);
|
int fastcgi_header(fastcgi_cnx_t *cnx, http_res *res, char *err_msg);
|
||||||
|
|
||||||
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len);
|
long fastcgi_send(fastcgi_cnx_t *cnx, sock *client);
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_FASTCGI_H
|
int fastcgi_dump(fastcgi_cnx_t *cnx, char *buf, long len);
|
||||||
|
|
||||||
|
int fastcgi_receive(fastcgi_cnx_t *cnx, sock *client, unsigned long len);
|
||||||
|
|
||||||
|
int fastcgi_receive_chunked(fastcgi_cnx_t *cnx, sock *client);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif //SESIMOS_FASTCGI_H
|
||||||
|
169
src/lib/geoip.c
169
src/lib/geoip.c
@ -1,13 +1,33 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* MaxMind GeoIP Database interface
|
* @brief MaxMind GeoIP Database interface
|
||||||
* src/lib/geoip.c
|
* @file src/lib/geoip.c
|
||||||
* Lorenz Stechauner, 2021-05-04
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-05-04
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "geoip.h"
|
#include "geoip.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len) {
|
#include <memory.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
static MMDB_s mmdbs[GEOIP_MAX_MMDB];
|
||||||
|
|
||||||
|
static void mmdb_error(int err) {
|
||||||
|
if (err == MMDB_SUCCESS) {
|
||||||
|
errno = 0;
|
||||||
|
} else if (err == MMDB_IO_ERROR) {
|
||||||
|
// errno already set
|
||||||
|
} else {
|
||||||
|
error_mmdb(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MMDB_entry_data_list_s *geoip_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len) {
|
||||||
switch (list->entry_data.type) {
|
switch (list->entry_data.type) {
|
||||||
case MMDB_DATA_TYPE_MAP:
|
case MMDB_DATA_TYPE_MAP:
|
||||||
*str_off += sprintf(str + *str_off, "{");
|
*str_off += sprintf(str + *str_off, "{");
|
||||||
@ -31,7 +51,7 @@ MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long
|
|||||||
*str_off += sprintf(str + *str_off, "%llu", (unsigned long long) list->entry_data.uint128);
|
*str_off += sprintf(str + *str_off, "%llu", (unsigned long long) list->entry_data.uint128);
|
||||||
break;
|
break;
|
||||||
case MMDB_DATA_TYPE_INT32:
|
case MMDB_DATA_TYPE_INT32:
|
||||||
*str_off += sprintf(str + *str_off, "%i", list->entry_data.uint32);
|
*str_off += sprintf(str + *str_off, "%i", list->entry_data.int32);
|
||||||
break;
|
break;
|
||||||
case MMDB_DATA_TYPE_BOOLEAN:
|
case MMDB_DATA_TYPE_BOOLEAN:
|
||||||
*str_off += sprintf(str + *str_off, "%s", list->entry_data.boolean ? "true" : "false");
|
*str_off += sprintf(str + *str_off, "%s", list->entry_data.boolean ? "true" : "false");
|
||||||
@ -43,13 +63,14 @@ MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long
|
|||||||
*str_off += sprintf(str + *str_off, "%f", list->entry_data.double_value);
|
*str_off += sprintf(str + *str_off, "%f", list->entry_data.double_value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (list->entry_data.type != MMDB_DATA_TYPE_MAP && list->entry_data.type != MMDB_DATA_TYPE_ARRAY) {
|
|
||||||
|
if (list->entry_data.type != MMDB_DATA_TYPE_MAP && list->entry_data.type != MMDB_DATA_TYPE_ARRAY)
|
||||||
return list->next;
|
return list->next;
|
||||||
}
|
|
||||||
MMDB_entry_data_list_s *next = list->next;
|
MMDB_entry_data_list_s *next = list->next;
|
||||||
int stat = 0;
|
int stat = 0;
|
||||||
for (int i = 0; i < list->entry_data.data_size; i++) {
|
for (int i = 0; i < list->entry_data.data_size; i++) {
|
||||||
next = mmdb_json(next, str, str_off, str_len);
|
next = geoip_json(next, str, str_off, str_len);
|
||||||
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
|
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
|
||||||
stat = !stat;
|
stat = !stat;
|
||||||
if (stat) {
|
if (stat) {
|
||||||
@ -58,12 +79,130 @@ MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i != list->entry_data.data_size - 1) *str_off += sprintf(str + *str_off, ",");
|
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, "]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*str_off += sprintf(str + *str_off, (list->entry_data.type == MMDB_DATA_TYPE_MAP) ? "}" : "]");
|
||||||
|
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int geoip_init(const char *directory) {
|
||||||
|
char buf[512];
|
||||||
|
|
||||||
|
memset(mmdbs, 0, sizeof(mmdbs));
|
||||||
|
|
||||||
|
if (directory[0] == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
DIR *geoip_dir;
|
||||||
|
if ((geoip_dir = opendir(directory)) == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
int i = 0, status;
|
||||||
|
while ((entry = readdir(geoip_dir)) != NULL) {
|
||||||
|
if (!strends(entry->d_name, ".mmdb"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (i >= GEOIP_MAX_MMDB) {
|
||||||
|
critical("Unable to initialize geoip: Too many .mmdb files");
|
||||||
|
closedir(geoip_dir);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(buf, "%s/%s", directory, entry->d_name);
|
||||||
|
if ((status = MMDB_open(buf, 0, &mmdbs[i])) != MMDB_SUCCESS) {
|
||||||
|
mmdb_error(status);
|
||||||
|
critical("Unable to initialize geoip: Unable to open .mmdb file");
|
||||||
|
closedir(geoip_dir);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(geoip_dir);
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
critical("Unable to initialize geoip: No .mmdb files found in %s", directory);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void geoip_free() {
|
||||||
|
for (int i = 0; i < GEOIP_MAX_MMDB && mmdbs[i].file_content != NULL; i++) {
|
||||||
|
MMDB_close(&mmdbs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int geoip_lookup_country(struct sockaddr *addr, char *str) {
|
||||||
|
for (int i = 0; i < GEOIP_MAX_MMDB && mmdbs[i].file_content != NULL; i++) {
|
||||||
|
// lookup
|
||||||
|
int mmdb_res;
|
||||||
|
MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&mmdbs[i], addr, &mmdb_res);
|
||||||
|
if (mmdb_res != MMDB_SUCCESS) {
|
||||||
|
return -1;
|
||||||
|
} else if (!result.found_entry) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get country iso code
|
||||||
|
MMDB_entry_data_s data;
|
||||||
|
int status;
|
||||||
|
if ((status = MMDB_get_value(&result.entry, &data, "country", "iso_code", NULL)) != MMDB_SUCCESS) {
|
||||||
|
if (status == MMDB_IO_ERROR) {
|
||||||
|
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no, or invalid data
|
||||||
|
if (!data.has_data || data.type != MMDB_DATA_TYPE_UTF8_STRING)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// return country code
|
||||||
|
sprintf(str, "%.2s", data.utf8_string);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int geoip_lookup_json(struct sockaddr *addr, char *json, long len) {
|
||||||
|
long str_off = 0;
|
||||||
|
for (int i = 0; i < GEOIP_MAX_MMDB && mmdbs[i].filename != NULL; i++) {
|
||||||
|
int mmdb_res;
|
||||||
|
MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&mmdbs[i], addr, &mmdb_res);
|
||||||
|
if (mmdb_res != MMDB_SUCCESS) {
|
||||||
|
mmdb_error(mmdb_res);
|
||||||
|
error("Unable to lookup geoip info");
|
||||||
|
continue;
|
||||||
|
} else if (!result.found_entry) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MMDB_entry_data_list_s *list;
|
||||||
|
if ((mmdb_res = MMDB_get_entry_data_list(&result.entry, &list)) != MMDB_SUCCESS) {
|
||||||
|
mmdb_error(mmdb_res);
|
||||||
|
error("Unable to lookup geoip info");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
long prev = str_off;
|
||||||
|
if (str_off != 0) {
|
||||||
|
str_off--;
|
||||||
|
}
|
||||||
|
geoip_json(list, json, &str_off, len);
|
||||||
|
if (prev != 0) {
|
||||||
|
json[prev - 1] = ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
MMDB_free_entry_data_list(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* MaxMind GeoIP Database interface (header file)
|
* @brief MaxMind GeoIP Database interface (header file)
|
||||||
* src/lib/geoip.h
|
* @file src/lib/geoip.h
|
||||||
* Lorenz Stechauner, 2021-05-04
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-05-04
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_GEOIP_H
|
#ifndef SESIMOS_GEOIP_H
|
||||||
#define NECRONDA_SERVER_GEOIP_H
|
#define SESIMOS_GEOIP_H
|
||||||
|
|
||||||
#include <maxminddb.h>
|
#include <maxminddb.h>
|
||||||
|
|
||||||
#define GEOIP_MAX_SIZE 8192
|
#define GEOIP_MAX_JSON_SIZE 8192
|
||||||
|
#define GEOIP_MAX_MMDB 3
|
||||||
|
|
||||||
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len);
|
int geoip_init(const char *directory);
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_GEOIP_H
|
void geoip_free();
|
||||||
|
|
||||||
|
int geoip_lookup_country(struct sockaddr *addr, char *str);
|
||||||
|
|
||||||
|
#endif //SESIMOS_GEOIP_H
|
||||||
|
550
src/lib/http.c
550
src/lib/http.c
@ -1,18 +1,56 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* HTTP implementation
|
* @brief HTTP implementation
|
||||||
* src/lib/http.c
|
* @file src/lib/http.c
|
||||||
* Lorenz Stechauner, 2020-12-09
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-09
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "compress.h"
|
#include "compress.h"
|
||||||
|
#include "list.h"
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
void http_append_to_header_field(http_field *field, const char *value, unsigned long len);
|
||||||
|
|
||||||
|
static int http_error(int err) {
|
||||||
|
if (err == 0) {
|
||||||
|
errno = 0;
|
||||||
|
} else if (err == HTTP_ERROR_SYSCALL) {
|
||||||
|
// errno already set
|
||||||
|
} else {
|
||||||
|
error_http(err);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *http_error_str(int err) {
|
||||||
|
switch (err) {
|
||||||
|
case HTTP_ERROR_TOO_MANY_HEADER_FIELDS:
|
||||||
|
return "too many header fields";
|
||||||
|
case HTTP_ERROR_EOH_NOT_FOUND:
|
||||||
|
return "end of http header not found";
|
||||||
|
case HTTP_ERROR_HEADER_MALFORMED:
|
||||||
|
return "http header malformed";
|
||||||
|
case HTTP_ERROR_INVALID_VERSION:
|
||||||
|
return "invalid http version";
|
||||||
|
case HTTP_ERROR_URI_TOO_LONG:
|
||||||
|
return "uri too long";
|
||||||
|
case HTTP_ERROR_GENERAL:
|
||||||
|
default:
|
||||||
|
return "unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void http_to_camel_case(char *str, int mode) {
|
void http_to_camel_case(char *str, int mode) {
|
||||||
char last = '-';
|
if (mode == HTTP_PRESERVE)
|
||||||
char ch;
|
return;
|
||||||
|
|
||||||
|
char ch, last = '-';
|
||||||
for (int i = 0; i < strlen(str); i++) {
|
for (int i = 0; i < strlen(str); i++) {
|
||||||
ch = str[i];
|
ch = str[i];
|
||||||
if (mode == HTTP_CAMEL && last == '-' && ch >= 'a' && ch <= 'z') {
|
if (mode == HTTP_CAMEL && last == '-' && ch >= 'a' && ch <= 'z') {
|
||||||
@ -24,12 +62,50 @@ void http_to_camel_case(char *str, int mode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_free_hdr(http_hdr *hdr) {
|
const char *http_field_get_name(const http_field *field) {
|
||||||
for (int i = 0; i < hdr->field_num; i++) {
|
if (field->type == HTTP_FIELD_NORMAL) {
|
||||||
free(hdr->fields[i][0]);
|
return field->normal.name;
|
||||||
free(hdr->fields[i][1]);
|
} 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;
|
||||||
}
|
}
|
||||||
hdr->field_num = 0;
|
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 < list_size(hdr->fields); i++) {
|
||||||
|
http_free_field(&hdr->fields[i]);
|
||||||
|
}
|
||||||
|
list_free(hdr->fields);
|
||||||
|
hdr->last_field_num = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void http_free_req(http_req *req) {
|
void http_free_req(http_req *req) {
|
||||||
@ -42,174 +118,238 @@ void http_free_res(http_res *res) {
|
|||||||
http_free_hdr(&res->hdr);
|
http_free_hdr(&res->hdr);
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) {
|
int http_init_hdr(http_hdr *hdr) {
|
||||||
char *pos1 = memchr(buf, ':', end_ptr - buf);
|
hdr->last_field_num = -1;
|
||||||
char *pos2;
|
hdr->fields = list_create(sizeof(http_field), HTTP_INIT_HEADER_FIELD_NUM);
|
||||||
if (pos1 == NULL) {
|
if (hdr->fields == NULL)
|
||||||
print(ERR_STR "Unable to parse header" CLR_STR);
|
return http_error(HTTP_ERROR_SYSCALL);
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
long len = pos1 - buf;
|
|
||||||
hdr->fields[(int) hdr->field_num][0] = malloc(len + 1);
|
|
||||||
sprintf(hdr->fields[(int) hdr->field_num][0], "%.*s", (int) len, buf);
|
|
||||||
http_to_camel_case(hdr->fields[(int) hdr->field_num][0], HTTP_CAMEL);
|
|
||||||
|
|
||||||
pos1++;
|
|
||||||
pos2 = (char *) end_ptr - 1;
|
|
||||||
while (pos1[0] == ' ') pos1++;
|
|
||||||
while (pos2[0] == ' ') pos2--;
|
|
||||||
len = pos2 - pos1 + 1;
|
|
||||||
|
|
||||||
if (len <= 0) {
|
|
||||||
hdr->fields[(int) hdr->field_num][1] = malloc(1);
|
|
||||||
hdr->fields[(int) hdr->field_num][1][0] = 0;
|
|
||||||
} else {
|
|
||||||
hdr->fields[(int) hdr->field_num][1] = malloc(len + 1);
|
|
||||||
sprintf(hdr->fields[(int) hdr->field_num][1], "%.*s", (int) len, pos1);
|
|
||||||
}
|
|
||||||
hdr->field_num++;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr, int flags) {
|
||||||
|
if (hdr->last_field_num > list_size(hdr->fields))
|
||||||
|
return http_error(HTTP_ERROR_GENERAL);
|
||||||
|
|
||||||
|
char *pos1 = (char *) buf, *pos2 = (char *) end_ptr;
|
||||||
|
if (buf[0] == ' ' || buf[0] == '\t') {
|
||||||
|
if (hdr->last_field_num == -1)
|
||||||
|
return http_error(HTTP_ERROR_GENERAL);
|
||||||
|
|
||||||
|
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)
|
||||||
|
return http_error(HTTP_ERROR_GENERAL);
|
||||||
|
|
||||||
|
long len1 = pos1 - buf;
|
||||||
|
|
||||||
|
pos1++;
|
||||||
|
str_trim_lws(&pos1, &pos2);
|
||||||
|
long len2 = pos2 - pos1;
|
||||||
|
|
||||||
|
char header_name[256];
|
||||||
|
sprintf(header_name, "%.*s", (int) len1, buf);
|
||||||
|
|
||||||
|
int field_num = list_size(hdr->fields);
|
||||||
|
int found = http_get_header_field_num(hdr, header_name);
|
||||||
|
if (!(flags & HTTP_MERGE_FIELDS) || found == -1) {
|
||||||
|
if (http_add_header_field_len(hdr, buf, len1, pos1, len2 < 0 ? 0 : len2) != 0)
|
||||||
|
return http_error(HTTP_ERROR_TOO_MANY_HEADER_FIELDS);
|
||||||
|
} else {
|
||||||
|
field_num = found;
|
||||||
|
http_append_to_header_field(&hdr->fields[found], ", ", 2);
|
||||||
|
http_append_to_header_field(&hdr->fields[found], pos1, len2);
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr->last_field_num = field_num;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int http_parse_request(char *buf, http_req *req) {
|
||||||
|
char *ptr, *pos0 = buf, *pos1, *pos2;
|
||||||
|
long len;
|
||||||
|
|
||||||
|
unsigned long header_len = strstr(buf, "\r\n\r\n") - buf + 4;
|
||||||
|
if (header_len <= 0)
|
||||||
|
return http_error(HTTP_ERROR_EOH_NOT_FOUND);
|
||||||
|
|
||||||
|
for (int i = 0; i < header_len; i++) {
|
||||||
|
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F)
|
||||||
|
return http_error(HTTP_ERROR_HEADER_MALFORMED);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = buf;
|
||||||
|
while (header_len > (ptr - buf + 2)) {
|
||||||
|
pos0 = strstr(ptr, "\r\n");
|
||||||
|
if (pos0 == NULL)
|
||||||
|
return http_error(HTTP_ERROR_HEADER_MALFORMED);
|
||||||
|
|
||||||
|
if (req->version[0] == 0) {
|
||||||
|
pos1 = (char *) strchr(ptr, ' ') + 1;
|
||||||
|
if (pos1 == NULL)
|
||||||
|
return http_error(HTTP_ERROR_HEADER_MALFORMED);
|
||||||
|
|
||||||
|
if (pos1 - ptr - 1 >= sizeof(req->method))
|
||||||
|
return http_error(HTTP_ERROR_HEADER_MALFORMED);
|
||||||
|
|
||||||
|
for (int i = 0; i < (pos1 - ptr - 1); i++) {
|
||||||
|
if (ptr[i] < 'A' || ptr[i] > 'Z')
|
||||||
|
return http_error(HTTP_ERROR_HEADER_MALFORMED);
|
||||||
|
}
|
||||||
|
snprintf(req->method, sizeof(req->method), "%.*s", (int) (pos1 - ptr - 1), ptr);
|
||||||
|
|
||||||
|
pos2 = (char *) strchr(pos1, ' ') + 1;
|
||||||
|
if (pos2 == NULL)
|
||||||
|
return http_error(HTTP_ERROR_HEADER_MALFORMED);
|
||||||
|
|
||||||
|
if (memcmp(pos2, "HTTP/", 5) != 0 || memcmp(pos2 + 8, "\r\n", 2) != 0)
|
||||||
|
return http_error(HTTP_ERROR_INVALID_VERSION);
|
||||||
|
|
||||||
|
len = pos2 - pos1 - 1;
|
||||||
|
if (len >= 2048)
|
||||||
|
return http_error(HTTP_ERROR_URI_TOO_LONG);
|
||||||
|
|
||||||
|
req->uri = malloc(len + 1);
|
||||||
|
sprintf(req->uri, "%.*s", (int) len, pos1);
|
||||||
|
sprintf(req->version, "%.3s", pos2 + 5);
|
||||||
|
} else {
|
||||||
|
if (http_parse_header_field(&req->hdr, ptr, pos0, HTTP_MERGE_FIELDS) != 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ptr = pos0 + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||||
|
return (int) header_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return http_error(HTTP_ERROR_GENERAL);
|
||||||
|
}
|
||||||
|
|
||||||
int http_receive_request(sock *client, http_req *req) {
|
int http_receive_request(sock *client, http_req *req) {
|
||||||
long rcv_len, len;
|
long rcv_len;
|
||||||
char *ptr, *pos0, *pos1, *pos2;
|
|
||||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
memset(req->method, 0, sizeof(req->method));
|
memset(req->method, 0, sizeof(req->method));
|
||||||
memset(req->version, 0, sizeof(req->version));
|
memset(req->version, 0, sizeof(req->version));
|
||||||
req->uri = NULL;
|
req->uri = NULL;
|
||||||
req->hdr.field_num = 0;
|
http_init_hdr(&req->hdr);
|
||||||
|
|
||||||
while (1) {
|
rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE - 1, MSG_PEEK);
|
||||||
rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE, 0);
|
if (rcv_len <= 0)
|
||||||
if (rcv_len <= 0) {
|
return -1;
|
||||||
print("Unable to receive http header: %s", sock_strerror(client));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long header_len = strstr(buf, "\r\n\r\n") - buf + 4;
|
buf[rcv_len] = 0;
|
||||||
if (header_len <= 0) {
|
|
||||||
print(ERR_STR "Unable to parse http header: End of header not found" CLR_STR);
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < header_len; i++) {
|
long header_len = http_parse_request(buf, req);
|
||||||
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
|
if (header_len < 0)
|
||||||
print(ERR_STR "Unable to parse http header: Header contains illegal characters" CLR_STR);
|
return (int) -header_len;
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr = buf;
|
if (sock_recv_x(client, buf, header_len, 0) == -1)
|
||||||
while (header_len > (ptr - buf + 2)) {
|
return -1;
|
||||||
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) {
|
return 0;
|
||||||
pos1 = memchr(ptr, ' ', rcv_len - (ptr - buf)) + 1;
|
}
|
||||||
if (pos1 == NULL) goto err_hdr_fmt;
|
|
||||||
|
|
||||||
if (pos1 - ptr - 1 >= sizeof(req->method)) {
|
const char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
|
||||||
print(ERR_STR "Unable to parse http header: Method name too long" CLR_STR);
|
int num = http_get_header_field_num(hdr, field_name);
|
||||||
return 2;
|
return (num >= 0 && num < list_size(hdr->fields)) ? http_field_get_value(&hdr->fields[num]) : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < (pos1 - ptr - 1); i++) {
|
int http_get_header_field_num(const http_hdr *hdr, const char *field_name) {
|
||||||
if (ptr[i] < 'A' || ptr[i] > 'Z') {
|
for (int i = 0; i < list_size(hdr->fields); i++) {
|
||||||
print(ERR_STR "Unable to parse http header: Invalid method" CLR_STR);
|
if (strcasecmp(field_name, http_field_get_name(&hdr->fields[i])) == 0)
|
||||||
return 2;
|
return i;
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
if (ret != 0) return ret;
|
|
||||||
}
|
|
||||||
ptr = pos0 + 2;
|
|
||||||
}
|
|
||||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client->buf_len = rcv_len - (pos0 - buf + 4);
|
return -1;
|
||||||
if (client->buf_len > 0) {
|
}
|
||||||
client->buf = malloc(client->buf_len);
|
|
||||||
client->buf_off = 0;
|
int http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
|
||||||
memcpy(client->buf, pos0 + 4, client->buf_len);
|
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) {
|
||||||
|
http_field *f;
|
||||||
|
hdr->fields = list_append_ptr(hdr->fields, (void **) &f);
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
|
int http_add_to_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
|
||||||
char field_name_1[256], field_name_2[256];
|
int field_num = http_get_header_field_num(hdr, field_name);
|
||||||
strcpy(field_name_1, field_name);
|
if (field_num == -1)
|
||||||
http_to_camel_case(field_name_1, HTTP_LOWER);
|
return http_add_header_field(hdr, field_name, field_value);
|
||||||
for (int i = 0; i < hdr->field_num; i++) {
|
|
||||||
strcpy(field_name_2, hdr->fields[i][0]);
|
http_append_to_header_field(&hdr->fields[field_num], field_value, strlen(field_value));
|
||||||
http_to_camel_case(field_name_2, HTTP_LOWER);
|
return 0;
|
||||||
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) {
|
void http_append_to_header_field(http_field *field, const char *value, unsigned long len) {
|
||||||
size_t len_name = strlen(field_name);
|
if (field->type == HTTP_FIELD_NORMAL) {
|
||||||
size_t len_value = strlen(field_value);
|
unsigned long total_len = strlen(field->normal.value) + len + 1;
|
||||||
char *_field_name = malloc(len_name + 1);
|
if (total_len < sizeof(field->normal.value)) {
|
||||||
char *_field_value = malloc(len_value + 1);
|
strncat(field->normal.value, value, len);
|
||||||
strcpy(_field_name, field_name);
|
} else {
|
||||||
strcpy(_field_value, field_value);
|
field->type = HTTP_FIELD_EX_VALUE;
|
||||||
http_to_camel_case(_field_name, HTTP_PRESERVE);
|
char *new = malloc(total_len);
|
||||||
hdr->fields[(int) hdr->field_num][0] = _field_name;
|
strcpy(new, field->normal.value);
|
||||||
hdr->fields[(int) hdr->field_num][1] = _field_value;
|
strncat(new, value, len);
|
||||||
hdr->field_num++;
|
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) {
|
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 i = 0;
|
||||||
int diff = 1;
|
int diff = 1;
|
||||||
if (mode == HTTP_REMOVE_LAST) {
|
if (mode == HTTP_REMOVE_LAST) {
|
||||||
i = hdr->field_num - 1;
|
i = list_size(hdr->fields) - 1;
|
||||||
diff = -1;
|
diff = -1;
|
||||||
}
|
}
|
||||||
for (; i < hdr->field_num && i >= 0; i += diff) {
|
for (; i < list_size(hdr->fields) && i >= 0; i += diff) {
|
||||||
strcpy(field_name_2, hdr->fields[i][0]);
|
if (strcasecmp(field_name, http_field_get_name(&hdr->fields[i])) == 0) {
|
||||||
http_to_camel_case(field_name_2, HTTP_LOWER);
|
http_free_field(&hdr->fields[i]);
|
||||||
if (strcmp(field_name_1, field_name_2) == 0) {
|
list_remove(hdr->fields, i);
|
||||||
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_ALL) {
|
if (mode == HTTP_REMOVE_ALL) {
|
||||||
i -= diff;
|
i -= diff;
|
||||||
} else {
|
} else {
|
||||||
@ -222,32 +362,41 @@ 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_response(sock *client, http_res *res) {
|
||||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||||
long off = sprintf(buf, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
|
long off = sprintf(buf, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
|
||||||
for (int i = 0; i < res->hdr.field_num; i++) {
|
for (int i = 0; i < list_size(res->hdr.fields); i++) {
|
||||||
off += sprintf(buf + off, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]);
|
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");
|
off += sprintf(buf + off, "\r\n");
|
||||||
if (sock_send(client, buf, off, 0) < 0) {
|
if (sock_send_x(client, buf, off, 0) != off)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_send_request(sock *server, http_req *req) {
|
int http_send_request(sock *server, http_req *req) {
|
||||||
char buf[CLIENT_MAX_HEADER_SIZE];
|
char buf[CLIENT_MAX_HEADER_SIZE];
|
||||||
long off = sprintf(buf, "%s %s HTTP/%s\r\n", req->method, req->uri, req->version);
|
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++) {
|
for (int i = 0; i < list_size(req->hdr.fields); i++) {
|
||||||
off += sprintf(buf + off, "%s: %s\r\n", req->hdr.fields[i][0], req->hdr.fields[i][1]);
|
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");
|
off += sprintf(buf + off, "\r\n");
|
||||||
long ret = sock_send(server, buf, off, 0);
|
if (sock_send_x(server, buf, off, 0) != off)
|
||||||
if (ret <= 0) {
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const http_status *http_get_status(unsigned short status_code) {
|
int http_send_100_continue(sock *client) {
|
||||||
for (int i = 0; i < http_statuses_size / sizeof(http_status); i++) {
|
char buf[256];
|
||||||
|
char date_buf[64];
|
||||||
|
int size = sprintf(buf, "HTTP/1.1 100 Continue\r\nDate: %s\r\nServer: " SERVER_STR "\r\n\r\n",
|
||||||
|
http_get_date(date_buf, sizeof(date_buf)));
|
||||||
|
return sock_send_x(client, buf, size, 0) == -1 ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const http_status *http_get_status(status_code_t status_code) {
|
||||||
|
for (int i = 0; i < http_statuses_size; i++) {
|
||||||
if (http_statuses[i].code == status_code) {
|
if (http_statuses[i].code == status_code) {
|
||||||
return &http_statuses[i];
|
return &http_statuses[i];
|
||||||
}
|
}
|
||||||
@ -255,74 +404,75 @@ const http_status *http_get_status(unsigned short status_code) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const http_status_msg *http_get_error_msg(const http_status *status) {
|
const http_status_msg *http_get_error_msg(status_code_t status_code) {
|
||||||
unsigned short code = status->code;
|
for (int i = 0; i < http_status_messages_size; i++) {
|
||||||
for (int i = 0; i < http_status_messages_size / sizeof(http_status_msg); i++) {
|
if (http_status_messages[i].code == status_code) {
|
||||||
if (http_status_messages[i].code == code) {
|
|
||||||
return &http_status_messages[i];
|
return &http_status_messages[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *http_get_status_color(const http_status *status) {
|
const char *http_get_status_color(status_code_t status_code) {
|
||||||
unsigned short code = status->code;
|
if (status_code == 304) return HTTP_2XX_STR;
|
||||||
if (code >= 100 && code < 200) {
|
switch (status_code / 100) {
|
||||||
return HTTP_1XX_STR;
|
case 1: return HTTP_1XX_STR;
|
||||||
} else if ((code >= 200 && code < 300) || code == 304) {
|
case 2: return HTTP_2XX_STR;
|
||||||
return HTTP_2XX_STR;
|
case 3: return HTTP_3XX_STR;
|
||||||
} else if (code >= 300 && code < 400) {
|
case 4: return HTTP_4XX_STR;
|
||||||
return HTTP_3XX_STR;
|
case 5: return HTTP_5XX_STR;
|
||||||
} else if (code >= 400 && code < 500) {
|
default: return "";
|
||||||
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) {
|
char *http_format_date(time_t ts, char *buf, size_t size) {
|
||||||
struct tm *timeinfo = gmtime(&time);
|
struct tm time_info;
|
||||||
strftime(buf, size, "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
|
strftime(buf, size, "%a, %d %b %Y %H:%M:%S GMT", gmtime_r(&ts, &time_info));
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *http_get_date(char *buf, size_t size) {
|
char *http_get_date(char *buf, size_t size) {
|
||||||
time_t rawtime;
|
time_t raw_time;
|
||||||
time(&rawtime);
|
time(&raw_time);
|
||||||
return http_format_date(rawtime, buf, size);
|
return http_format_date(raw_time, buf, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
const http_doc_info *http_get_status_info(const http_status *status) {
|
const http_doc_info *http_get_status_info(status_code_t status_code) {
|
||||||
unsigned short code = status->code;
|
static const http_doc_info info[] = {
|
||||||
static http_doc_info info[] = {
|
{"info", HTTP_COLOR_INFO, "/.sesimos/res/icon-info.svg", http_info_doc},
|
||||||
{"info", HTTP_COLOR_INFO, http_info_icon, http_info_document},
|
{"success", HTTP_COLOR_SUCCESS, "/.sesimos/res/icon-success.svg", http_success_doc},
|
||||||
{"success", HTTP_COLOR_SUCCESS, http_success_icon, http_success_document},
|
{"warning", HTTP_COLOR_WARNING, "/.sesimos/res/icon-warning.svg", http_warning_doc},
|
||||||
{"warning", HTTP_COLOR_WARNING, http_warning_icon, http_warning_document},
|
{"error", HTTP_COLOR_ERROR, "/.sesimos/res/icon-error.svg", http_error_doc}
|
||||||
{"error", HTTP_COLOR_ERROR, http_error_icon, http_error_document}
|
|
||||||
};
|
};
|
||||||
if (code >= 100 && code < 200) {
|
if (status_code == 304) return &info[1];
|
||||||
return &info[0];
|
switch (status_code / 100) {
|
||||||
} else if ((code >= 200 && code < 300) || code == 304) {
|
case 1: return &info[0];
|
||||||
return &info[1];
|
case 2: return &info[1];
|
||||||
} else if (code >= 300 && code < 400) {
|
case 3: return &info[2];
|
||||||
return &info[2];
|
case 4: // see case 5
|
||||||
} else if (code >= 400 && code < 600) {
|
case 5: return &info[3];
|
||||||
return &info[3];
|
default: return NULL;
|
||||||
}
|
}
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_get_compression(const http_req *req, const http_res *res) {
|
int http_get_compression(const http_req *req, const http_res *res) {
|
||||||
char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
|
const char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
|
||||||
char *content_type = http_get_header_field(&res->hdr, "Content-Type");
|
const char *content_type = http_get_header_field(&res->hdr, "Content-Type");
|
||||||
char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
|
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 (mime_is_compressible(content_type) && content_encoding == NULL && accept_encoding != NULL) {
|
||||||
if (strstr(accept_encoding, "br") != NULL) {
|
if (strcontains(accept_encoding, "br")) {
|
||||||
return COMPRESS_BR;
|
return COMPRESS_BR;
|
||||||
} else if (strstr(accept_encoding, "gzip") != NULL) {
|
} else if (strcontains(accept_encoding, "gzip")) {
|
||||||
return COMPRESS_GZ;
|
return COMPRESS_GZ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long http_get_keep_alive_timeout(http_hdr *hdr) {
|
||||||
|
const char *keep_alive = http_get_header_field(hdr, "Keep-Alive");
|
||||||
|
if (!keep_alive) return -1;
|
||||||
|
const char *timeout = strstr(keep_alive, "timeout=");
|
||||||
|
if (!timeout) return -1;
|
||||||
|
return strtol(timeout + 8, NULL, 10);
|
||||||
|
}
|
||||||
|
124
src/lib/http.h
124
src/lib/http.h
@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* HTTP implementation (header file)
|
* @brief HTTP implementation (header file)
|
||||||
* src/lib/http.h
|
* @file src/lib/http.h
|
||||||
* Lorenz Stechauner, 2020-12-09
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-09
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_HTTP_H
|
#ifndef SESIMOS_HTTP_H
|
||||||
#define NECRONDA_SERVER_HTTP_H
|
#define SESIMOS_HTTP_H
|
||||||
|
|
||||||
#include "sock.h"
|
#include "sock.h"
|
||||||
|
|
||||||
@ -18,6 +19,12 @@
|
|||||||
#define HTTP_REMOVE_ALL 1
|
#define HTTP_REMOVE_ALL 1
|
||||||
#define HTTP_REMOVE_LAST 2
|
#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_1XX_STR "\x1B[1;32m"
|
||||||
#define HTTP_2XX_STR "\x1B[1;32m"
|
#define HTTP_2XX_STR "\x1B[1;32m"
|
||||||
#define HTTP_3XX_STR "\x1B[1;33m"
|
#define HTTP_3XX_STR "\x1B[1;33m"
|
||||||
@ -25,28 +32,45 @@
|
|||||||
#define HTTP_5XX_STR "\x1B[1;31m"
|
#define HTTP_5XX_STR "\x1B[1;31m"
|
||||||
|
|
||||||
#define HTTP_COLOR_SUCCESS "#008000"
|
#define HTTP_COLOR_SUCCESS "#008000"
|
||||||
#define HTTP_COLOR_INFO "#606060"
|
#define HTTP_COLOR_INFO "#606060"
|
||||||
#define HTTP_COLOR_WARNING "#E0C000"
|
#define HTTP_COLOR_WARNING "#E0C000"
|
||||||
#define HTTP_COLOR_ERROR "#C00000"
|
#define HTTP_COLOR_ERROR "#C00000"
|
||||||
|
|
||||||
#define CLIENT_MAX_HEADER_SIZE 8192
|
#define CLIENT_MAX_HEADER_SIZE 8192
|
||||||
|
#define HTTP_INIT_HEADER_FIELD_NUM 16
|
||||||
|
|
||||||
|
#define HTTP_TYPE_INFORMATIONAL 1
|
||||||
|
#define HTTP_TYPE_SUCCESS 2
|
||||||
|
#define HTTP_TYPE_REDIRECTION 3
|
||||||
|
#define HTTP_TYPE_CLIENT_ERROR 4
|
||||||
|
#define HTTP_TYPE_SERVER_ERROR 5
|
||||||
|
|
||||||
|
#define HTTP_ERROR_GENERAL 1
|
||||||
|
#define HTTP_ERROR_SYSCALL 2
|
||||||
|
#define HTTP_ERROR_TOO_MANY_HEADER_FIELDS 3
|
||||||
|
#define HTTP_ERROR_EOH_NOT_FOUND 4
|
||||||
|
#define HTTP_ERROR_HEADER_MALFORMED 5
|
||||||
|
#define HTTP_ERROR_INVALID_VERSION 6
|
||||||
|
#define HTTP_ERROR_URI_TOO_LONG 7
|
||||||
|
|
||||||
#ifndef SERVER_STR
|
#ifndef SERVER_STR
|
||||||
# define SERVER_STR "Necronda"
|
# define SERVER_STR "sesimos"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef SERVER_STR_HTML
|
#ifndef SERVER_STR_HTML
|
||||||
# define SERVER_STR_HTML "Necronda web server"
|
# define SERVER_STR_HTML "sesimos web server"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef unsigned short status_code_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned short code;
|
status_code_t code:10;
|
||||||
char type[16];
|
unsigned char type:3;
|
||||||
char msg[64];
|
char msg[64];
|
||||||
} http_status;
|
} http_status;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned short code;
|
status_code_t code:10;
|
||||||
const char *msg;
|
const char *msg;
|
||||||
} http_status_msg;
|
} http_status_msg;
|
||||||
|
|
||||||
@ -58,8 +82,26 @@ typedef struct {
|
|||||||
} http_doc_info;
|
} http_doc_info;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char field_num;
|
char type;
|
||||||
char *fields[64][2];
|
union {
|
||||||
|
struct {
|
||||||
|
char name[32];
|
||||||
|
char value[32];
|
||||||
|
} normal;
|
||||||
|
struct {
|
||||||
|
char name[64 - sizeof(char *)];
|
||||||
|
char *value;
|
||||||
|
} ex_value;
|
||||||
|
struct {
|
||||||
|
char *name;
|
||||||
|
char *value;
|
||||||
|
} ex_name;
|
||||||
|
};
|
||||||
|
} http_field;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int last_field_num;
|
||||||
|
http_field *fields;
|
||||||
} http_hdr;
|
} http_hdr;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -80,8 +122,9 @@ typedef enum {
|
|||||||
} http_error_origin;
|
} http_error_origin;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned short status;
|
status_code_t status;
|
||||||
http_error_origin origin;
|
http_error_origin origin;
|
||||||
|
const char* ws_key;
|
||||||
} http_status_ctx;
|
} http_status_ctx;
|
||||||
|
|
||||||
extern const http_status http_statuses[];
|
extern const http_status http_statuses[];
|
||||||
@ -89,32 +132,41 @@ extern const http_status_msg http_status_messages[];
|
|||||||
extern const int http_statuses_size;
|
extern const int http_statuses_size;
|
||||||
extern const int http_status_messages_size;
|
extern const int http_status_messages_size;
|
||||||
|
|
||||||
extern const char http_default_document[];
|
extern const char http_error_doc[], http_warning_doc[], http_success_doc[], http_info_doc[];
|
||||||
extern const char http_rev_proxy_document[];
|
|
||||||
extern const char http_error_document[];
|
const char *http_error_str(int err);
|
||||||
extern const char http_error_icon[];
|
|
||||||
extern const char http_warning_document[];
|
|
||||||
extern const char http_warning_icon[];
|
|
||||||
extern const char http_success_document[];
|
|
||||||
extern const char http_success_icon[];
|
|
||||||
extern const char http_info_document[];
|
|
||||||
extern const char http_info_icon[];
|
|
||||||
|
|
||||||
void http_to_camel_case(char *str, int mode);
|
void http_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);
|
||||||
|
|
||||||
|
int http_init_hdr(http_hdr *hdr);
|
||||||
|
|
||||||
|
void http_free_field(http_field *f);
|
||||||
|
|
||||||
void http_free_hdr(http_hdr *hdr);
|
void http_free_hdr(http_hdr *hdr);
|
||||||
|
|
||||||
void http_free_req(http_req *req);
|
void http_free_req(http_req *req);
|
||||||
|
|
||||||
void http_free_res(http_res *res);
|
void http_free_res(http_res *res);
|
||||||
|
|
||||||
|
int http_parse_request(char *buf, http_req *req);
|
||||||
|
|
||||||
int http_receive_request(sock *client, http_req *req);
|
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 http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr, int flags);
|
||||||
|
|
||||||
char *http_get_header_field(const http_hdr *hdr, const char *field_name);
|
const 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);
|
int http_get_header_field_num(const http_hdr *hdr, const char *field_name);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
int http_add_to_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);
|
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode);
|
||||||
|
|
||||||
@ -122,18 +174,22 @@ int http_send_response(sock *client, http_res *res);
|
|||||||
|
|
||||||
int http_send_request(sock *server, http_req *req);
|
int http_send_request(sock *server, http_req *req);
|
||||||
|
|
||||||
const http_status *http_get_status(unsigned short status_code);
|
int http_send_100_continue(sock *client);
|
||||||
|
|
||||||
const http_status_msg *http_get_error_msg(const http_status *status);
|
const http_status *http_get_status(status_code_t status_code);
|
||||||
|
|
||||||
const char *http_get_status_color(const http_status *status);
|
const http_status_msg *http_get_error_msg(status_code_t status_code);
|
||||||
|
|
||||||
|
const char *http_get_status_color(status_code_t status_code);
|
||||||
|
|
||||||
char *http_format_date(time_t time, char *buf, size_t size);
|
char *http_format_date(time_t time, char *buf, size_t size);
|
||||||
|
|
||||||
char *http_get_date(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);
|
const http_doc_info *http_get_status_info(status_code_t status_code);
|
||||||
|
|
||||||
int http_get_compression(const http_req *req, const http_res *res);
|
int http_get_compression(const http_req *req, const http_res *res);
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_HTTP_H
|
long http_get_keep_alive_timeout(http_hdr *hdr);
|
||||||
|
|
||||||
|
#endif //SESIMOS_HTTP_H
|
||||||
|
@ -1,303 +1,212 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* HTTP static implementation
|
* @brief HTTP static implementation
|
||||||
* src/lib/http_static.c
|
* @file src/lib/http_static.c
|
||||||
* Lorenz Stechauner, 2021-05-03
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-05-03
|
||||||
|
* @details https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "../necronda.h"
|
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
|
||||||
const http_status http_statuses[] = {
|
const http_status http_statuses[] = {
|
||||||
{100, "Informational", "Continue"},
|
{100, HTTP_TYPE_INFORMATIONAL, "Continue"},
|
||||||
{101, "Informational", "Switching Protocols"},
|
{101, HTTP_TYPE_INFORMATIONAL, "Switching Protocols"},
|
||||||
|
{102, HTTP_TYPE_INFORMATIONAL, "Processing"},
|
||||||
|
{103, HTTP_TYPE_INFORMATIONAL, "Early Hints"},
|
||||||
|
|
||||||
{200, "Success", "OK"},
|
{200, HTTP_TYPE_SUCCESS, "OK"},
|
||||||
{201, "Success", "Created"},
|
{201, HTTP_TYPE_SUCCESS, "Created"},
|
||||||
{202, "Success", "Accepted"},
|
{202, HTTP_TYPE_SUCCESS, "Accepted"},
|
||||||
{203, "Success", "Non-Authoritative Information"},
|
{203, HTTP_TYPE_SUCCESS, "Non-Authoritative Information"},
|
||||||
{204, "Success", "No Content"},
|
{204, HTTP_TYPE_SUCCESS, "No Content"},
|
||||||
{205, "Success", "Reset Content"},
|
{205, HTTP_TYPE_SUCCESS, "Reset Content"},
|
||||||
{206, "Success", "Partial Content"},
|
{206, HTTP_TYPE_SUCCESS, "Partial Content"},
|
||||||
|
{207, HTTP_TYPE_SUCCESS, "Multi-Status"},
|
||||||
|
{208, HTTP_TYPE_SUCCESS, "Already Reported"},
|
||||||
|
{226, HTTP_TYPE_SUCCESS, "Instance Manipulation Used"},
|
||||||
|
|
||||||
{300, "Redirection", "Multiple Choices"},
|
{300, HTTP_TYPE_REDIRECTION, "Multiple Choices"},
|
||||||
{301, "Redirection", "Moved Permanently"},
|
{301, HTTP_TYPE_REDIRECTION, "Moved Permanently"},
|
||||||
{302, "Redirection", "Found"},
|
{302, HTTP_TYPE_REDIRECTION, "Found"},
|
||||||
{303, "Redirection", "See Other"},
|
{303, HTTP_TYPE_REDIRECTION, "See Other"},
|
||||||
{304, "Success", "Not Modified"},
|
{304, HTTP_TYPE_SUCCESS, "Not Modified"},
|
||||||
{305, "Redirection", "Use Proxy"},
|
{305, HTTP_TYPE_REDIRECTION, "Use Proxy"},
|
||||||
{307, "Redirection", "Temporary Redirect"},
|
{307, HTTP_TYPE_REDIRECTION, "Temporary Redirect"},
|
||||||
{308, "Redirection", "Permanent Redirect"},
|
{308, HTTP_TYPE_REDIRECTION, "Permanent Redirect"},
|
||||||
|
|
||||||
{400, "Client Error", "Bad Request"},
|
{400, HTTP_TYPE_CLIENT_ERROR, "Bad Request"},
|
||||||
{401, "Client Error", "Unauthorized"},
|
{401, HTTP_TYPE_CLIENT_ERROR, "Unauthorized"},
|
||||||
{402, "Client Error", "Payment Required"},
|
{402, HTTP_TYPE_CLIENT_ERROR, "Payment Required"},
|
||||||
{403, "Client Error", "Forbidden"},
|
{403, HTTP_TYPE_CLIENT_ERROR, "Forbidden"},
|
||||||
{404, "Client Error", "Not Found"},
|
{404, HTTP_TYPE_CLIENT_ERROR, "Not Found"},
|
||||||
{405, "Client Error", "Method Not Allowed"},
|
{405, HTTP_TYPE_CLIENT_ERROR, "Method Not Allowed"},
|
||||||
{406, "Client Error", "Not Acceptable"},
|
{406, HTTP_TYPE_CLIENT_ERROR, "Not Acceptable"},
|
||||||
{407, "Client Error", "Proxy Authentication Required"},
|
{407, HTTP_TYPE_CLIENT_ERROR, "Proxy Authentication Required"},
|
||||||
{408, "Client Error", "Request Timeout"},
|
{408, HTTP_TYPE_CLIENT_ERROR, "Request Timeout"},
|
||||||
{409, "Client Error", "Conflict"},
|
{409, HTTP_TYPE_CLIENT_ERROR, "Conflict"},
|
||||||
{410, "Client Error", "Gone"},
|
{410, HTTP_TYPE_CLIENT_ERROR, "Gone"},
|
||||||
{411, "Client Error", "Length Required"},
|
{411, HTTP_TYPE_CLIENT_ERROR, "Length Required"},
|
||||||
{412, "Client Error", "Precondition Failed"},
|
{412, HTTP_TYPE_CLIENT_ERROR, "Precondition Failed"},
|
||||||
{413, "Client Error", "Request Entity Too Large"},
|
{413, HTTP_TYPE_CLIENT_ERROR, "Request Entity Too Large"},
|
||||||
{414, "Client Error", "Request-URI Too Long"},
|
{414, HTTP_TYPE_CLIENT_ERROR, "Request-URI Too Long"},
|
||||||
{415, "Client Error", "Unsupported Media Type"},
|
{415, HTTP_TYPE_CLIENT_ERROR, "Unsupported Media Type"},
|
||||||
{416, "Client Error", "Range Not Satisfiable"},
|
{416, HTTP_TYPE_CLIENT_ERROR, "Range Not Satisfiable"},
|
||||||
{417, "Client Error", "Expectation Failed"},
|
{417, HTTP_TYPE_CLIENT_ERROR, "Expectation Failed"},
|
||||||
|
{421, HTTP_TYPE_CLIENT_ERROR, "Misdirected Request"},
|
||||||
|
{422, HTTP_TYPE_CLIENT_ERROR, "Unprocessable Content"},
|
||||||
|
{423, HTTP_TYPE_CLIENT_ERROR, "Locked"},
|
||||||
|
{424, HTTP_TYPE_CLIENT_ERROR, "Failed Dependency"},
|
||||||
|
{425, HTTP_TYPE_CLIENT_ERROR, "Too Early"},
|
||||||
|
{426, HTTP_TYPE_CLIENT_ERROR, "Upgrade Required"},
|
||||||
|
{428, HTTP_TYPE_CLIENT_ERROR, "Precondition Required"},
|
||||||
|
{429, HTTP_TYPE_CLIENT_ERROR, "Too Many Requests"},
|
||||||
|
{431, HTTP_TYPE_CLIENT_ERROR, "Request Header Fields Too Large"},
|
||||||
|
{451, HTTP_TYPE_CLIENT_ERROR, "Unavailable For Legal Reasons"},
|
||||||
|
|
||||||
{500, "Server Error", "Internal Server Error"},
|
{500, HTTP_TYPE_SERVER_ERROR, "Internal Server Error"},
|
||||||
{501, "Server Error", "Not Implemented"},
|
{501, HTTP_TYPE_SERVER_ERROR, "Not Implemented"},
|
||||||
{502, "Server Error", "Bad Gateway"},
|
{502, HTTP_TYPE_SERVER_ERROR, "Bad Gateway"},
|
||||||
{503, "Server Error", "Service Unavailable"},
|
{503, HTTP_TYPE_SERVER_ERROR, "Service Unavailable"},
|
||||||
{504, "Server Error", "Gateway Timeout"},
|
{504, HTTP_TYPE_SERVER_ERROR, "Gateway Timeout"},
|
||||||
{505, "Server Error", "HTTP Version Not Supported"},
|
{505, HTTP_TYPE_SERVER_ERROR, "HTTP Version Not Supported"},
|
||||||
|
{506, HTTP_TYPE_SERVER_ERROR, "Variant Also Negotiates"},
|
||||||
|
{507, HTTP_TYPE_SERVER_ERROR, "Insufficient Storage"},
|
||||||
|
{508, HTTP_TYPE_SERVER_ERROR, "Loop Detected"},
|
||||||
|
{511, HTTP_TYPE_SERVER_ERROR, "Network Authentication Required"},
|
||||||
};
|
};
|
||||||
|
|
||||||
const http_status_msg http_status_messages[] = {
|
const http_status_msg http_status_messages[] = {
|
||||||
{100, "The client SHOULD continue with its request."},
|
{100, "The client SHOULD continue with its request. The server MUST send a final response after the 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."},
|
"has been completed."},
|
||||||
|
{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."},
|
||||||
|
{102, "The server has a reasonable expectation that the request will take significant time to complete. The "
|
||||||
|
"server MUST send a final response after the request has been completed."},
|
||||||
|
{103, "The client can speculatively evaluate the header fields included in the response while waiting for the "
|
||||||
|
"final response. The server MUST send a final response after the request has been completed."},
|
||||||
|
|
||||||
{200, "The request has succeeded."},
|
{200, "The request has succeeded."},
|
||||||
{201, "The request has been fulfilled and resulted in a new resource being created."},
|
{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."},
|
{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."},
|
{203, "The returned meta information in the entity-header is not the definitive set as available from the "
|
||||||
{204, "The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta information."},
|
"origin server, but is gathered from a local or a third-party copy."},
|
||||||
{205, "The server has fulfilled the request and the user agent SHOULD reset the document view which caused the request to be sent."},
|
{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."},
|
{206, "The server has fulfilled the partial GET request for the resource."},
|
||||||
|
{207, "The response provides status for multiple independent operations."},
|
||||||
|
{208, "The response is used to avoid enumerating the internal members of multiple bindings to the same "
|
||||||
|
"collection repeatedly."},
|
||||||
|
{226, "The server has fulfilled a GET request for the resource, and the response is a representation of the "
|
||||||
|
"result of one or more instance-manipulations applied to the current instance."},
|
||||||
|
|
||||||
{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."},
|
{300, "The requested resource corresponds to any one of a set of representations, each with its own specific "
|
||||||
{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."},
|
"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."},
|
{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."},
|
{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."},
|
{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."},
|
{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."},
|
{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."},
|
{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."},
|
{400, "The request could not be understood by the server due to malformed syntax."},
|
||||||
{401, "The request requires user authentication."},
|
{401, "The request requires user authentication."},
|
||||||
{403, "The server understood the request, but is refusing to fulfill it."},
|
{403, "The server understood the request, but is refusing to fulfill it."},
|
||||||
{404, "The server has not found anything matching the Request-URI."},
|
{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."},
|
{405, "The method specified in the Request-Line is not allowed for the resource identified by the "
|
||||||
{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."},
|
"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."},
|
{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."},
|
{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."},
|
{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."},
|
{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."},
|
{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."},
|
{412, "The precondition given in one or more of the request-header fields evaluated to false when it was "
|
||||||
{413, "The server is refusing to process a request because the request entity is larger than the server is willing or able to process."},
|
"tested on the server."},
|
||||||
{414, "The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret."},
|
{413, "The server is refusing to process a request because the request entity is larger than the server is "
|
||||||
{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."},
|
"willing or able to process."},
|
||||||
{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."},
|
{414, "The server is refusing to service the request because the Request-URI is longer than the server is "
|
||||||
{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."},
|
"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."},
|
||||||
|
{421, "The server is not able to produce a response. The client MAY retry the request over a different "
|
||||||
|
"connection."},
|
||||||
|
{422, "The server understands the content type of the request content, and the syntax of the request content "
|
||||||
|
"is correct, but the server was unable to process the contained information."},
|
||||||
|
{423, "The source or destination resource of a method is locked."},
|
||||||
|
{424, "The method could not be performed on the resource because the requested action depended on another "
|
||||||
|
"action and that action failed."},
|
||||||
|
{425, "The server is unwilling to risk processing a request that might be replayed."},
|
||||||
|
{426, "The server refuses to perform the request using the current protocol but might be willing to do so "
|
||||||
|
"after the client upgrades to a different protocol. The server MUST send an Upgrade header field to"
|
||||||
|
"indicate the required protocol(s)."},
|
||||||
|
{428, "The origin server requires the request to be conditional. By requiring requests to be conditional, the "
|
||||||
|
"server can assure that clients are working with the correct copies and thus avoiding a lost update."},
|
||||||
|
{429, "The client has sent too many requests in a given amount of time."},
|
||||||
|
{431, "The server is unwilling to process the request because its header fields are too large. The request MAY "
|
||||||
|
"be resubmitted after reducing the size of the request header fields."},
|
||||||
|
{451, "The server is denying access to the resource as a consequence of a legal demand."},
|
||||||
|
|
||||||
{500, "The server encountered an unexpected condition which prevented it from fulfilling the request."},
|
{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."},
|
{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."},
|
{502, "The server, while acting as a gateway or proxy, received an invalid response from the upstream server "
|
||||||
{503, "The server is currently unable to handle the request due to a temporary overloading or maintenance of the server."},
|
"it accessed in attempting to fulfill the request."},
|
||||||
{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."},
|
{503, "The server is currently unable to handle the request due to a temporary overloading or maintenance of "
|
||||||
{505, "The server does not support, or refuses to support, the HTTP protocol version that was used in the request message."}
|
"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."},
|
||||||
|
{506, "The server has an internal configuration error: the chosen variant resource is configured to engage in "
|
||||||
|
"transparent content negotiation itself, and is therefore not a proper end point in the negotiation "
|
||||||
|
"process."},
|
||||||
|
{507, "The method could not be performed on the resource because the server is unable to store the "
|
||||||
|
"representation needed to successfully complete the request. This condition is considered to be "
|
||||||
|
"temporary."},
|
||||||
|
{508, "The server terminated an operation because it encountered an infinite loop while processing the "
|
||||||
|
"request."},
|
||||||
|
{511, "The client needs to authenticate to gain network access. The response representation SHOULD contain a "
|
||||||
|
"link to a resource that allows the user to submit credentials."},
|
||||||
};
|
};
|
||||||
|
|
||||||
const char http_default_document[] =
|
const int http_statuses_size = sizeof(http_statuses) / sizeof(http_status);
|
||||||
"<!DOCTYPE html>\n"
|
const int http_status_messages_size = sizeof(http_status_messages) / sizeof(http_status_msg);
|
||||||
"<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[] =
|
const char http_error_doc[] =
|
||||||
"\t\t<section class=\"error-ctx\">\n"
|
" <h1>%1$i</h1>\n"
|
||||||
"\t\t\t<div class=\"box%1$s\">\n"
|
" <h2>%2$s :(</h2>\n"
|
||||||
"\t\t\t\t<div class=\"content\">\n"
|
" <p>%3$s</p>\n"
|
||||||
"\t\t\t\t\t<span>Client</span>\n"
|
" <p>%4$s</p>\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[] =
|
const char http_warning_doc[] =
|
||||||
"\t\t\t<h1>%1$i</h1>\n"
|
" <h1>%1$i</h1>\n"
|
||||||
"\t\t\t<h2>%2$s :(</h2>\n"
|
" <h2>%2$s :)</h2>\n"
|
||||||
"\t\t\t<p>%3$s</p>\n"
|
" <p>%3$s</p>\n"
|
||||||
"\t\t\t<p>%4$s</p>\n";
|
" <p>%4$s</p>\n";
|
||||||
|
|
||||||
const char http_error_icon[] =
|
const char http_success_doc[] =
|
||||||
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
|
" <h1>%1$i</h1>\n"
|
||||||
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
|
" <h2>%2$s :)</h2>\n"
|
||||||
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNDMDAwMDAiIHN0eWxlPSJmb250LWZhbWls"
|
" <p>%3$s</p>\n"
|
||||||
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjooPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
|
" <p>%4$s</p>\n";
|
||||||
|
|
||||||
|
const char http_info_doc[] =
|
||||||
const char http_warning_document[] =
|
" <h1>%1$i</h1>\n"
|
||||||
"\t\t\t<h1>%1$i</h1>\n"
|
" <h2>%2$s :)</h2>\n"
|
||||||
"\t\t\t<h2>%2$s :)</h2>\n"
|
" <p>%3$s</p>\n"
|
||||||
"\t\t\t<p>%3$s</p>\n"
|
" <p>%4$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,12 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* FastCGI header file
|
* @brief FastCGI header file
|
||||||
* src/lib/include/fastcgi.h
|
* @file src/lib/include/fastcgi.h
|
||||||
* Lorenz Stechauner, 2021-05-03
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_EXTERN_FASTCGI_H
|
#ifndef SESIMOS_EXTERN_FASTCGI_H
|
||||||
#define NECRONDA_SERVER_EXTERN_FASTCGI_H
|
#define SESIMOS_EXTERN_FASTCGI_H
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Listening socket file number
|
* Listening socket file number
|
||||||
@ -16,10 +15,8 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned char version;
|
unsigned char version;
|
||||||
unsigned char type;
|
unsigned char type;
|
||||||
unsigned char requestIdB1;
|
unsigned short requestId;
|
||||||
unsigned char requestIdB0;
|
unsigned short contentLength;
|
||||||
unsigned char contentLengthB1;
|
|
||||||
unsigned char contentLengthB0;
|
|
||||||
unsigned char paddingLength;
|
unsigned char paddingLength;
|
||||||
unsigned char reserved;
|
unsigned char reserved;
|
||||||
} FCGI_Header;
|
} FCGI_Header;
|
||||||
@ -57,8 +54,7 @@ typedef struct {
|
|||||||
#define FCGI_NULL_REQUEST_ID 0
|
#define FCGI_NULL_REQUEST_ID 0
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned char roleB1;
|
unsigned short role;
|
||||||
unsigned char roleB0;
|
|
||||||
unsigned char flags;
|
unsigned char flags;
|
||||||
unsigned char reserved[5];
|
unsigned char reserved[5];
|
||||||
} FCGI_BeginRequestBody;
|
} FCGI_BeginRequestBody;
|
||||||
@ -81,10 +77,7 @@ typedef struct {
|
|||||||
#define FCGI_FILTER 3
|
#define FCGI_FILTER 3
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned char appStatusB3;
|
unsigned int appStatus;
|
||||||
unsigned char appStatusB2;
|
|
||||||
unsigned char appStatusB1;
|
|
||||||
unsigned char appStatusB0;
|
|
||||||
unsigned char protocolStatus;
|
unsigned char protocolStatus;
|
||||||
unsigned char reserved[3];
|
unsigned char reserved[3];
|
||||||
} FCGI_EndRequestBody;
|
} FCGI_EndRequestBody;
|
||||||
@ -119,4 +112,4 @@ typedef struct {
|
|||||||
FCGI_UnknownTypeBody body;
|
FCGI_UnknownTypeBody body;
|
||||||
} FCGI_UnknownTypeRecord;
|
} FCGI_UnknownTypeRecord;
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_EXTERN_FASTCGI_H
|
#endif //SESIMOS_EXTERN_FASTCGI_H
|
||||||
|
150
src/lib/list.c
Normal file
150
src/lib/list.c
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
|
||||||
|
#include "list.h"
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#define FACTOR 4
|
||||||
|
#define meta(ptr) ((list_meta_t *) ((unsigned char *) (ptr) - sizeof(list_meta_t)))
|
||||||
|
#define data(ptr) ((unsigned char *) (ptr) + sizeof(list_meta_t))
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int init_size, elem_size, max_size, size;
|
||||||
|
} list_meta_t;
|
||||||
|
|
||||||
|
static void *list_resize(list_meta_t *list, int new_size) {
|
||||||
|
if (new_size <= 0) {
|
||||||
|
return NULL;
|
||||||
|
} else if (new_size == list->max_size) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
list_meta_t *new_ptr = realloc(list, sizeof(list_meta_t) + list->elem_size * new_size);
|
||||||
|
if (new_ptr == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
new_ptr->max_size = new_size;
|
||||||
|
return new_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_create(int elem_size, int init_elem_n) {
|
||||||
|
if (elem_size <= 0 || init_elem_n <= 0) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_ptr = malloc(sizeof(list_meta_t) + elem_size * init_elem_n);
|
||||||
|
if (list_ptr == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
memset(list_ptr, 0, sizeof(list_meta_t) + elem_size * init_elem_n);
|
||||||
|
list_meta_t *list = list_ptr;
|
||||||
|
list->init_size = init_elem_n;
|
||||||
|
list->elem_size = elem_size;
|
||||||
|
list->max_size = init_elem_n;
|
||||||
|
list->size = 0;
|
||||||
|
return data(list_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int list_size(const void *list_ptr) {
|
||||||
|
return meta(list_ptr)->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int list_find(void *list_ptr, void *elem) {
|
||||||
|
list_meta_t *list = meta(list_ptr);
|
||||||
|
unsigned char *array = list_ptr;
|
||||||
|
|
||||||
|
for (int i = 0; i < list->size; i++) {
|
||||||
|
if (memcmp(array + i * list->elem_size, elem, list->elem_size) == 0) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int list_contains(void *list_ptr, void *elem) {
|
||||||
|
return list_find(list_ptr, elem) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_insert(void *list_ptr, void *elem, int n) {
|
||||||
|
void *ptr = NULL;
|
||||||
|
list_ptr = list_insert_ptr(list_ptr, &ptr, n);
|
||||||
|
if (list_ptr != NULL && ptr != NULL) {
|
||||||
|
memcpy(ptr, elem, meta(list_ptr)->elem_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_insert_ptr(void *list_ptr, void **elem, int n) {
|
||||||
|
list_meta_t *list = meta(list_ptr);
|
||||||
|
if (n < 0)
|
||||||
|
n = list->size + n + 1;
|
||||||
|
|
||||||
|
if (list->size >= list->max_size) {
|
||||||
|
if ((list = list_resize(list, list->max_size * FACTOR)) == NULL)
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char *array = data(list);
|
||||||
|
|
||||||
|
if (n < list->size)
|
||||||
|
memmove(array + (n + 1) * list->elem_size, array + n * list->elem_size, (list->size - n) * list->elem_size);
|
||||||
|
*elem = array + n * list->elem_size;
|
||||||
|
memset(*elem, 0, list->elem_size);
|
||||||
|
|
||||||
|
list->size++;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_append(void *list_ptr, void *elem) {
|
||||||
|
return list_insert(list_ptr, elem, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_append_ptr(void *list_ptr, void **elem) {
|
||||||
|
return list_insert_ptr(list_ptr, elem, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_remove(void *list_ptr, int n) {
|
||||||
|
list_meta_t *list = meta(list_ptr);
|
||||||
|
if (n < 0)
|
||||||
|
n = list->size + n;
|
||||||
|
|
||||||
|
unsigned char *array = list_ptr;
|
||||||
|
|
||||||
|
if (list->size > 1 && n < list->size)
|
||||||
|
memmove(array + n * list->elem_size, array + (n + 1) * list->elem_size, (list->size - n - 1) * list->elem_size);
|
||||||
|
|
||||||
|
list->size--;
|
||||||
|
memset(array + list->size * list->elem_size, 0, list->elem_size);
|
||||||
|
|
||||||
|
if (list->size <= list->max_size / FACTOR * 3 / 4 && list->max_size / FACTOR >= list->init_size) {
|
||||||
|
if ((list = list_resize(list, list->max_size / FACTOR)) == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_delete(void *list_ptr, void *elem) {
|
||||||
|
int idx = list_find(list_ptr, elem);
|
||||||
|
if (idx == -1) {
|
||||||
|
return list_ptr;
|
||||||
|
} else {
|
||||||
|
return list_remove(list_ptr, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void *list_clear(void *list_ptr) {
|
||||||
|
list_meta_t *list = meta(list_ptr);
|
||||||
|
list->size = 0;
|
||||||
|
memset(list_ptr, 0, list->max_size * list->elem_size);
|
||||||
|
return data(list_resize(list, list->init_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
void list_free(void *list_ptr) {
|
||||||
|
free(meta(list_ptr));
|
||||||
|
}
|
29
src/lib/list.h
Normal file
29
src/lib/list.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
#ifndef SESIMOS_LIST_H
|
||||||
|
#define SESIMOS_LIST_H
|
||||||
|
|
||||||
|
void *list_create(int elem_size, int init_elem_n);
|
||||||
|
|
||||||
|
int list_size(const void *list_ptr);
|
||||||
|
|
||||||
|
int list_find(void *list_ptr, void *elem);
|
||||||
|
|
||||||
|
int list_contains(void *list_ptr, void *elem);
|
||||||
|
|
||||||
|
void *list_insert(void *list_ptr, void *elem, int n);
|
||||||
|
|
||||||
|
void *list_insert_ptr(void *list_ptr, void **elem, int n);
|
||||||
|
|
||||||
|
void *list_append(void *list_ptr, void *elem);
|
||||||
|
|
||||||
|
void *list_append_ptr(void *list_ptr, void **elem);
|
||||||
|
|
||||||
|
void *list_remove(void *list_ptr, int n);
|
||||||
|
|
||||||
|
void *list_delete(void *list_ptr, void *elem);
|
||||||
|
|
||||||
|
void *list_clear(void *list_ptr);
|
||||||
|
|
||||||
|
void list_free(void *list_ptr);
|
||||||
|
|
||||||
|
#endif //SESIMOS_LIST_H
|
167
src/lib/mpmc.c
Normal file
167
src/lib/mpmc.c
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
|
||||||
|
#include "mpmc.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
static void *mpmc_worker(void *arg);
|
||||||
|
|
||||||
|
int mpmc_init(mpmc_t *ctx, int n_workers, int buf_size, void (*consumer)(void *obj), const char *name) {
|
||||||
|
ctx->alive = 1;
|
||||||
|
ctx->n_workers = n_workers;
|
||||||
|
ctx->size = buf_size, ctx->max_size = buf_size;
|
||||||
|
ctx->rd = 0, ctx->wr = 0;
|
||||||
|
ctx->buffer = NULL, ctx->workers = NULL;
|
||||||
|
ctx->consumer = consumer;
|
||||||
|
ctx->name = name;
|
||||||
|
|
||||||
|
if (sem_init(&ctx->free, 0, ctx->size) != 0 ||
|
||||||
|
sem_init(&ctx->used, 0, 0) != 0 ||
|
||||||
|
sem_init(&ctx->lck_rd, 0, 1) != 0 ||
|
||||||
|
sem_init(&ctx->lck_wr, 0, 1) != 0)
|
||||||
|
{
|
||||||
|
mpmc_destroy(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ctx->buffer = malloc(ctx->size * sizeof(void *))) == NULL ||
|
||||||
|
(ctx->workers = malloc(ctx->n_workers * sizeof(pthread_t))) == NULL)
|
||||||
|
{
|
||||||
|
mpmc_destroy(ctx);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(ctx->buffer, 0, ctx->size * sizeof(void *));
|
||||||
|
memset(ctx->workers, -1, ctx->n_workers * sizeof(pthread_t));
|
||||||
|
|
||||||
|
for (int i = 0; i < ctx->n_workers; i++) {
|
||||||
|
int ret;
|
||||||
|
if ((ret = pthread_create(&ctx->workers[i], NULL, mpmc_worker, ctx)) != 0) {
|
||||||
|
mpmc_destroy(ctx);
|
||||||
|
errno = ret;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mpmc_queue(mpmc_t *ctx, void *obj) {
|
||||||
|
// wait for buffer to be emptied
|
||||||
|
while (sem_wait(&ctx->free) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock wr field
|
||||||
|
while (sem_wait(&ctx->lck_wr) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
sem_post(&ctx->free);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int p = ctx->wr;
|
||||||
|
ctx->wr = (ctx->wr + 1) % ctx->size;
|
||||||
|
|
||||||
|
// unlock wr field
|
||||||
|
sem_post(&ctx->lck_wr);
|
||||||
|
|
||||||
|
// fill buffer with object
|
||||||
|
ctx->buffer[p] = obj;
|
||||||
|
|
||||||
|
// inform worker
|
||||||
|
sem_post(&ctx->used);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *mpmc_worker(void *arg) {
|
||||||
|
mpmc_t *ctx = arg;
|
||||||
|
|
||||||
|
int id;
|
||||||
|
for (id = 0; id < ctx->n_workers && ctx->workers[id] != pthread_self(); id++);
|
||||||
|
logger_set_name("%s/%i", ctx->name, id);
|
||||||
|
|
||||||
|
while (ctx->alive) {
|
||||||
|
// wait for buffer to be filled
|
||||||
|
if (sem_wait(&ctx->used) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
critical("Unable to lock semaphore");
|
||||||
|
errno = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock rd field
|
||||||
|
if (sem_wait(&ctx->lck_rd) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
sem_post(&ctx->used);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
critical("Unable to lock semaphore");
|
||||||
|
errno = 0;
|
||||||
|
sem_post(&ctx->used);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int p = ctx->rd;
|
||||||
|
ctx->rd = (ctx->rd + 1) % ctx->size;
|
||||||
|
|
||||||
|
// unlock rd field
|
||||||
|
sem_post(&ctx->lck_rd);
|
||||||
|
|
||||||
|
// consume object
|
||||||
|
ctx->consumer(ctx->buffer[p]);
|
||||||
|
logger_set_prefix("");
|
||||||
|
|
||||||
|
// unlock slot in buffer
|
||||||
|
sem_post(&ctx->free);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mpmc_stop(mpmc_t *ctx) {
|
||||||
|
ctx->alive = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mpmc_destroy(mpmc_t *ctx) {
|
||||||
|
int e = errno;
|
||||||
|
|
||||||
|
// stop threads, if running
|
||||||
|
mpmc_stop(ctx);
|
||||||
|
for (int i = 0; i < ctx->n_workers; i++) {
|
||||||
|
if (ctx->workers[i] == -1) break;
|
||||||
|
debug("Waiting for worker %s/%i to finish...", ctx->name, i);
|
||||||
|
pthread_kill(ctx->workers[i], SIGUSR1);
|
||||||
|
pthread_join(ctx->workers[i], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
sem_destroy(&ctx->free);
|
||||||
|
sem_destroy(&ctx->used);
|
||||||
|
sem_destroy(&ctx->lck_rd);
|
||||||
|
sem_destroy(&ctx->lck_wr);
|
||||||
|
free(ctx->buffer);
|
||||||
|
free(ctx->workers);
|
||||||
|
|
||||||
|
// reset errno
|
||||||
|
errno = e;
|
||||||
|
}
|
||||||
|
|
27
src/lib/mpmc.h
Normal file
27
src/lib/mpmc.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
#ifndef SESIMOS_MPMC_H
|
||||||
|
#define SESIMOS_MPMC_H
|
||||||
|
|
||||||
|
#include <semaphore.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char alive;
|
||||||
|
int n_workers;
|
||||||
|
int rd, wr;
|
||||||
|
sem_t free, used, lck_rd, lck_wr;
|
||||||
|
int size, max_size;
|
||||||
|
void **buffer;
|
||||||
|
pthread_t *workers;
|
||||||
|
void (*consumer)(void *obj);
|
||||||
|
const char* name;
|
||||||
|
} mpmc_t;
|
||||||
|
|
||||||
|
int mpmc_init(mpmc_t *ctx, int n_workers, int buf_size, void (*consumer)(void *obj), const char *name);
|
||||||
|
|
||||||
|
int mpmc_queue(mpmc_t *ctx, void *obj);
|
||||||
|
|
||||||
|
void mpmc_stop(mpmc_t *ctx);
|
||||||
|
|
||||||
|
void mpmc_destroy(mpmc_t *ctx);
|
||||||
|
|
||||||
|
#endif //SESIMOS_MPMC_H
|
694
src/lib/proxy.c
Normal file
694
src/lib/proxy.c
Normal file
@ -0,0 +1,694 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Reverse proxy
|
||||||
|
* @file src/lib/proxy.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-01-07
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../defs.h"
|
||||||
|
#include "../server.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "proxy.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
|
||||||
|
static SSL_CTX *proxy_ctx = NULL;
|
||||||
|
static proxy_ctx_t *proxies = NULL;
|
||||||
|
static sem_t *available = NULL;
|
||||||
|
static sem_t lock;
|
||||||
|
static int num_proxy_hosts = -1;
|
||||||
|
|
||||||
|
int proxy_preload(void) {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
|
||||||
|
host_config_t *hc = &config.hosts[i];
|
||||||
|
if (hc->type == CONFIG_TYPE_UNSET) break;
|
||||||
|
if (hc->type != CONFIG_TYPE_REVERSE_PROXY) continue;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_ctx = SSL_CTX_new(TLS_client_method());
|
||||||
|
if (proxy_ctx == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxies = malloc(n * MAX_PROXY_CNX_PER_HOST * sizeof(proxy_ctx_t));
|
||||||
|
if (proxies == NULL) {
|
||||||
|
proxy_unload();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memset(proxies, 0, n * MAX_PROXY_CNX_PER_HOST * sizeof(proxy_ctx_t));
|
||||||
|
|
||||||
|
available = malloc(n * sizeof(*available));
|
||||||
|
if (available == NULL) {
|
||||||
|
proxy_unload();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (sem_init(&available[i], 0, MAX_PROXY_CNX_PER_HOST) != 0) {
|
||||||
|
proxy_unload();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sem_init(&lock, 0, 1) != 0) {
|
||||||
|
proxy_unload();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
num_proxy_hosts = n;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void proxy_unload(void) {
|
||||||
|
int e = errno;
|
||||||
|
SSL_CTX_free(proxy_ctx);
|
||||||
|
sem_destroy(&lock);
|
||||||
|
if (num_proxy_hosts != -1) {
|
||||||
|
for (int i = 0; i < num_proxy_hosts; i++) {
|
||||||
|
sem_destroy(&available[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(available);
|
||||||
|
free(proxies);
|
||||||
|
errno = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
void proxy_close_all(void) {
|
||||||
|
proxy_ctx_t *ptr = proxies;
|
||||||
|
for (int i = 0; i < MAX_PROXY_CNX_PER_HOST * num_proxy_hosts; i++, ptr++) {
|
||||||
|
if (ptr->initialized) {
|
||||||
|
proxy_close(ptr);
|
||||||
|
logger_set_prefix("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_ctx_t *proxy_get_by_conf(host_config_t *conf) {
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
|
||||||
|
host_config_t *hc = &config.hosts[i];
|
||||||
|
if (hc->type == CONFIG_TYPE_UNSET) break;
|
||||||
|
if (hc->type != CONFIG_TYPE_REVERSE_PROXY) continue;
|
||||||
|
if (hc == conf) break;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sem_wait(&available[n]) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sem_wait(&lock) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
sem_post(&available[n]);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_ctx_t *ptr = proxies + n * MAX_PROXY_CNX_PER_HOST;
|
||||||
|
for (int i = 0; i < MAX_PROXY_CNX_PER_HOST; i++, ptr++) {
|
||||||
|
if (!ptr->in_use) {
|
||||||
|
ptr->in_use = 1;
|
||||||
|
sem_post(&lock);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sem_post(&lock);
|
||||||
|
sem_post(&available[n]);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int proxy_unlock_ctx(proxy_ctx_t *ctx) {
|
||||||
|
int n = (int) ((ctx - proxies) / MAX_PROXY_CNX_PER_HOST);
|
||||||
|
if (ctx->close) proxy_close(ctx);
|
||||||
|
|
||||||
|
debug("Released proxy connection slot %i/%i", (ctx - proxies) % MAX_PROXY_CNX_PER_HOST, MAX_PROXY_CNX_PER_HOST);
|
||||||
|
ctx->in_use = 0;
|
||||||
|
ctx->client = NULL;
|
||||||
|
sem_post(&available[n]);
|
||||||
|
if (!ctx->close) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
ctx->close = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int proxy_request_header(http_req *req, sock *sock) {
|
||||||
|
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)) {
|
||||||
|
error("Header field 'Via' too long");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
http_remove_header_field(&req->hdr, "Via", HTTP_REMOVE_ALL);
|
||||||
|
http_add_header_field(&req->hdr, "Via", buf2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int client_ipv6 = strchr(sock->addr, ':') != NULL;
|
||||||
|
int server_ipv6 = strchr(sock->s_addr, ':') != NULL;
|
||||||
|
|
||||||
|
p_len = snprintf(buf1, sizeof(buf1), "by=%s%s%s;for=%s%s%s;host=%s;proto=%s",
|
||||||
|
server_ipv6 ? "\"[" : "", sock->s_addr, server_ipv6 ? "]\"" : "",
|
||||||
|
client_ipv6 ? "\"[" : "", sock->addr, client_ipv6 ? "]\"" : "",
|
||||||
|
http_get_header_field(&req->hdr, "Host"), sock->enc ? "https" : "http");
|
||||||
|
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
||||||
|
error("Appended part of header field 'Forwarded' too long");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *forwarded = http_get_header_field(&req->hdr, "Forwarded");
|
||||||
|
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)) {
|
||||||
|
error("Header field 'Forwarded' too long");
|
||||||
|
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", sock->addr);
|
||||||
|
} else {
|
||||||
|
sprintf(buf1, "%s, %s", xff, sock->addr);
|
||||||
|
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");
|
||||||
|
forwarded = http_get_header_field(&req->hdr, "Forwarded");
|
||||||
|
if (xfh == NULL) {
|
||||||
|
if (forwarded == NULL) {
|
||||||
|
http_add_header_field(&req->hdr, "X-Forwarded-Host", http_get_header_field(&req->hdr, "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");
|
||||||
|
forwarded = http_get_header_field(&req->hdr, "Forwarded");
|
||||||
|
if (xfp == NULL) {
|
||||||
|
if (forwarded == NULL) {
|
||||||
|
http_add_header_field(&req->hdr, "X-Forwarded-Proto", sock->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 proxy_response_header(http_req *req, http_res *res, host_config_t *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)) {
|
||||||
|
error("Appended part of header field 'Via' too long");
|
||||||
|
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)) {
|
||||||
|
error("Header field 'Via' too long");
|
||||||
|
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->proxy.hostname};
|
||||||
|
int found = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < sizeof(hostnames) / sizeof(hostnames[0]); i++) {
|
||||||
|
char *hostname = hostnames[i];
|
||||||
|
found = 1;
|
||||||
|
|
||||||
|
p_len = snprintf(buf1, sizeof(buf1), "http://%s/", hostname);
|
||||||
|
if (strstarts(location, buf1)) break;
|
||||||
|
|
||||||
|
p_len = snprintf(buf1, sizeof(buf1), "https://%s/", hostname);
|
||||||
|
if (strstarts(location, buf1)) break;
|
||||||
|
|
||||||
|
p_len = snprintf(buf1, sizeof(buf1), "http://%s:%i/", hostname, conf->proxy.port);
|
||||||
|
if (strstarts(location, buf1)) break;
|
||||||
|
|
||||||
|
p_len = snprintf(buf1, sizeof(buf1), "https://%s:%i/", hostname, conf->proxy.port);
|
||||||
|
if (strstarts(location, buf1)) break;
|
||||||
|
|
||||||
|
found = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int proxy_connect(proxy_ctx_t *proxy, host_config_t *conf, http_res *res, http_status_ctx *ctx, char *err_msg) {
|
||||||
|
char err_buf[256], addr_buf[1024];
|
||||||
|
|
||||||
|
info(BLUE_STR "Connecting to " BLD_STR "[%s]:%i" CLR_STR BLUE_STR "...", conf->proxy.hostname, conf->proxy.port);
|
||||||
|
|
||||||
|
int fd;
|
||||||
|
if ((fd = sock_connect(conf->proxy.hostname, conf->proxy.port, SERVER_SOCKET_TIMEOUT_INIT, addr_buf, sizeof(addr_buf))) == -1) {
|
||||||
|
if (errno == ETIMEDOUT || errno == EINPROGRESS || errno == EHOSTDOWN || errno == EHOSTUNREACH) {
|
||||||
|
res->status = http_get_status(504);
|
||||||
|
ctx->origin = SERVER_REQ;
|
||||||
|
} else if (errno == ECONNREFUSED) {
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
ctx->origin = SERVER_REQ;
|
||||||
|
} else if (errno == ECONNABORTED || errno == ECONNRESET) {
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
ctx->origin = SERVER_RES;
|
||||||
|
} else {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
ctx->origin = INTERNAL;
|
||||||
|
}
|
||||||
|
error("Unable to connect to [%s]:%i", addr_buf, conf->proxy.port);
|
||||||
|
sprintf(err_msg, "Unable to connect to server: %s.", error_str(errno, err_buf, sizeof(err_buf)));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sock_init(&proxy->proxy, fd, 0);
|
||||||
|
|
||||||
|
if (sock_set_timeout(&proxy->proxy, SERVER_TIMEOUT) != 0) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
ctx->origin = INTERNAL;
|
||||||
|
error("Unable to set timeout for reverse proxy socket");
|
||||||
|
sprintf(err_msg, "Unable to set timeout for reverse proxy socket: %s", error_str(errno, err_buf, sizeof(err_buf)));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conf->proxy.enc) {
|
||||||
|
proxy->proxy.ssl = SSL_new(proxy_ctx);
|
||||||
|
SSL_set_fd(proxy->proxy.ssl, proxy->proxy.socket);
|
||||||
|
SSL_set_connect_state(proxy->proxy.ssl);
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
if ((ret = SSL_do_handshake(proxy->proxy.ssl)) != 1) {
|
||||||
|
sock_error(&proxy->proxy, (int) ret);
|
||||||
|
SSL_free(proxy->proxy.ssl);
|
||||||
|
proxy->proxy.ssl = NULL;
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
ctx->origin = SERVER_REQ;
|
||||||
|
error("Unable to perform handshake");
|
||||||
|
sprintf(err_msg, "Unable to perform handshake: %s.", error_str(errno, err_buf, sizeof(err_buf)));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
proxy->proxy.enc = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy->initialized = 1;
|
||||||
|
proxy->cnx_s = clock_micros();
|
||||||
|
proxy->host = conf->name;
|
||||||
|
proxy->http_timeout = 0;
|
||||||
|
|
||||||
|
info(BLUE_STR "Established new connection with " BLD_STR "[%s]:%i" CLR_STR BLUE_STR " (slot %i/%i)",
|
||||||
|
addr_buf, conf->proxy.port, (proxy - proxies) % MAX_PROXY_CNX_PER_HOST, MAX_PROXY_CNX_PER_HOST);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int proxy_init(proxy_ctx_t **proxy_ptr, http_req *req, http_res *res, http_status_ctx *ctx, host_config_t *conf, sock *client, http_status *custom_status, char *err_msg) {
|
||||||
|
char buffer[CHUNK_SIZE], err_buf[256];
|
||||||
|
long ret;
|
||||||
|
|
||||||
|
*proxy_ptr = proxy_get_by_conf(conf);
|
||||||
|
proxy_ctx_t *proxy = *proxy_ptr;
|
||||||
|
proxy->client = NULL;
|
||||||
|
debug("Selected proxy connection slot %i/%i", (proxy - proxies) % MAX_PROXY_CNX_PER_HOST, MAX_PROXY_CNX_PER_HOST);
|
||||||
|
|
||||||
|
const char *connection = http_get_header_field(&req->hdr, "Connection");
|
||||||
|
if (strcontains(connection, "upgrade") || strcontains(connection, "Upgrade")) {
|
||||||
|
const char *upgrade = http_get_header_field(&req->hdr, "Upgrade");
|
||||||
|
const char *ws_version = http_get_header_field(&req->hdr, "Sec-WebSocket-Version");
|
||||||
|
if (streq(upgrade, "websocket") && streq(ws_version, "13")) {
|
||||||
|
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 = proxy_request_header(req, client);
|
||||||
|
if (ret != 0) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
ctx->origin = INTERNAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int retry = 1, srv_error = 0, tries = 0;; tries++) {
|
||||||
|
errno = 0;
|
||||||
|
if (!retry)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// honor server timeout with one second buffer
|
||||||
|
if (!proxy->initialized || srv_error ||
|
||||||
|
(proxy->http_timeout > 0 && (clock_micros() - proxy->proxy.ts_last_send) >= proxy->http_timeout) ||
|
||||||
|
sock_has_pending(&proxy->proxy, SOCK_DONTWAIT))
|
||||||
|
{
|
||||||
|
if (proxy->initialized)
|
||||||
|
proxy_close(proxy);
|
||||||
|
|
||||||
|
retry = 0;
|
||||||
|
srv_error = 0;
|
||||||
|
tries++;
|
||||||
|
|
||||||
|
if (proxy_connect(proxy, conf, res, ctx, err_msg) != 0)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = http_send_request(&proxy->proxy, req);
|
||||||
|
if (ret < 0) {
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
ctx->origin = SERVER_REQ;
|
||||||
|
error("Unable to send request to server (1)");
|
||||||
|
sprintf(err_msg, "Unable to send request to server: %s.", error_str(errno, err_buf, sizeof(err_buf)));
|
||||||
|
retry = tries < 4;
|
||||||
|
srv_error = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *client_expect = http_get_header_field(&req->hdr, "Expect");
|
||||||
|
int expect_100_continue = (client_expect != NULL && strcasecmp(client_expect, "100-continue") == 0);
|
||||||
|
int ignore_content = 0;
|
||||||
|
if (expect_100_continue) {
|
||||||
|
http_res tmp_res = {
|
||||||
|
.version = "1.1",
|
||||||
|
.status = http_get_status(501),
|
||||||
|
};
|
||||||
|
if (http_init_hdr(&tmp_res.hdr) != 0) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
ctx->origin = INTERNAL;
|
||||||
|
error("Unable to initialize http header");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = proxy_peek_response(proxy, &tmp_res, ctx, custom_status, err_msg);
|
||||||
|
http_free_hdr(&tmp_res.hdr);
|
||||||
|
if (ret < 0)
|
||||||
|
return (int) ret;
|
||||||
|
|
||||||
|
if (tmp_res.status->code == 100) {
|
||||||
|
if (sock_recv_x(&proxy->proxy, buffer, ret, 0) == -1) {
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
ctx->origin = SERVER_RES;
|
||||||
|
error("Unable to receive from server");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
info("%s -> %03i %s%s", http_get_status_color(tmp_res.status->code), tmp_res.status->code, tmp_res.status->msg, CLR_STR);
|
||||||
|
if (http_send_response(client, &tmp_res) != 0) {
|
||||||
|
res->status = http_get_status(400);
|
||||||
|
ctx->origin = CLIENT_RES;
|
||||||
|
error("Unable to send to client");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ignore_content = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignore_content) {
|
||||||
|
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(&proxy->proxy, client, buffer, sizeof(buffer), content_len);
|
||||||
|
} else if (strcontains(transfer_encoding, "chunked")) {
|
||||||
|
ret = sock_splice_chunked(&proxy->proxy, client, buffer, sizeof(buffer), SOCK_CHUNKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0 || (content_len != 0 && ret != content_len)) {
|
||||||
|
if (ret == -1 && errno != EPROTO) {
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
ctx->origin = SERVER_REQ;
|
||||||
|
error("Unable to send request to server (2)");
|
||||||
|
sprintf(err_msg, "Unable to send request to server: %s.", error_str(errno, err_buf, sizeof(err_buf)));
|
||||||
|
return -1;
|
||||||
|
} else if (ret == -1) {
|
||||||
|
res->status = http_get_status(400);
|
||||||
|
ctx->origin = CLIENT_REQ;
|
||||||
|
error("Unable to receive request from client");
|
||||||
|
sprintf(err_msg, "Unable to receive request from client: %s.", error_str(errno, err_buf, sizeof(err_buf)));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
ctx->origin = INTERNAL;
|
||||||
|
error("Unknown Error");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock_set_socket_timeout(&proxy->proxy, SERVER_SOCKET_TIMEOUT_RES) != 0) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
ctx->origin = INTERNAL;
|
||||||
|
error("Unable to set timeout for reverse proxy socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
ret = proxy_peek_response(proxy, res, ctx, custom_status, err_msg);
|
||||||
|
if (ret < 0) {
|
||||||
|
return (int) ret;
|
||||||
|
} else if (sock_recv_x(&proxy->proxy, buffer, ret, 0) == -1) {
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
ctx->origin = SERVER_RES;
|
||||||
|
error("Unable to receive from server");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (res->status->code == 100) {
|
||||||
|
info("%s -> %03i %s%s", http_get_status_color(res->status->code), res->status->code, res->status->msg, CLR_STR);
|
||||||
|
if (http_send_response(client, res) != 0) {
|
||||||
|
res->status = http_get_status(400);
|
||||||
|
ctx->origin = CLIENT_RES;
|
||||||
|
error("Unable to send to client");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long keep_alive_timeout = http_get_keep_alive_timeout(&res->hdr);
|
||||||
|
proxy->http_timeout = (keep_alive_timeout > 0) ? keep_alive_timeout * 1000000 : 0;
|
||||||
|
|
||||||
|
connection = http_get_header_field(&res->hdr, "Connection");
|
||||||
|
proxy->close = !streq(res->version, "1.1") || strcontains(connection, "close") || strcontains(connection, "Close");
|
||||||
|
|
||||||
|
ret = proxy_response_header(req, res, conf);
|
||||||
|
if (ret != 0) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
ctx->origin = INTERNAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int proxy_peek_response(proxy_ctx_t *proxy, http_res *res, http_status_ctx *ctx, http_status *custom_status, char *err_msg) {
|
||||||
|
char buffer[CHUNK_SIZE], err_buf[256];
|
||||||
|
long ret;
|
||||||
|
|
||||||
|
ret = sock_recv(&proxy->proxy, buffer, sizeof(buffer) - 1, MSG_PEEK);
|
||||||
|
if (ret <= 0) {
|
||||||
|
int e_sys = error_get_sys(), e_ssl = error_get_ssl();
|
||||||
|
if (e_sys == EAGAIN || e_sys == EINPROGRESS || e_ssl == SSL_ERROR_WANT_READ || e_ssl == 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;
|
||||||
|
}
|
||||||
|
error("Unable to receive response from server");
|
||||||
|
sprintf(err_msg, "Unable to receive response from server: %s.", error_str(errno, err_buf, sizeof(err_buf)));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buffer[ret] = 0;
|
||||||
|
|
||||||
|
if (sock_set_socket_timeout(&proxy->proxy, SOCKET_TIMEOUT) != 0) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
ctx->origin = INTERNAL;
|
||||||
|
error("Unable to set timeout for reverse proxy socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
error("Unable to parse header: End of header not found");
|
||||||
|
sprintf(err_msg, "Unable to parser header: End of header not found.");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
error("Unable to parse header: Header contains illegal characters");
|
||||||
|
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
error("Unable to parse header: Invalid header format");
|
||||||
|
sprintf(err_msg, "Unable to parse header: Invalid header format.");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
if (ptr == buf) {
|
||||||
|
if (!strstarts(ptr, "HTTP/")) {
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
ctx->origin = SERVER_RES;
|
||||||
|
error("Unable to parse header: Invalid header format");
|
||||||
|
sprintf(err_msg, "Unable to parse header: Invalid header format.");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
custom_status->type = 0;
|
||||||
|
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;
|
||||||
|
error("Unable to parse header: Invalid or unknown status code");
|
||||||
|
sprintf(err_msg, "Unable to parse header: Invalid or unknown status code.");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (http_parse_header_field(&res->hdr, ptr, pos0, 0) != 0) {
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
ctx->origin = SERVER_RES;
|
||||||
|
error("Unable to parse header");
|
||||||
|
sprintf(err_msg, "Unable to parse header.");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ptr = pos0 + 2;
|
||||||
|
}
|
||||||
|
return header_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int proxy_send(proxy_ctx_t *proxy, sock *client, unsigned long len_to_send, int flags) {
|
||||||
|
char buffer[CHUNK_SIZE];
|
||||||
|
if (sock_splice(client, &proxy->proxy, buffer, sizeof(buffer), len_to_send) == -1)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int proxy_dump(proxy_ctx_t *proxy, char *buf, long len) {
|
||||||
|
long ret = sock_recv(&proxy->proxy, buf, len, 0);
|
||||||
|
if (ret == -1) return -1;
|
||||||
|
buf[ret] = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void proxy_close(proxy_ctx_t *ctx) {
|
||||||
|
client_ctx_t *cctx = ctx->client;
|
||||||
|
if (cctx) {
|
||||||
|
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, cctx->req_host, CLR_STR, cctx->log_prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->initialized) {
|
||||||
|
ctx->cnx_e = clock_micros();
|
||||||
|
char buf[32];
|
||||||
|
info(BLUE_STR "Closing proxy connection %i/%i (%s)",
|
||||||
|
(ctx - proxies) % MAX_PROXY_CNX_PER_HOST, MAX_PROXY_CNX_PER_HOST,
|
||||||
|
format_duration(ctx->cnx_e - ctx->cnx_s, buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
sock_close(&ctx->proxy);
|
||||||
|
ctx->initialized = 0;
|
||||||
|
ctx->http_timeout = 0;
|
||||||
|
ctx->cnx_e = 0, ctx->cnx_s = 0;
|
||||||
|
ctx->client = NULL;
|
||||||
|
ctx->host = NULL;
|
||||||
|
errno = 0;
|
||||||
|
}
|
54
src/lib/proxy.h
Normal file
54
src/lib/proxy.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Reverse proxy (header file)
|
||||||
|
* @file src/lib/proxy.h
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-01-07
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SESIMOS_PROXY_H
|
||||||
|
#define SESIMOS_PROXY_H
|
||||||
|
|
||||||
|
#define PROXY_CHUNKED 1
|
||||||
|
|
||||||
|
#ifndef SERVER_NAME
|
||||||
|
# define SERVER_NAME "reverse proxy"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "http.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
volatile unsigned char initialized:1, in_use:1, close:1;
|
||||||
|
sock proxy;
|
||||||
|
long cnx_s, cnx_e;
|
||||||
|
long http_timeout;
|
||||||
|
char *host;
|
||||||
|
void *client;
|
||||||
|
} proxy_ctx_t;
|
||||||
|
|
||||||
|
int proxy_preload(void);
|
||||||
|
|
||||||
|
void proxy_unload(void);
|
||||||
|
|
||||||
|
void proxy_close_all(void);
|
||||||
|
|
||||||
|
proxy_ctx_t *proxy_get_by_conf(host_config_t *conf);
|
||||||
|
|
||||||
|
int proxy_unlock_ctx(proxy_ctx_t *ctx);
|
||||||
|
|
||||||
|
int proxy_request_header(http_req *req, sock *sock);
|
||||||
|
|
||||||
|
int proxy_response_header(http_req *req, http_res *res, host_config_t *conf);
|
||||||
|
|
||||||
|
int proxy_init(proxy_ctx_t **proxy, http_req *req, http_res *res, http_status_ctx *ctx, host_config_t *conf, sock *client, http_status *custom_status, char *err_msg);
|
||||||
|
|
||||||
|
int proxy_peek_response(proxy_ctx_t *proxy, http_res *res, http_status_ctx *ctx, http_status *custom_status, char *err_msg);
|
||||||
|
|
||||||
|
int proxy_send(proxy_ctx_t *proxy, sock *client, unsigned long len_to_send, int flags);
|
||||||
|
|
||||||
|
int proxy_dump(proxy_ctx_t *proxy, char *buf, long len);
|
||||||
|
|
||||||
|
void proxy_close(proxy_ctx_t *ctx);
|
||||||
|
|
||||||
|
#endif //SESIMOS_PROXY_H
|
62
src/lib/res.h
Normal file
62
src/lib/res.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief HTTP resources (header file)
|
||||||
|
* @file src/lib/res.h
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-31
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SESIMOS_RES_H
|
||||||
|
#define SESIMOS_RES_H
|
||||||
|
|
||||||
|
#define http_default_doc _binary_bin_res_default_txt_start
|
||||||
|
#define http_default_doc_size ((unsigned int) (_binary_bin_res_default_txt_end - _binary_bin_res_default_txt_start) - 1)
|
||||||
|
#define http_proxy_doc _binary_bin_res_proxy_txt_start
|
||||||
|
#define http_proxy_doc_size ((unsigned int) (_binary_bin_res_proxy_txt_end - _binary_bin_res_proxy_txt_start) - 1)
|
||||||
|
#define http_style_doc _binary_bin_res_style_txt_start
|
||||||
|
#define http_style_doc_size ((unsigned int) (_binary_bin_res_style_txt_end - _binary_bin_res_style_txt_start) - 1)
|
||||||
|
|
||||||
|
#define http_icon_error _binary_bin_res_icon_error_txt_start
|
||||||
|
#define http_icon_error_size ((unsigned int) (_binary_bin_res_icon_error_txt_end - _binary_bin_res_icon_error_txt_start) - 1)
|
||||||
|
#define http_icon_info _binary_bin_res_icon_info_txt_start
|
||||||
|
#define http_icon_info_size ((unsigned int) (_binary_bin_res_icon_info_txt_end - _binary_bin_res_icon_info_txt_start) - 1)
|
||||||
|
#define http_icon_success _binary_bin_res_icon_success_txt_start
|
||||||
|
#define http_icon_success_size ((unsigned int) (_binary_bin_res_icon_success_txt_end - _binary_bin_res_icon_success_txt_start) - 1)
|
||||||
|
#define http_icon_warning _binary_bin_res_icon_warning_txt_start
|
||||||
|
#define http_icon_warning_size ((unsigned int) (_binary_bin_res_icon_warning_txt_end - _binary_bin_res_icon_warning_txt_start) - 1)
|
||||||
|
|
||||||
|
#define http_icon_globe _binary_bin_res_globe_txt_start
|
||||||
|
#define http_icon_globe_size ((unsigned int) (_binary_bin_res_globe_txt_end - _binary_bin_res_globe_txt_start) - 1)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
const char *type;
|
||||||
|
const char *content;
|
||||||
|
const unsigned int size;
|
||||||
|
} res_t;
|
||||||
|
|
||||||
|
extern const char _binary_bin_res_default_txt_start[];
|
||||||
|
extern const char _binary_bin_res_default_txt_end[];
|
||||||
|
|
||||||
|
extern const char _binary_bin_res_proxy_txt_start[];
|
||||||
|
extern const char _binary_bin_res_proxy_txt_end[];
|
||||||
|
|
||||||
|
extern const char _binary_bin_res_style_txt_start[];
|
||||||
|
extern const char _binary_bin_res_style_txt_end[];
|
||||||
|
|
||||||
|
extern const char _binary_bin_res_icon_error_txt_start[];
|
||||||
|
extern const char _binary_bin_res_icon_error_txt_end[];
|
||||||
|
|
||||||
|
extern const char _binary_bin_res_icon_info_txt_start[];
|
||||||
|
extern const char _binary_bin_res_icon_info_txt_end[];
|
||||||
|
|
||||||
|
extern const char _binary_bin_res_icon_success_txt_start[];
|
||||||
|
extern const char _binary_bin_res_icon_success_txt_end[];
|
||||||
|
|
||||||
|
extern const char _binary_bin_res_icon_warning_txt_start[];
|
||||||
|
extern const char _binary_bin_res_icon_warning_txt_end[];
|
||||||
|
|
||||||
|
extern const char _binary_bin_res_globe_txt_start[];
|
||||||
|
extern const char _binary_bin_res_globe_txt_end[];
|
||||||
|
|
||||||
|
#endif //SESIMOS_RES_H
|
@ -1,541 +0,0 @@
|
|||||||
/**
|
|
||||||
* Necronda Web Server
|
|
||||||
* Reverse proxy
|
|
||||||
* src/lib/rev_proxy.c
|
|
||||||
* Lorenz Stechauner, 2021-01-07
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "rev_proxy.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "compress.h"
|
|
||||||
#include "../necronda-server.h"
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
|
|
||||||
sock rev_proxy;
|
|
||||||
char *rev_proxy_host = NULL;
|
|
||||||
struct timeval server_timeout = {.tv_sec = SERVER_TIMEOUT, .tv_usec = 0};
|
|
||||||
|
|
||||||
int rev_proxy_preload() {
|
|
||||||
rev_proxy.buf = NULL;
|
|
||||||
rev_proxy.buf_len = 0;
|
|
||||||
rev_proxy.buf_off = 0;
|
|
||||||
rev_proxy.ctx = SSL_CTX_new(TLS_client_method());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rev_proxy_request_header(http_req *req, int enc) {
|
|
||||||
char buf1[256];
|
|
||||||
char buf2[256];
|
|
||||||
int p_len;
|
|
||||||
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
|
|
||||||
http_add_header_field(&req->hdr, "Connection", "keep-alive");
|
|
||||||
|
|
||||||
char *via = http_get_header_field(&req->hdr, "Via");
|
|
||||||
sprintf(buf1, "HTTP/%s %s", req->version, SERVER_NAME);
|
|
||||||
if (via == NULL) {
|
|
||||||
http_add_header_field(&req->hdr, "Via", buf1);
|
|
||||||
} else {
|
|
||||||
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
|
|
||||||
if (p_len < 0 || p_len >= sizeof(buf2)) {
|
|
||||||
print(ERR_STR "Header field 'Via' too long" CLR_STR);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
http_remove_header_field(&req->hdr, "Via", HTTP_REMOVE_ALL);
|
|
||||||
http_add_header_field(&req->hdr, "Via", buf2);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *host = http_get_header_field(&req->hdr, "Host");
|
|
||||||
char *forwarded = http_get_header_field(&req->hdr, "Forwarded");
|
|
||||||
int client_ipv6 = strchr(client_addr_str, ':') != NULL;
|
|
||||||
int server_ipv6 = strchr(server_addr_str, ':') != NULL;
|
|
||||||
|
|
||||||
p_len = snprintf(buf1, sizeof(buf1), "by=%s%s%s;for=%s%s%s;host=%s;proto=%s",
|
|
||||||
server_ipv6 ? "\"[" : "", server_addr_str, server_ipv6 ? "]\"" : "",
|
|
||||||
client_ipv6 ? "\"[" : "", client_addr_str, client_ipv6 ? "]\"" : "",
|
|
||||||
host, enc ? "https" : "http");
|
|
||||||
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
|
||||||
print(ERR_STR "Appended part of header field 'Forwarded' too long" CLR_STR);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (forwarded == NULL) {
|
|
||||||
http_add_header_field(&req->hdr, "Forwarded", buf1);
|
|
||||||
} else {
|
|
||||||
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", forwarded, buf1);
|
|
||||||
if (p_len < 0 || p_len >= sizeof(buf2)) {
|
|
||||||
print(ERR_STR "Header field 'Forwarded' too long" CLR_STR);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
http_remove_header_field(&req->hdr, "Forwarded", HTTP_REMOVE_ALL);
|
|
||||||
http_add_header_field(&req->hdr, "Forwarded", buf2);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *xff = http_get_header_field(&req->hdr, "X-Forwarded-For");
|
|
||||||
if (xff == NULL) {
|
|
||||||
http_add_header_field(&req->hdr, "X-Forwarded-For", client_addr_str);
|
|
||||||
} else {
|
|
||||||
sprintf(buf1, "%s, %s", xff, client_addr_str);
|
|
||||||
http_remove_header_field(&req->hdr, "X-Forwarded-For", HTTP_REMOVE_ALL);
|
|
||||||
http_add_header_field(&req->hdr, "X-Forwarded-For", buf1);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *xfh = http_get_header_field(&req->hdr, "X-Forwarded-Host");
|
|
||||||
if (xfh == NULL) {
|
|
||||||
if (forwarded == NULL) {
|
|
||||||
http_add_header_field(&req->hdr, "X-Forwarded-Host", host);
|
|
||||||
} else {
|
|
||||||
char *ptr = strchr(forwarded, ',');
|
|
||||||
unsigned long len;
|
|
||||||
if (ptr != NULL) len = ptr - forwarded;
|
|
||||||
else len = strlen(forwarded);
|
|
||||||
ptr = strstr(forwarded, "host=");
|
|
||||||
if ((ptr - forwarded) < len) {
|
|
||||||
char *end = strchr(ptr, ';');
|
|
||||||
if (end == NULL) len -= (ptr - forwarded);
|
|
||||||
else len = (end - ptr);
|
|
||||||
len -= 5;
|
|
||||||
sprintf(buf1, "%.*s", (int) len, ptr + 5);
|
|
||||||
http_add_header_field(&req->hdr, "X-Forwarded-Host", buf1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *xfp = http_get_header_field(&req->hdr, "X-Forwarded-Proto");
|
|
||||||
if (xfp == NULL) {
|
|
||||||
if (forwarded == NULL) {
|
|
||||||
http_add_header_field(&req->hdr, "X-Forwarded-Proto", enc ? "https" : "http");
|
|
||||||
} else {
|
|
||||||
char *ptr = strchr(forwarded, ',');
|
|
||||||
unsigned long len;
|
|
||||||
if (ptr != NULL) len = ptr - forwarded;
|
|
||||||
else len = strlen(forwarded);
|
|
||||||
ptr = strstr(forwarded, "proto=");
|
|
||||||
if ((ptr - forwarded) < len) {
|
|
||||||
char *end = strchr(ptr, ';');
|
|
||||||
if (end == NULL) len -= (ptr - forwarded);
|
|
||||||
else len = (end - ptr);
|
|
||||||
len -= 6;
|
|
||||||
sprintf(buf1, "%.*s", (int) len, ptr + 6);
|
|
||||||
http_add_header_field(&req->hdr, "X-Forwarded-Proto", buf1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rev_proxy_response_header(http_req *req, http_res *res) {
|
|
||||||
char buf1[256];
|
|
||||||
char buf2[256];
|
|
||||||
int p_len;
|
|
||||||
|
|
||||||
char *via = http_get_header_field(&res->hdr, "Via");
|
|
||||||
p_len = snprintf(buf1, sizeof(buf1), "HTTP/%s %s", req->version, SERVER_NAME);
|
|
||||||
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
|
||||||
print(ERR_STR "Appended part of header field 'Via' too long" CLR_STR);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (via == NULL) {
|
|
||||||
http_add_header_field(&res->hdr, "Via", buf1);
|
|
||||||
} else {
|
|
||||||
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
|
|
||||||
if (p_len < 0 || p_len >= sizeof(buf2)) {
|
|
||||||
print(ERR_STR "Header field 'Via' too long" CLR_STR);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
http_remove_header_field(&res->hdr, "Via", HTTP_REMOVE_ALL);
|
|
||||||
http_add_header_field(&res->hdr, "Via", buf2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_config *conf, sock *client,
|
|
||||||
http_status *custom_status, char *err_msg) {
|
|
||||||
char buffer[CHUNK_SIZE];
|
|
||||||
long ret;
|
|
||||||
int tries = 0;
|
|
||||||
int retry = 0;
|
|
||||||
|
|
||||||
if (rev_proxy.socket != 0 && strcmp(rev_proxy_host, conf->name) == 0 && sock_check(&rev_proxy) == 0) {
|
|
||||||
goto rev_proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
retry:
|
|
||||||
if (rev_proxy.socket != 0) {
|
|
||||||
print(BLUE_STR "Closing proxy connection" CLR_STR);
|
|
||||||
sock_close(&rev_proxy);
|
|
||||||
}
|
|
||||||
retry = 0;
|
|
||||||
tries++;
|
|
||||||
|
|
||||||
rev_proxy.socket = socket(AF_INET6, SOCK_STREAM, 0);
|
|
||||||
if (rev_proxy.socket < 0) {
|
|
||||||
print(ERR_STR "Unable to create socket: %s" CLR_STR, strerror(errno));
|
|
||||||
res->status = http_get_status(500);
|
|
||||||
ctx->origin = INTERNAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
server_timeout.tv_sec = SERVER_TIMEOUT_INIT;
|
|
||||||
server_timeout.tv_usec = 0;
|
|
||||||
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
|
|
||||||
goto rev_proxy_timeout_err;
|
|
||||||
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
|
|
||||||
goto rev_proxy_timeout_err;
|
|
||||||
|
|
||||||
struct hostent *host_ent = gethostbyname2(conf->rev_proxy.hostname, AF_INET6);
|
|
||||||
if (host_ent == NULL) {
|
|
||||||
host_ent = gethostbyname2(conf->rev_proxy.hostname, AF_INET);
|
|
||||||
if (host_ent == NULL) {
|
|
||||||
res->status = http_get_status(503);
|
|
||||||
ctx->origin = SERVER_REQ;
|
|
||||||
print(ERR_STR "Unable to connect to server: Name or service not known" CLR_STR);
|
|
||||||
sprintf(err_msg, "Unable to connect to server: Name or service not known.");
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_in6 address = {.sin6_family = AF_INET6, .sin6_port = htons(conf->rev_proxy.port)};
|
|
||||||
if (host_ent->h_addrtype == AF_INET6) {
|
|
||||||
memcpy(&address.sin6_addr, host_ent->h_addr_list[0], host_ent->h_length);
|
|
||||||
} else if (host_ent->h_addrtype == AF_INET) {
|
|
||||||
unsigned char addr[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0};
|
|
||||||
memcpy(addr + 12, host_ent->h_addr_list[0], host_ent->h_length);
|
|
||||||
memcpy(&address.sin6_addr, addr, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
inet_ntop(address.sin6_family, (void *) &address.sin6_addr, buffer, sizeof(buffer));
|
|
||||||
|
|
||||||
print(BLUE_STR "Connecting to " BLD_STR "[%s]:%i" CLR_STR BLUE_STR "..." CLR_STR, buffer, conf->rev_proxy.port);
|
|
||||||
if (connect(rev_proxy.socket, (struct sockaddr *) &address, sizeof(address)) < 0) {
|
|
||||||
if (errno == ETIMEDOUT || errno == EINPROGRESS) {
|
|
||||||
res->status = http_get_status(504);
|
|
||||||
ctx->origin = SERVER_REQ;
|
|
||||||
} else if (errno == ECONNREFUSED) {
|
|
||||||
res->status = http_get_status(503);
|
|
||||||
ctx->origin = SERVER_REQ;
|
|
||||||
} else {
|
|
||||||
res->status = http_get_status(500);
|
|
||||||
ctx->origin = INTERNAL;
|
|
||||||
}
|
|
||||||
print(ERR_STR "Unable to connect to [%s]:%i: %s" CLR_STR, buffer, conf->rev_proxy.port, strerror(errno));
|
|
||||||
sprintf(err_msg, "Unable to connect to server: %s.", strerror(errno));
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
|
|
||||||
server_timeout.tv_sec = SERVER_TIMEOUT;
|
|
||||||
server_timeout.tv_usec = 0;
|
|
||||||
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
|
|
||||||
goto rev_proxy_timeout_err;
|
|
||||||
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0) {
|
|
||||||
rev_proxy_timeout_err:
|
|
||||||
res->status = http_get_status(500);
|
|
||||||
ctx->origin = INTERNAL;
|
|
||||||
print(ERR_STR "Unable to set timeout for reverse proxy socket: %s" CLR_STR, strerror(errno));
|
|
||||||
sprintf(err_msg, "Unable to set timeout for reverse proxy socket: %s", strerror(errno));
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conf->rev_proxy.enc) {
|
|
||||||
rev_proxy.ssl = SSL_new(rev_proxy.ctx);
|
|
||||||
SSL_set_fd(rev_proxy.ssl, rev_proxy.socket);
|
|
||||||
SSL_set_connect_state(rev_proxy.ssl);
|
|
||||||
|
|
||||||
ret = SSL_do_handshake(rev_proxy.ssl);
|
|
||||||
rev_proxy._last_ret = ret;
|
|
||||||
rev_proxy._errno = errno;
|
|
||||||
rev_proxy._ssl_error = ERR_get_error();
|
|
||||||
rev_proxy.enc = 1;
|
|
||||||
if (ret < 0) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_REQ;
|
|
||||||
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(&rev_proxy));
|
|
||||||
sprintf(err_msg, "Unable to perform handshake: %s.", sock_strerror(&rev_proxy));
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rev_proxy_host = conf->name;
|
|
||||||
print(BLUE_STR "Established new connection with " BLD_STR "[%s]:%i" CLR_STR, buffer, conf->rev_proxy.port);
|
|
||||||
|
|
||||||
rev_proxy:
|
|
||||||
ret = rev_proxy_request_header(req, (int) client->enc);
|
|
||||||
if (ret != 0) {
|
|
||||||
res->status = http_get_status(500);
|
|
||||||
ctx->origin = INTERNAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = http_send_request(&rev_proxy, req);
|
|
||||||
if (ret < 0) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_REQ;
|
|
||||||
print(ERR_STR "Unable to send request to server (1): %s" CLR_STR, sock_strerror(&rev_proxy));
|
|
||||||
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
|
|
||||||
retry = tries < 4;
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
|
|
||||||
if (content_length != NULL) {
|
|
||||||
unsigned long content_len = strtoul(content_length, NULL, 10);
|
|
||||||
if (client->buf_len - client->buf_off > 0) {
|
|
||||||
unsigned long len = client->buf_len - client->buf_off;
|
|
||||||
if (len > content_len) {
|
|
||||||
len = content_len;
|
|
||||||
}
|
|
||||||
ret = sock_send(&rev_proxy, client->buf, len, 0);
|
|
||||||
if (ret <= 0) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_REQ;
|
|
||||||
print(ERR_STR "Unable to send request to server (2): %s" CLR_STR, sock_strerror(&rev_proxy));
|
|
||||||
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
|
|
||||||
retry = tries < 4;
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
content_len -= len;
|
|
||||||
}
|
|
||||||
if (content_len > 0) {
|
|
||||||
ret = sock_splice(&rev_proxy, client, buffer, sizeof(buffer), content_len);
|
|
||||||
if (ret <= 0) {
|
|
||||||
if (ret == -1) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_REQ;
|
|
||||||
print(ERR_STR "Unable to send request to server (3): %s" CLR_STR, sock_strerror(&rev_proxy));
|
|
||||||
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
|
|
||||||
goto proxy_err;
|
|
||||||
} else if (ret == -2) {
|
|
||||||
res->status = http_get_status(400);
|
|
||||||
ctx->origin = CLIENT_REQ;
|
|
||||||
print(ERR_STR "Unable to receive request from client: %s" CLR_STR, sock_strerror(client));
|
|
||||||
sprintf(err_msg, "Unable to receive request from client: %s.", sock_strerror(client));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
res->status = http_get_status(500);
|
|
||||||
ctx->origin = INTERNAL;
|
|
||||||
print(ERR_STR "Unknown Error" CLR_STR);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = sock_recv(&rev_proxy, buffer, sizeof(buffer), MSG_PEEK);
|
|
||||||
if (ret <= 0) {
|
|
||||||
int enc_err = sock_enc_error(&rev_proxy);
|
|
||||||
if (errno == EAGAIN || errno == EINPROGRESS || enc_err == SSL_ERROR_WANT_READ ||
|
|
||||||
enc_err == SSL_ERROR_WANT_WRITE)
|
|
||||||
{
|
|
||||||
res->status = http_get_status(504);
|
|
||||||
ctx->origin = SERVER_RES;
|
|
||||||
} else {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_RES;
|
|
||||||
}
|
|
||||||
print(ERR_STR "Unable to receive response from server: %s" CLR_STR, sock_strerror(&rev_proxy));
|
|
||||||
sprintf(err_msg, "Unable to receive response from server: %s.", sock_strerror(&rev_proxy));
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *buf = buffer;
|
|
||||||
unsigned short header_len = (unsigned short) (strstr(buffer, "\r\n\r\n") - buffer + 4);
|
|
||||||
|
|
||||||
if (header_len <= 0) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_RES;
|
|
||||||
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
|
|
||||||
sprintf(err_msg, "Unable to parser header: End of header not found.");
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < header_len; i++) {
|
|
||||||
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_RES;
|
|
||||||
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
|
|
||||||
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *ptr = buf;
|
|
||||||
while (header_len != (ptr - buf)) {
|
|
||||||
char *pos0 = strstr(ptr, "\r\n");
|
|
||||||
if (pos0 == NULL) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_RES;
|
|
||||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
|
||||||
sprintf(err_msg, "Unable to parse header: Invalid header format.");
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
if (ptr == buf) {
|
|
||||||
if (strncmp(ptr, "HTTP/", 5) != 0) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_RES;
|
|
||||||
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
|
|
||||||
sprintf(err_msg, "Unable to parse header: Invalid header format.");
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
int status_code = (int) strtol(ptr + 9, NULL, 10);
|
|
||||||
res->status = http_get_status(status_code);
|
|
||||||
if (res->status == NULL && status_code >= 100 && status_code <= 999) {
|
|
||||||
custom_status->code = status_code;
|
|
||||||
strcpy(custom_status->type, "");
|
|
||||||
snprintf(custom_status->msg, sizeof(custom_status->msg), "%.*s",
|
|
||||||
(int) (strchr(ptr, '\r') - ptr - 13), ptr + 13);
|
|
||||||
res->status = custom_status;
|
|
||||||
} else if (res->status == NULL) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_RES;
|
|
||||||
print(ERR_STR "Unable to parse header: Invalid or unknown status code" CLR_STR);
|
|
||||||
sprintf(err_msg, "Unable to parse header: Invalid or unknown status code.");
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ret = http_parse_header_field(&res->hdr, ptr, pos0);
|
|
||||||
if (ret != 0) {
|
|
||||||
res->status = http_get_status(502);
|
|
||||||
ctx->origin = SERVER_RES;
|
|
||||||
print(ERR_STR "Unable to parse header" CLR_STR);
|
|
||||||
sprintf(err_msg, "Unable to parse header.");
|
|
||||||
goto proxy_err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pos0[2] == '\r' && pos0[3] == '\n') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ptr = pos0 + 2;
|
|
||||||
}
|
|
||||||
sock_recv(&rev_proxy, buffer, header_len, 0);
|
|
||||||
|
|
||||||
ret = rev_proxy_response_header(req, res);
|
|
||||||
if (ret != 0) {
|
|
||||||
res->status = http_get_status(500);
|
|
||||||
ctx->origin = INTERNAL;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
proxy_err:
|
|
||||||
if (retry) goto retry;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rev_proxy_send(sock *client, unsigned long len_to_send, int flags) {
|
|
||||||
// TODO handle websockets
|
|
||||||
long ret;
|
|
||||||
char buffer[CHUNK_SIZE];
|
|
||||||
char comp_out[CHUNK_SIZE];
|
|
||||||
char buf[256];
|
|
||||||
long len, snd_len;
|
|
||||||
int finish_comp = 0;
|
|
||||||
char *ptr;
|
|
||||||
|
|
||||||
compress_ctx comp_ctx;
|
|
||||||
if (flags & REV_PROXY_COMPRESS_BR) {
|
|
||||||
flags &= ~REV_PROXY_COMPRESS_GZ;
|
|
||||||
if (compress_init(&comp_ctx, COMPRESS_BR) != 0) {
|
|
||||||
print(ERR_STR "Unable to init brotli: %s" CLR_STR, strerror(errno));
|
|
||||||
flags &= ~REV_PROXY_COMPRESS_BR;
|
|
||||||
}
|
|
||||||
} else if (flags & REV_PROXY_COMPRESS_GZ) {
|
|
||||||
flags &= ~REV_PROXY_COMPRESS_BR;
|
|
||||||
if (compress_init(&comp_ctx, COMPRESS_GZ) != 0) {
|
|
||||||
print(ERR_STR "Unable to init gzip: %s" CLR_STR, strerror(errno));
|
|
||||||
flags &= ~REV_PROXY_COMPRESS_GZ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
snd_len = 0;
|
|
||||||
if (flags & REV_PROXY_CHUNKED) {
|
|
||||||
char *pos;
|
|
||||||
ret = sock_recv(&rev_proxy, buffer, 16, MSG_PEEK);
|
|
||||||
if (ret <= 0) goto err0;
|
|
||||||
|
|
||||||
len_to_send = strtol(buffer, NULL, 16);
|
|
||||||
pos = strstr(buffer, "\r\n");
|
|
||||||
len = pos - buffer + 2;
|
|
||||||
sock_recv(&rev_proxy, buffer, len, 0);
|
|
||||||
if (ret <= 0) {
|
|
||||||
err0:
|
|
||||||
print("Unable to receive from server: %s", sock_strerror(&rev_proxy));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len_to_send == 0 && (flags & REV_PROXY_COMPRESS)) {
|
|
||||||
finish_comp = 1;
|
|
||||||
len = 0;
|
|
||||||
goto out;
|
|
||||||
finish:
|
|
||||||
compress_free(&comp_ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (snd_len < len_to_send) {
|
|
||||||
unsigned long avail_in, avail_out;
|
|
||||||
ret = sock_recv(&rev_proxy, buffer, CHUNK_SIZE < (len_to_send - snd_len) ? CHUNK_SIZE : len_to_send - snd_len, 0);
|
|
||||||
if (ret <= 0) {
|
|
||||||
print("Unable to receive from server: %s", sock_strerror(&rev_proxy));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
len = ret;
|
|
||||||
ptr = buffer;
|
|
||||||
out:
|
|
||||||
avail_in = len;
|
|
||||||
void *next_in = ptr;
|
|
||||||
do {
|
|
||||||
long buf_len = len;
|
|
||||||
if (flags & REV_PROXY_COMPRESS) {
|
|
||||||
avail_out = sizeof(comp_out);
|
|
||||||
compress_compress(&comp_ctx, next_in + len - avail_in, &avail_in, comp_out, &avail_out,
|
|
||||||
finish_comp);
|
|
||||||
ptr = comp_out;
|
|
||||||
buf_len = (int) (sizeof(comp_out) - avail_out);
|
|
||||||
snd_len += (long) (len - avail_in);
|
|
||||||
}
|
|
||||||
if (buf_len != 0) {
|
|
||||||
len = sprintf(buf, "%lX\r\n", buf_len);
|
|
||||||
ret = 1;
|
|
||||||
if (flags & REV_PROXY_CHUNKED) ret = sock_send(client, buf, len, 0);
|
|
||||||
if (ret <= 0) goto err;
|
|
||||||
ret = sock_send(client, ptr, buf_len, 0);
|
|
||||||
if (ret <= 0) goto err;
|
|
||||||
if (!(flags & REV_PROXY_COMPRESS)) snd_len += ret;
|
|
||||||
if (flags & REV_PROXY_CHUNKED) ret = sock_send(client, "\r\n", 2, 0);
|
|
||||||
if (ret <= 0) {
|
|
||||||
err:
|
|
||||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while ((flags & REV_PROXY_COMPRESS) && (avail_in != 0 || avail_out != sizeof(comp_out)));
|
|
||||||
if (ret <= 0) break;
|
|
||||||
if (finish_comp) goto finish;
|
|
||||||
}
|
|
||||||
if (ret <= 0) break;
|
|
||||||
if (flags & REV_PROXY_CHUNKED) sock_recv(&rev_proxy, buffer, 2, 0);
|
|
||||||
} while ((flags & REV_PROXY_CHUNKED) && len_to_send > 0);
|
|
||||||
|
|
||||||
if (ret <= 0) return (int) -1;
|
|
||||||
|
|
||||||
if (flags & REV_PROXY_CHUNKED) {
|
|
||||||
ret = sock_send(client, "0\r\n\r\n", 5, 0);
|
|
||||||
if (ret <= 0) {
|
|
||||||
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int rev_proxy_dump(char *buf, long len) {
|
|
||||||
sock_recv(&rev_proxy, buf, len, 0);
|
|
||||||
sock_close(&rev_proxy);
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* Necronda Web Server
|
|
||||||
* Reverse proxy (header file)
|
|
||||||
* src/lib/rev_proxy.h
|
|
||||||
* Lorenz Stechauner, 2021-01-07
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_REV_PROXY_H
|
|
||||||
#define NECRONDA_SERVER_REV_PROXY_H
|
|
||||||
|
|
||||||
#define REV_PROXY_CHUNKED 1
|
|
||||||
#define REV_PROXY_COMPRESS_GZ 2
|
|
||||||
#define REV_PROXY_COMPRESS_BR 4
|
|
||||||
#define REV_PROXY_COMPRESS 6
|
|
||||||
|
|
||||||
#ifndef SERVER_NAME
|
|
||||||
# define SERVER_NAME "revproxy"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "http.h"
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
extern sock rev_proxy;
|
|
||||||
|
|
||||||
int rev_proxy_preload();
|
|
||||||
|
|
||||||
int rev_proxy_request_header(http_req *req, int enc);
|
|
||||||
|
|
||||||
int rev_proxy_response_header(http_req *req, http_res *res);
|
|
||||||
|
|
||||||
int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_config *conf, sock *client,
|
|
||||||
http_status *custom_status, char *err_msg);
|
|
||||||
|
|
||||||
int rev_proxy_send(sock *client, unsigned long len_to_send, int flags);
|
|
||||||
|
|
||||||
int rev_proxy_dump(char *buf, long len);
|
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_REV_PROXY_H
|
|
512
src/lib/sock.c
512
src/lib/sock.c
@ -1,118 +1,486 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* Basic TCP and TLS socket
|
* @brief Basic TCP and TLS socket
|
||||||
* src/lib/sock.c
|
* @file src/lib/sock.c
|
||||||
* Lorenz Stechauner, 2021-01-07
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-01-07
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "sock.h"
|
#include "sock.h"
|
||||||
#include <openssl/err.h>
|
#include "utils.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
int sock_enc_error(sock *s) {
|
static void sock_ssl_error(unsigned long err) {
|
||||||
return (int) s->enc ? SSL_get_error(s->ssl, (int) s->_last_ret) : 0;
|
if (err == SSL_ERROR_NONE) {
|
||||||
|
errno = 0;
|
||||||
|
} else if (err == SSL_ERROR_SYSCALL) {
|
||||||
|
// errno already set
|
||||||
|
} else if (err == SSL_ERROR_SSL) {
|
||||||
|
error_ssl_err(ERR_get_error());
|
||||||
|
} else {
|
||||||
|
error_ssl(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *sock_strerror(sock *s) {
|
void sock_error(sock *s, int ret) {
|
||||||
if (s->_last_ret == 0) {
|
sock_ssl_error(SSL_get_error(s->ssl, ret));
|
||||||
return "closed";
|
}
|
||||||
} else if (s->enc) {
|
|
||||||
if (s->_last_ret > 0) {
|
int sock_gai_error(int ret) {
|
||||||
return NULL;
|
if (ret == 0) {
|
||||||
}
|
errno = 0;
|
||||||
const char *err1 = ERR_reason_error_string(s->_ssl_error);
|
} else if (ret == EAI_SYSTEM) {
|
||||||
const char *err2 = strerror(errno);
|
// errno already set
|
||||||
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 {
|
} else {
|
||||||
return strerror(s->_errno);
|
error_gai(ret);
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *sock_error_str(unsigned long err) {
|
||||||
|
switch (err) {
|
||||||
|
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_WANT_ASYNC:
|
||||||
|
return "want async";
|
||||||
|
case SSL_ERROR_WANT_ASYNC_JOB:
|
||||||
|
return "want async job";
|
||||||
|
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
|
||||||
|
return "want client hello callback";
|
||||||
|
//case SSL_ERROR_WANT_RETRY_VERIFY:
|
||||||
|
// return "want retry verify";
|
||||||
|
default:
|
||||||
|
return "unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_init(sock *s, int fd, int flags) {
|
||||||
|
if ((flags & SOCK_ENCRYPTED) && (flags & SOCK_PIPE)) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->socket = fd;
|
||||||
|
s->enc = !!(flags & SOCK_ENCRYPTED);
|
||||||
|
s->pipe = !!(flags & SOCK_PIPE);
|
||||||
|
s->ts_start = clock_micros();
|
||||||
|
s->ts_last = s->ts_start;
|
||||||
|
s->ts_last_send = s->ts_last;
|
||||||
|
s->timeout_us = -1;
|
||||||
|
s->ssl = NULL;
|
||||||
|
s->addr = NULL;
|
||||||
|
s->s_addr = NULL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_connect(const char *hostname, unsigned short port, double timeout_sec, char *addr_buf, size_t addr_buf_size) {
|
||||||
|
char buf[INET6_ADDRSTRLEN + 1];
|
||||||
|
int ret, fd, e = 0;
|
||||||
|
long timeout_micros = (long) (timeout_sec * 1000000L);
|
||||||
|
struct addrinfo *result, *rp,
|
||||||
|
hints = {
|
||||||
|
.ai_family = AF_UNSPEC,
|
||||||
|
.ai_socktype = SOCK_STREAM,
|
||||||
|
.ai_protocol = 0,
|
||||||
|
.ai_flags = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (addr_buf && addr_buf_size > 1)
|
||||||
|
addr_buf[0] = 0;
|
||||||
|
|
||||||
|
if ((ret = getaddrinfo(hostname, NULL, &hints, &result)) != 0)
|
||||||
|
return sock_gai_error(ret);
|
||||||
|
|
||||||
|
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
||||||
|
switch (rp->ai_family) {
|
||||||
|
case AF_INET:
|
||||||
|
((struct sockaddr_in *) rp->ai_addr)->sin_port = htons(port);
|
||||||
|
inet_ntop(rp->ai_family, &((struct sockaddr_in *) rp->ai_addr)->sin_addr, buf, addr_buf_size);
|
||||||
|
break;
|
||||||
|
case AF_INET6:
|
||||||
|
((struct sockaddr_in6 *) rp->ai_addr)->sin6_port = htons(port);
|
||||||
|
inet_ntop(rp->ai_family, &((struct sockaddr_in6 *) rp->ai_addr)->sin6_addr, buf, addr_buf_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Trying [%s]:%i", buf, port);
|
||||||
|
|
||||||
|
if ((fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) {
|
||||||
|
if (e == 0) {
|
||||||
|
e = errno;
|
||||||
|
} else if (e != errno) {
|
||||||
|
e = -1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock_set_socket_timeout_micros(fd, timeout_micros, timeout_micros) == -1) {
|
||||||
|
close(fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
|
||||||
|
e = errno;
|
||||||
|
close(fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
freeaddrinfo(result);
|
||||||
|
|
||||||
|
if (addr_buf && addr_buf_size > 1 && addr_buf[0] == 0)
|
||||||
|
strncpy(addr_buf, buf, addr_buf_size);
|
||||||
|
|
||||||
|
errno = e;
|
||||||
|
return (e == 0) ? fd : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_reverse_lookup(const sock *s, char *host, size_t host_size) {
|
||||||
|
memset(host, 0, host_size);
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
if ((ret = getnameinfo(&s->_addr.sock, sizeof(s->_addr), host, host_size, NULL, 0, 0)) != 0) {
|
||||||
|
if (ret == EAI_NONAME) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return sock_gai_error(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_set_socket_timeout_micros(int fd, long recv_micros, long send_micros) {
|
||||||
|
struct timeval recv_to = {.tv_sec = recv_micros / 1000000, .tv_usec = recv_micros % 1000000},
|
||||||
|
send_to = {.tv_sec = send_micros / 1000000, .tv_usec = send_micros % 1000000};
|
||||||
|
|
||||||
|
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &recv_to, sizeof(recv_to)) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &send_to, sizeof(send_to)) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_set_socket_timeout(sock *s, double sec) {
|
||||||
|
return sock_set_socket_timeout_micros(s->socket, (long) (sec * 1000000L), (long) (sec * 1000000L));
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_set_timeout_micros(sock *s, long micros) {
|
||||||
|
if (micros < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
s->timeout_us = micros;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_set_timeout(sock *s, double sec) {
|
||||||
|
return sock_set_timeout_micros(s, (long) (sec * 1000000));
|
||||||
}
|
}
|
||||||
|
|
||||||
long sock_send(sock *s, void *buf, unsigned long len, int flags) {
|
long sock_send(sock *s, void *buf, unsigned long len, int flags) {
|
||||||
|
if (s->socket < 0) {
|
||||||
|
errno = ENOTCONN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
long ret;
|
long ret;
|
||||||
if (s->enc) {
|
if (s->enc) {
|
||||||
ret = SSL_write(s->ssl, buf, (int) len);
|
ret = SSL_write(s->ssl, buf, (int) len);
|
||||||
s->_ssl_error = ERR_get_error();
|
if (ret <= 0) sock_error(s, (int) ret);
|
||||||
|
} else if (s->pipe) {
|
||||||
|
if (flags & ~MSG_MORE) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ret = write(s->socket, buf, len);
|
||||||
} else {
|
} else {
|
||||||
ret = send(s->socket, buf, len, flags);
|
ret = send(s->socket, buf, len, flags);
|
||||||
}
|
}
|
||||||
s->_last_ret = ret;
|
|
||||||
s->_errno = errno;
|
if (ret >= 0) {
|
||||||
return ret >= 0 ? ret : -1;
|
s->ts_last = clock_micros();
|
||||||
|
s->ts_last_send = s->ts_last;
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long sock_send_x(sock *s, void *buf, unsigned long len, int flags) {
|
||||||
|
for (long ret, sent = 0; sent < len; sent += ret) {
|
||||||
|
if ((ret = sock_send(s, (unsigned char *) buf + sent, len - sent, flags)) <= 0) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN) {
|
||||||
|
errno = 0, ret = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (long) len;
|
||||||
}
|
}
|
||||||
|
|
||||||
long sock_recv(sock *s, void *buf, unsigned long len, int flags) {
|
long sock_recv(sock *s, void *buf, unsigned long len, int flags) {
|
||||||
|
if (s->socket < 0) {
|
||||||
|
errno = ENOTCONN;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
long ret;
|
long ret;
|
||||||
if (s->enc) {
|
if (s->enc) {
|
||||||
if (flags & MSG_PEEK) {
|
int (*func)(SSL *, void *, int) = (flags & MSG_PEEK) ? SSL_peek : SSL_read;
|
||||||
ret = SSL_peek(s->ssl, buf, (int) len);
|
ret = func(s->ssl, buf, (int) len);
|
||||||
} else {
|
if (ret <= 0) sock_error(s, (int) ret);
|
||||||
ret = SSL_read(s->ssl, buf, (int) len);
|
} else if (s->pipe) {
|
||||||
|
if (flags & ~MSG_WAITALL) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
s->_ssl_error = ERR_get_error();
|
ret = read(s->socket, buf, len);
|
||||||
} else {
|
} else {
|
||||||
ret = recv(s->socket, buf, len, flags);
|
ret = recv(s->socket, buf, len, flags);
|
||||||
}
|
}
|
||||||
s->_last_ret = ret;
|
|
||||||
s->_errno = errno;
|
if (ret >= 0) {
|
||||||
return ret >= 0 ? ret : -1;
|
s->ts_last = clock_micros();
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long sock_recv_x(sock *s, void *buf, unsigned long len, int flags) {
|
||||||
|
for (long ret, rcv = 0; rcv < len; rcv += ret) {
|
||||||
|
if ((ret = sock_recv(s, (unsigned char *) buf + rcv, len - rcv, flags | MSG_WAITALL)) <= 0) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN) {
|
||||||
|
errno = 0, ret = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (long) len;
|
||||||
}
|
}
|
||||||
|
|
||||||
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len) {
|
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len) {
|
||||||
long ret;
|
long send_len = 0;
|
||||||
unsigned long send_len = 0;
|
|
||||||
unsigned long next_len;
|
if ((src->pipe || dst->pipe) && !src->enc && !dst->enc) {
|
||||||
while (send_len < len) {
|
for (long ret; send_len < len; send_len += ret) {
|
||||||
next_len = (buf_len < (len - send_len)) ? buf_len : (len - send_len);
|
if ((ret = splice(src->socket, 0, dst->socket, 0, len, 0)) == -1) {
|
||||||
ret = sock_recv(src, buf, next_len, 0);
|
if (errno == EINTR || errno == EAGAIN) {
|
||||||
if (ret < 0) return -2;
|
errno = 0, ret = 0;
|
||||||
next_len = ret;
|
continue;
|
||||||
ret = sock_send(dst, buf, next_len, send_len + next_len < len ? MSG_MORE : 0);
|
} else {
|
||||||
if (ret < 0) return -1;
|
return -1;
|
||||||
if (ret != next_len) return -3;
|
}
|
||||||
send_len += next_len;
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (long ret, next_len; send_len < len; send_len += ret) {
|
||||||
|
next_len = (long) ((buf_len < (len - send_len)) ? buf_len : (len - send_len));
|
||||||
|
|
||||||
|
if ((ret = sock_recv(src, buf, next_len, MSG_WAITALL)) <= 0) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN) {
|
||||||
|
errno = 0, ret = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock_send_x(dst, buf, ret, send_len + ret < len ? MSG_MORE : 0) == -1)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return send_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
long sock_splice_all(sock *dst, sock *src, void *buf, unsigned long buf_len) {
|
||||||
|
long send_len = 0;
|
||||||
|
for (long ret;; send_len += ret) {
|
||||||
|
if ((ret = sock_recv(src, buf, buf_len, 0)) <= 0) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN) {
|
||||||
|
errno = 0, ret = 0;
|
||||||
|
continue;
|
||||||
|
} else if (ret == 0) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock_send_x(dst, buf, ret, 0) == -1)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return send_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
long sock_splice_chunked(sock *dst, sock *src, void *buf, unsigned long buf_len, int flags) {
|
||||||
|
long ret;
|
||||||
|
unsigned long send_len = 0, next_len;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if ((ret = sock_recv_chunk_header(src)) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
next_len = ret;
|
||||||
|
|
||||||
|
if (flags & SOCK_CHUNKED)
|
||||||
|
if (sock_send_chunk_header(dst, next_len) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if ((ret = sock_splice(dst, src, buf, buf_len, next_len)) == -1)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
send_len += ret;
|
||||||
|
|
||||||
|
if (sock_recv_chunk_trailer(src) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (flags & SOCK_CHUNKED)
|
||||||
|
if (sock_send_chunk_trailer(dst) == -1)
|
||||||
|
return -1;
|
||||||
|
} while (!(flags & SOCK_SINGLE_CHUNK) && next_len != 0);
|
||||||
|
|
||||||
return (long) send_len;
|
return (long) send_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sock_close(sock *s) {
|
int sock_close(sock *s) {
|
||||||
if ((int) s->enc && s->ssl != NULL) {
|
int e = errno;
|
||||||
if (s->_last_ret >= 0) SSL_shutdown(s->ssl);
|
if (s->ssl != NULL) {
|
||||||
|
SSL_shutdown(s->ssl);
|
||||||
SSL_free(s->ssl);
|
SSL_free(s->ssl);
|
||||||
s->ssl = NULL;
|
s->ssl = NULL;
|
||||||
}
|
}
|
||||||
shutdown(s->socket, SHUT_RDWR);
|
if (s->socket != -1) close(s->socket);
|
||||||
close(s->socket);
|
s->socket = -1;
|
||||||
s->socket = 0;
|
s->enc = 0, s->pipe = 0;
|
||||||
s->enc = 0;
|
errno = e;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sock_check(sock *s) {
|
int sock_has_pending(sock *s, int flags) {
|
||||||
char buf;
|
int e = errno;
|
||||||
return recv(s->socket, &buf, 1, MSG_PEEK | MSG_DONTWAIT) == 1;
|
long ret;
|
||||||
|
if (s->pipe) {
|
||||||
|
int arg;
|
||||||
|
ioctl(s->socket, FIONREAD, &arg);
|
||||||
|
ret = arg;
|
||||||
|
} else if (s->enc && (flags & SOCK_DONTWAIT)) {
|
||||||
|
ret = SSL_pending(s->ssl);
|
||||||
|
} else {
|
||||||
|
char buf[1];
|
||||||
|
ret = sock_recv(s, &buf, sizeof(buf), MSG_PEEK | ((flags & SOCK_DONTWAIT) ? MSG_DONTWAIT : 0));
|
||||||
|
}
|
||||||
|
errno = e;
|
||||||
|
return ret > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long sock_recv_chunk_header(sock *s) {
|
||||||
|
if (s->pipe) {
|
||||||
|
uint64_t len;
|
||||||
|
if (sock_recv_x(s, &len, sizeof(len), 0) == -1)
|
||||||
|
return -1;
|
||||||
|
return (long) len;
|
||||||
|
}
|
||||||
|
|
||||||
|
long ret;
|
||||||
|
size_t len = 0;
|
||||||
|
char buf[20];
|
||||||
|
|
||||||
|
do {
|
||||||
|
if ((ret = sock_recv(s, buf, sizeof(buf) - 1, MSG_PEEK)) <= 0) {
|
||||||
|
if (errno == EINTR || errno == EAGAIN) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else if (ret < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buf[ret] = 0;
|
||||||
|
|
||||||
|
if ((ret = parse_chunk_header(buf, ret, &len)) == -1 && errno == EPROTO)
|
||||||
|
return -1;
|
||||||
|
} while (ret < 0);
|
||||||
|
|
||||||
|
if (sock_recv_x(s, buf, len, 0) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_send_chunk_header(sock *s, unsigned long size) {
|
||||||
|
if (s->pipe) {
|
||||||
|
uint64_t len = size;
|
||||||
|
if (sock_send_x(s, &len, sizeof(len), 0) == -1)
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
char buf[20];
|
||||||
|
if (sock_send_x(s, buf, sprintf(buf, "%lX\r\n", size), 0) == -1)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_recv_chunk_trailer(sock *s) {
|
||||||
|
if (s->pipe) return 0;
|
||||||
|
|
||||||
|
char buf[2];
|
||||||
|
if (sock_recv_x(s, buf, sizeof(buf), MSG_PEEK) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (buf[0] != '\r' || buf[1] != '\n') {
|
||||||
|
errno = EPROTO;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock_recv_x(s, buf, sizeof(buf), 0) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_send_chunk_trailer(sock *s) {
|
||||||
|
if (s->pipe) return 0;
|
||||||
|
if (sock_send_x(s, "\r\n", 2, 0) == -1)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sock_send_last_chunk(sock *s) {
|
||||||
|
if (s->pipe) return sock_send_chunk_header(s, 0);
|
||||||
|
if (sock_send_x(s, "0\r\n\r\n", 5, 0) == -1)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,85 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* Basic TCP and TLS socket (header file)
|
* @brief Basic TCP and TLS socket (header file)
|
||||||
* src/lib/sock.h
|
* @file src/lib/sock.h
|
||||||
* Lorenz Stechauner, 2021-01-07
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2021-01-07
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_SOCK_H
|
#ifndef SESIMOS_SOCK_H
|
||||||
#define NECRONDA_SERVER_SOCK_H
|
#define SESIMOS_SOCK_H
|
||||||
|
|
||||||
#include <openssl/crypto.h>
|
#include <openssl/crypto.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#define SOCK_CHUNKED 1
|
||||||
|
#define SOCK_SINGLE_CHUNK 2
|
||||||
|
|
||||||
|
#define SOCK_ENCRYPTED 1
|
||||||
|
#define SOCK_PIPE 2
|
||||||
|
|
||||||
|
#define SOCK_DONTWAIT 1
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned int enc:1;
|
unsigned int enc:1, pipe:1;
|
||||||
int socket;
|
int socket;
|
||||||
|
union {
|
||||||
|
struct sockaddr sock;
|
||||||
|
struct sockaddr_in6 ipv6;
|
||||||
|
} _addr;
|
||||||
|
char *addr, *s_addr;
|
||||||
SSL_CTX *ctx;
|
SSL_CTX *ctx;
|
||||||
SSL *ssl;
|
SSL *ssl;
|
||||||
char *buf;
|
long ts_start, ts_last, ts_last_send, timeout_us;
|
||||||
unsigned long buf_len;
|
|
||||||
unsigned long buf_off;
|
|
||||||
long _last_ret;
|
|
||||||
int _errno;
|
|
||||||
unsigned long _ssl_error;
|
|
||||||
} sock;
|
} sock;
|
||||||
|
|
||||||
int sock_enc_error(sock *s);
|
void sock_error(sock *s, int ret);
|
||||||
|
|
||||||
const char *sock_strerror(sock *s);
|
const char *sock_error_str(unsigned long err);
|
||||||
|
|
||||||
|
int sock_init(sock *s, int fd, int flags);
|
||||||
|
|
||||||
|
int sock_connect(const char *hostname, unsigned short port, double timeout_sec, char *addr_buf, size_t addr_buf_size);
|
||||||
|
|
||||||
|
int sock_reverse_lookup(const sock *s, char *host, size_t host_size);
|
||||||
|
|
||||||
|
int sock_init_addr_str(const sock *s, char *c_addr, size_t c_addr_size, char *s_addr, size_t s_addr_size);
|
||||||
|
|
||||||
|
int sock_set_socket_timeout_micros(int fd, long recv_micros, long send_micros);
|
||||||
|
|
||||||
|
int sock_set_socket_timeout(sock *s, double sec);
|
||||||
|
|
||||||
|
int sock_set_timeout_micros(sock *s, long micros);
|
||||||
|
|
||||||
|
int sock_set_timeout(sock *s, double sec);
|
||||||
|
|
||||||
long sock_send(sock *s, void *buf, unsigned long len, int flags);
|
long sock_send(sock *s, void *buf, unsigned long len, int flags);
|
||||||
|
|
||||||
|
long sock_send_x(sock *s, void *buf, unsigned long len, int flags);
|
||||||
|
|
||||||
long sock_recv(sock *s, void *buf, unsigned long len, int flags);
|
long sock_recv(sock *s, void *buf, unsigned long len, int flags);
|
||||||
|
|
||||||
|
long sock_recv_x(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(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len);
|
||||||
|
|
||||||
|
long sock_splice_all(sock *dst, sock *src, void *buf, unsigned long buf_len);
|
||||||
|
|
||||||
|
long sock_splice_chunked(sock *dst, sock *src, void *buf, unsigned long buf_len, int flags);
|
||||||
|
|
||||||
int sock_close(sock *s);
|
int sock_close(sock *s);
|
||||||
|
|
||||||
int sock_check(sock *s);
|
int sock_has_pending(sock *s, int flags);
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_SOCK_H
|
long sock_recv_chunk_header(sock *s);
|
||||||
|
|
||||||
|
int sock_send_chunk_header(sock *s, unsigned long size);
|
||||||
|
|
||||||
|
int sock_recv_chunk_trailer(sock *s);
|
||||||
|
|
||||||
|
int sock_send_chunk_trailer(sock *s);
|
||||||
|
|
||||||
|
int sock_send_last_chunk(sock *s);
|
||||||
|
|
||||||
|
#endif //SESIMOS_SOCK_H
|
||||||
|
119
src/lib/uri.c
119
src/lib/uri.c
@ -1,37 +1,47 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* URI and path handlers
|
* @brief URI and path handlers
|
||||||
* src/lib/uri.c
|
* @file src/lib/uri.c
|
||||||
* Lorenz Stechauner, 2020-12-13
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-13
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "uri.h"
|
#include "uri.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
int path_is_directory(const char *path) {
|
int path_is_directory(const char *path) {
|
||||||
struct stat statbuf;
|
int e = errno;
|
||||||
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode) != 0;
|
struct stat stat_buf;
|
||||||
|
int ret = stat(path, &stat_buf);
|
||||||
|
errno = e;
|
||||||
|
return ret == 0 && S_ISDIR(stat_buf.st_mode) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int path_is_file(const char *path) {
|
int path_is_file(const char *path) {
|
||||||
struct stat statbuf;
|
int e = errno;
|
||||||
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode) == 0;
|
struct stat stat_buf;
|
||||||
|
int ret = stat(path, &stat_buf);
|
||||||
|
errno = e;
|
||||||
|
return ret == 0 && S_ISDIR(stat_buf.st_mode) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int path_exists(const char *path) {
|
int path_exists(const char *path) {
|
||||||
struct stat statbuf;
|
int e = errno;
|
||||||
return stat(path, &statbuf) == 0;
|
struct stat stat_buf;
|
||||||
|
int ret = stat(path, &stat_buf);
|
||||||
|
errno = e;
|
||||||
|
return ret == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mode) {
|
int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mode) {
|
||||||
char buf0[1024];
|
char buf0[1024], buf1[1024], buf2[1024], buf3[1024], buf4[1024];
|
||||||
char buf1[1024];
|
|
||||||
char buf2[1024];
|
|
||||||
char buf3[1024];
|
|
||||||
char buf4[1024];
|
|
||||||
int p_len;
|
int p_len;
|
||||||
|
|
||||||
uri->webroot = NULL;
|
uri->webroot = NULL;
|
||||||
uri->req_path = NULL;
|
uri->req_path = NULL;
|
||||||
uri->path = NULL;
|
uri->path = NULL;
|
||||||
@ -42,9 +52,10 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
|||||||
uri->meta = NULL;
|
uri->meta = NULL;
|
||||||
uri->is_static = 1;
|
uri->is_static = 1;
|
||||||
uri->is_dir = 0;
|
uri->is_dir = 0;
|
||||||
if (uri_str[0] != '/') {
|
|
||||||
|
if (uri_str[0] != '/')
|
||||||
return 1;
|
return 1;
|
||||||
}
|
|
||||||
uri->webroot = malloc(strlen(webroot) + 1);
|
uri->webroot = malloc(strlen(webroot) + 1);
|
||||||
strcpy(uri->webroot, webroot);
|
strcpy(uri->webroot, webroot);
|
||||||
|
|
||||||
@ -62,12 +73,10 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
|||||||
long size = (long) strlen(uri_str) + 1;
|
long size = (long) strlen(uri_str) + 1;
|
||||||
uri->req_path = malloc(size);
|
uri->req_path = malloc(size);
|
||||||
url_decode(uri_str, uri->req_path, &size);
|
url_decode(uri_str, uri->req_path, &size);
|
||||||
if (query != NULL) {
|
if (query != NULL) query[-1] = '?';
|
||||||
query[-1] = '?';
|
|
||||||
}
|
if (strcontains(uri->req_path, "/../") || strcontains(uri->req_path, "/./"))
|
||||||
if (strstr(uri->req_path, "/../") != NULL || strstr(uri->req_path, "/./") != NULL) {
|
|
||||||
return 2;
|
return 2;
|
||||||
}
|
|
||||||
|
|
||||||
size = (long) strlen(uri->req_path) + 1;
|
size = (long) strlen(uri->req_path) + 1;
|
||||||
uri->path = malloc(size);
|
uri->path = malloc(size);
|
||||||
@ -83,9 +92,8 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
|||||||
last = ch;
|
last = ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir_mode == URI_DIR_MODE_NO_VALIDATION) {
|
if (dir_mode == URI_DIR_MODE_NO_VALIDATION)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (uri->path[strlen(uri->path) - 1] == '/') {
|
if (uri->path[strlen(uri->path) - 1] == '/') {
|
||||||
uri->path[strlen(uri->path) - 1] = 0;
|
uri->path[strlen(uri->path) - 1] = 0;
|
||||||
@ -94,26 +102,20 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
|||||||
strcpy(uri->pathinfo, "");
|
strcpy(uri->pathinfo, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path_exists(uri->webroot)) {
|
if (!path_exists(uri->webroot))
|
||||||
return 3;
|
return 3;
|
||||||
}
|
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
sprintf(buf0, "%s%s", uri->webroot, uri->path);
|
sprintf(buf0, "%s%s", uri->webroot, uri->path);
|
||||||
p_len = snprintf(buf1, sizeof(buf1), "%s.ncr", buf0);
|
p_len = snprintf(buf1, sizeof(buf1), "%s.xhtml", buf0);
|
||||||
if (p_len < 0 || p_len >= sizeof(buf1)) return -1;
|
if (p_len < 0 || p_len >= sizeof(buf1)) return -1;
|
||||||
p_len = snprintf(buf2, sizeof(buf2), "%s.php", buf0);
|
p_len = snprintf(buf2, sizeof(buf2), "%s.html", buf0);
|
||||||
if (p_len < 0 || p_len >= sizeof(buf2)) return -1;
|
if (p_len < 0 || p_len >= sizeof(buf2)) return -1;
|
||||||
p_len = snprintf(buf3, sizeof(buf3), "%s.html", buf0);
|
p_len = snprintf(buf3, sizeof(buf3), "%s.php", buf0);
|
||||||
if (p_len < 0 || p_len >= sizeof(buf3)) return -1;
|
if (p_len < 0 || p_len >= sizeof(buf3)) return -1;
|
||||||
|
|
||||||
if (strlen(uri->path) <= 1 ||
|
if (strlen(uri->path) <= 1 || path_exists(buf0) || path_is_file(buf1) || path_is_file(buf2) || path_is_file(buf3))
|
||||||
path_exists(buf0) ||
|
|
||||||
path_is_file(buf1) ||
|
|
||||||
path_is_file(buf2) ||
|
|
||||||
path_is_file(buf3)) {
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
char *ptr;
|
char *ptr;
|
||||||
parent_dir:
|
parent_dir:
|
||||||
@ -132,40 +134,44 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
|
|||||||
uri->filename = malloc(strlen(buf0) + 1);
|
uri->filename = malloc(strlen(buf0) + 1);
|
||||||
strcpy(uri->filename, buf0);
|
strcpy(uri->filename, buf0);
|
||||||
long len = (long) strlen(uri->path);
|
long len = (long) strlen(uri->path);
|
||||||
if (strcmp(uri->path + len - 4, ".ncr") == 0 || strcmp(uri->path + len - 4, ".php") == 0) {
|
if (strends(uri->path, ".xhtml")) {
|
||||||
|
uri->path[len - 6] = 0;
|
||||||
|
} else if (strends(uri->path, ".html")) {
|
||||||
|
uri->path[len - 5] = 0;
|
||||||
|
} else if (strends(uri->path, ".php")) {
|
||||||
uri->path[len - 4] = 0;
|
uri->path[len - 4] = 0;
|
||||||
uri->is_static = 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)) {
|
} else if (path_is_file(buf1)) {
|
||||||
uri->is_static = 0;
|
|
||||||
uri->filename = malloc(strlen(buf1) + 1);
|
uri->filename = malloc(strlen(buf1) + 1);
|
||||||
strcpy(uri->filename, buf1);
|
strcpy(uri->filename, buf1);
|
||||||
} else if (path_is_file(buf2)) {
|
} else if (path_is_file(buf2)) {
|
||||||
uri->is_static = 0;
|
|
||||||
uri->filename = malloc(strlen(buf2) + 1);
|
uri->filename = malloc(strlen(buf2) + 1);
|
||||||
strcpy(uri->filename, buf2);
|
strcpy(uri->filename, buf2);
|
||||||
} else if (path_is_file(buf3)) {
|
} else if (path_is_file(buf3)) {
|
||||||
uri->filename = malloc(strlen(buf3) + 1);
|
uri->filename = malloc(strlen(buf3) + 1);
|
||||||
strcpy(uri->filename, buf3);
|
strcpy(uri->filename, buf3);
|
||||||
|
uri->is_static = 0;
|
||||||
} else {
|
} else {
|
||||||
uri->is_dir = 1;
|
uri->is_dir = 1;
|
||||||
strcpy(uri->path + strlen(uri->path), "/");
|
strcpy(uri->path + strlen(uri->path), "/");
|
||||||
sprintf(buf1, "%s%sindex.ncr", uri->webroot, uri->path);
|
sprintf(buf1, "%s%s" "index.xhtml", uri->webroot, uri->path);
|
||||||
sprintf(buf2, "%s%sindex.php", uri->webroot, uri->path);
|
sprintf(buf2, "%s%s" "index.html", uri->webroot, uri->path);
|
||||||
sprintf(buf3, "%s%sindex.html", uri->webroot, uri->path);
|
sprintf(buf3, "%s%s" "index.php", uri->webroot, uri->path);
|
||||||
if (path_is_file(buf1)) {
|
if (path_is_file(buf3) && uri->pathinfo[0] != 0) {
|
||||||
|
uri->filename = malloc(strlen(buf3) + 1);
|
||||||
|
strcpy(uri->filename, buf3);
|
||||||
|
uri->is_static = 0;
|
||||||
|
} else if (path_is_file(buf1)) {
|
||||||
uri->filename = malloc(strlen(buf1) + 1);
|
uri->filename = malloc(strlen(buf1) + 1);
|
||||||
strcpy(uri->filename, buf1);
|
strcpy(uri->filename, buf1);
|
||||||
uri->is_static = 0;
|
|
||||||
} else if (path_is_file(buf2)) {
|
} else if (path_is_file(buf2)) {
|
||||||
uri->filename = malloc(strlen(buf2) + 1);
|
uri->filename = malloc(strlen(buf2) + 1);
|
||||||
strcpy(uri->filename, buf2);
|
strcpy(uri->filename, buf2);
|
||||||
uri->is_static = 0;
|
|
||||||
} else if (path_is_file(buf3)) {
|
} else if (path_is_file(buf3)) {
|
||||||
uri->filename = malloc(strlen(buf3) + 1);
|
uri->filename = malloc(strlen(buf3) + 1);
|
||||||
strcpy(uri->filename, buf3);
|
strcpy(uri->filename, buf3);
|
||||||
|
uri->is_static = 0;
|
||||||
} else {
|
} else {
|
||||||
if (dir_mode == URI_DIR_MODE_FORBIDDEN) {
|
if (dir_mode == URI_DIR_MODE_FORBIDDEN) {
|
||||||
uri->is_static = 1;
|
uri->is_static = 1;
|
||||||
@ -182,20 +188,17 @@ 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) {
|
if (strends(uri->path, "/index"))
|
||||||
uri->path[strlen(uri->path) - 5] = 0;
|
uri->path[strlen(uri->path) - 5] = 0;
|
||||||
}
|
|
||||||
if (strcmp(uri->pathinfo, "index.ncr") == 0 ||
|
|
||||||
strcmp(uri->pathinfo, "index.php") == 0 ||
|
|
||||||
strcmp(uri->pathinfo, "index.html") == 0) {
|
|
||||||
uri->pathinfo[0] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
sprintf(buf0, "%s%s%s%s%s", uri->path,
|
if (streq(uri->pathinfo, "index.php") || streq(uri->pathinfo, "index.html") || streq(uri->pathinfo, "index.xhtml"))
|
||||||
(strlen(uri->pathinfo) == 0 || uri->path[strlen(uri->path) - 1] == '/') ? "" : "/", uri->pathinfo,
|
uri->pathinfo[0] = 0;
|
||||||
uri->query != NULL ? "?" : "", uri->query != NULL ? uri->query : "");
|
|
||||||
uri->uri = malloc(strlen(buf0) + 1);
|
sprintf(buf4, "%s%s%s%s%s", uri->path,
|
||||||
strcpy(uri->uri, buf0);
|
(strlen(uri->pathinfo) == 0 || uri->path[strlen(uri->path) - 1] == '/') ? "" : "/",
|
||||||
|
uri->pathinfo, uri->query != NULL ? "?" : "", uri->query != NULL ? uri->query : "");
|
||||||
|
uri->uri = malloc(strlen(buf4) + 1);
|
||||||
|
strcpy(uri->uri, buf4);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* URI and path handlers (header file)
|
* @brief URI and path handlers (header file)
|
||||||
* src/lib/uri.h
|
* @file src/lib/uri.h
|
||||||
* Lorenz Stechauner, 2020-12-13
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-13
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_URI_H
|
#ifndef SESIMOS_URI_H
|
||||||
#define NECRONDA_SERVER_URI_H
|
#define SESIMOS_URI_H
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
@ -15,14 +16,18 @@
|
|||||||
#define URI_DIR_MODE_LIST 2
|
#define URI_DIR_MODE_LIST 2
|
||||||
#define URI_DIR_MODE_INFO 3
|
#define URI_DIR_MODE_INFO 3
|
||||||
|
|
||||||
|
#define URI_ETAG_SIZE 65 // SHA256 size (hex)
|
||||||
|
#define URI_TYPE_SIZE 64
|
||||||
|
#define URI_CHARSET_SIZE 16
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char etag[64];
|
char etag[URI_ETAG_SIZE];
|
||||||
char type[24];
|
char type[URI_TYPE_SIZE];
|
||||||
char charset[16];
|
char charset[URI_CHARSET_SIZE];
|
||||||
char filename_comp_gz[256];
|
char filename_comp_gz[256];
|
||||||
char filename_comp_br[256];
|
char filename_comp_br[256];
|
||||||
struct stat stat;
|
long mtime;
|
||||||
} meta_data;
|
} metadata_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *webroot; // "/srv/www/www.test.org"
|
char *webroot; // "/srv/www/www.test.org"
|
||||||
@ -32,9 +37,8 @@ typedef struct {
|
|||||||
char *query; // "username=test"
|
char *query; // "username=test"
|
||||||
char *filename; // "/account/index.php"
|
char *filename; // "/account/index.php"
|
||||||
char *uri; // "/account/login?username=test"
|
char *uri; // "/account/login?username=test"
|
||||||
meta_data *meta;
|
metadata_t *meta;
|
||||||
unsigned char is_static:1;
|
unsigned int is_static:1, is_dir:1;
|
||||||
unsigned char is_dir:1;
|
|
||||||
} http_uri;
|
} http_uri;
|
||||||
|
|
||||||
|
|
||||||
@ -44,4 +48,4 @@ int uri_init_cache(http_uri *uri);
|
|||||||
|
|
||||||
void uri_free(http_uri *uri);
|
void uri_free(http_uri *uri);
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_URI_H
|
#endif //SESIMOS_URI_H
|
||||||
|
410
src/lib/utils.c
410
src/lib/utils.c
@ -1,80 +1,94 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* Utilities
|
* @brief Utilities
|
||||||
* src/lib/utils.c
|
* @file src/lib/utils.c
|
||||||
* Lorenz Stechauner, 2020-12-03
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-03
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
|
||||||
|
static const char base64_encode_table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
static const int base64_mod_table[3] = {0, 2, 1};
|
||||||
|
|
||||||
char *log_prefix;
|
|
||||||
|
|
||||||
char *format_duration(unsigned long micros, char *buf) {
|
char *format_duration(unsigned long micros, char *buf) {
|
||||||
if (micros < 10000) {
|
if (micros < 10000) {
|
||||||
sprintf(buf, "%.1f ms", (double) micros / 1000);
|
sprintf(buf, "%.1f ms", (double) micros / 1000);
|
||||||
} else if (micros < 1000000) {
|
} else if (micros < 1000000 - 1000) {
|
||||||
sprintf(buf, "%li ms", micros / 1000);
|
sprintf(buf, "%.0f ms", (double) micros / 1000);
|
||||||
} else if (micros < 60000000) {
|
} else if (micros < 60000000 - 1000000) {
|
||||||
sprintf(buf, "%.1f s", (double) micros / 1000000);
|
sprintf(buf, "%.1f s", (double) micros / 1000000);
|
||||||
} else if (micros < 6000000000) {
|
} else if (micros < 6000000000) {
|
||||||
sprintf(buf, "%.1f min", (double) micros / 1000000 / 60);
|
sprintf(buf, "%li:%02li min", micros / 1000000 / 60, micros / 1000000 % 60);
|
||||||
} else {
|
} else {
|
||||||
sprintf(buf, "%li min", micros / 1000000 / 60);
|
sprintf(buf, "%.0f min", (double) micros / 1000000 / 60);
|
||||||
}
|
}
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
int url_encode_component(const char *str, char *enc, long *size) {
|
int url_encode_component(const void *in, size_t size_in, char *out, size_t size_out) {
|
||||||
char *ptr = enc;
|
int size = 0;
|
||||||
char ch;
|
|
||||||
memset(enc, 0, *size);
|
// Encode control characters
|
||||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
for (int i = 0; i < size_in; i++) {
|
||||||
if ((ptr - enc) >= *size) {
|
unsigned char ch = ((unsigned char *) in)[i];
|
||||||
return -1;
|
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;
|
||||||
}
|
}
|
||||||
ch = str[i];
|
|
||||||
if (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@' || ch == '!' ||
|
if (ch != 0) {
|
||||||
ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '+' || ch == ',' ||
|
size++;
|
||||||
ch == ';' || ch == '=' || ch < ' ' || ch > '~') {
|
if (size < size_out) out[size - 1] = (char) 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;
|
// 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 char *str, char *enc, long *size) {
|
int url_encode(const void *in, size_t size_in, char *out, size_t size_out) {
|
||||||
char *ptr = enc;
|
int size = 0;
|
||||||
unsigned char ch;
|
|
||||||
memset(enc, 0, *size);
|
// Encode control characters
|
||||||
for (int i = 0; i < strlen(str); i++, ptr++) {
|
for (int i = 0; i < size_in; i++) {
|
||||||
if ((ptr - enc) >= *size) {
|
unsigned char ch = ((unsigned char *) in)[i];
|
||||||
return -1;
|
if (ch <= 0x20 || ch >= 0x7F) {
|
||||||
}
|
size += 3;
|
||||||
ch = str[i];
|
if (size < size_out) sprintf(out + size - 3, "%%%02X", ch);
|
||||||
if (ch > 0x7F || ch == ' ') {
|
|
||||||
if ((ptr - enc + 2) >= *size) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
sprintf(ptr, "%%%02X", ch);
|
|
||||||
ptr += 2;
|
|
||||||
} else {
|
} else {
|
||||||
ptr[0] = (char) ch;
|
size++;
|
||||||
|
if (size < size_out) out[size - 1] = (char) ch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*size = ptr - enc;
|
|
||||||
return 0;
|
// 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) {
|
int url_decode(const char *str, char *dec, long *size) {
|
||||||
@ -103,43 +117,285 @@ int url_decode(const char *str, char *dec, long *size) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int mime_is_compressible(const char *type) {
|
int mime_is_compressible(const char *restrict type) {
|
||||||
if (type == NULL) return 0;
|
if (type == NULL) return 0;
|
||||||
char type_parsed[64];
|
char type_parsed[64];
|
||||||
snprintf(type_parsed, sizeof(type_parsed), "%s", type);
|
snprintf(type_parsed, sizeof(type_parsed), "%s", type);
|
||||||
char *pos = strchr(type_parsed, ';');
|
char *pos = strchr(type_parsed, ';');
|
||||||
if (pos != NULL) pos[0] = 0;
|
if (pos != NULL) pos[0] = 0;
|
||||||
return
|
return
|
||||||
strncmp(type_parsed, "text/", 5) == 0 ||
|
mime_is_text(type) ||
|
||||||
strncmp(type_parsed, "message/", 7) == 0 ||
|
streq(type_parsed, "application/vnd.ms-fontobject") ||
|
||||||
strstr(type_parsed, "+xml") != NULL ||
|
streq(type_parsed, "application/x-font-ttf") ||
|
||||||
strstr(type_parsed, "+json") != NULL ||
|
streq(type_parsed, "font/eot") ||
|
||||||
strcmp(type_parsed, "application/javascript") == 0 ||
|
streq(type_parsed, "font/opentype") ||
|
||||||
strcmp(type_parsed, "application/json") == 0 ||
|
streq(type_parsed, "image/bmp") ||
|
||||||
strcmp(type_parsed, "application/xml") == 0 ||
|
streq(type_parsed, "image/gif") ||
|
||||||
strcmp(type_parsed, "application/x-www-form-urlencoded") == 0 ||
|
streq(type_parsed, "image/vnd.microsoft.icon") ||
|
||||||
strcmp(type_parsed, "application/x-tex") == 0 ||
|
streq(type_parsed, "image/vnd.microsoft.iconbinary") ||
|
||||||
strcmp(type_parsed, "application/x-httpd-php") == 0 ||
|
streq(type_parsed, "image/x-icon");
|
||||||
strcmp(type_parsed, "application/x-latex") == 0 ||
|
|
||||||
strcmp(type_parsed, "application/vnd.ms-fontobject") == 0 ||
|
|
||||||
strcmp(type_parsed, "application/x-font-ttf") == 0 ||
|
|
||||||
strcmp(type_parsed, "application/x-javascript") == 0 ||
|
|
||||||
strcmp(type_parsed, "application/x-web-app-manifest+json") == 0 ||
|
|
||||||
strcmp(type_parsed, "font/eot") == 0 ||
|
|
||||||
strcmp(type_parsed, "font/opentype") == 0 ||
|
|
||||||
strcmp(type_parsed, "image/bmp") == 0 ||
|
|
||||||
strcmp(type_parsed, "image/gif") == 0 ||
|
|
||||||
strcmp(type_parsed, "image/vnd.microsoft.icon") == 0 ||
|
|
||||||
strcmp(type_parsed, "image/vnd.microsoft.iconbinary") == 0 ||
|
|
||||||
strcmp(type_parsed, "image/x-icon") == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int strcpy_rem_webroot(char *dst, const char *src, long len, const char *webroot) {
|
int mime_is_text(const char *restrict type) {
|
||||||
strncpy(dst, src, len);
|
if (type == NULL) return 0;
|
||||||
if (webroot == 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
|
||||||
|
strstarts(type_parsed, "text/") ||
|
||||||
|
strstarts(type_parsed, "message/") ||
|
||||||
|
strends(type_parsed, "+xml") ||
|
||||||
|
strends(type_parsed, "+json") ||
|
||||||
|
streq(type_parsed, "application/javascript") ||
|
||||||
|
streq(type_parsed, "application/json") ||
|
||||||
|
streq(type_parsed, "application/xml") ||
|
||||||
|
streq(type_parsed, "application/sql") ||
|
||||||
|
streq(type_parsed, "application/x-www-form-urlencoded") ||
|
||||||
|
streq(type_parsed, "application/x-tex") ||
|
||||||
|
streq(type_parsed, "application/x-httpd-php") ||
|
||||||
|
streq(type_parsed, "application/x-latex") ||
|
||||||
|
streq(type_parsed, "application/x-javascript");
|
||||||
|
}
|
||||||
|
|
||||||
|
int strcpy_rem_webroot(char *dst, const char *src, const char *webroot) {
|
||||||
|
strcpy(dst, src);
|
||||||
|
if (webroot == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
char *pos;
|
char *pos;
|
||||||
|
const unsigned long webroot_len = strlen(webroot);
|
||||||
|
if (webroot_len == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
while ((pos = strstr(dst, webroot)) != NULL) {
|
while ((pos = strstr(dst, webroot)) != NULL) {
|
||||||
strcpy(pos, pos + strlen(webroot));
|
strcpy(pos, pos + webroot_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
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 streq(const char *restrict str1, const char *restrict str2) {
|
||||||
|
return str1 != NULL && str2 != NULL && strcmp(str1, str2) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int strcontains(const char *restrict haystack, const char *restrict needle) {
|
||||||
|
return haystack != NULL && needle != NULL && strstr(haystack, needle) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int strstarts(const char *restrict str, const char *restrict prefix) {
|
||||||
|
if (str == NULL || prefix == NULL) return 0;
|
||||||
|
unsigned long l1 = strlen(str), l2 = strlen(prefix);
|
||||||
|
return l2 <= l1 && strncmp(str, prefix, l2) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int strends(const char *restrict str, const char *restrict suffix) {
|
||||||
|
if (str == NULL || suffix == NULL) return 0;
|
||||||
|
unsigned long l1 = strlen(str), l2 = strlen(suffix);
|
||||||
|
return l2 <= l1 && strcmp(str + l1 - l2, suffix) == 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
long clock_micros(void) {
|
||||||
|
struct timespec time;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &time);
|
||||||
|
return time.tv_sec * 1000000 + time.tv_nsec / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
long clock_cpu(void) {
|
||||||
|
struct timespec time;
|
||||||
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time);
|
||||||
|
return time.tv_sec * 1000000000 + time.tv_nsec;
|
||||||
|
}
|
||||||
|
|
||||||
|
long stat_mtime(const char *filename) {
|
||||||
|
struct stat stat_buf;
|
||||||
|
stat(filename, &stat_buf);
|
||||||
|
return stat_buf.st_mtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rm_rf(const char *path) {
|
||||||
|
struct stat stat_buf;
|
||||||
|
if (lstat(path, &stat_buf) != 0)
|
||||||
|
return (errno == ENOENT) ? 0 : -1;
|
||||||
|
|
||||||
|
if (S_ISREG(stat_buf.st_mode)) {
|
||||||
|
// regular file
|
||||||
|
return unlink(path);
|
||||||
|
} else if (S_ISLNK(stat_buf.st_mode)) {
|
||||||
|
// link
|
||||||
|
return unlink(path);
|
||||||
|
} else if (S_ISDIR(stat_buf.st_mode)) {
|
||||||
|
// directory
|
||||||
|
char buf[FILENAME_MAX];
|
||||||
|
DIR *dir;
|
||||||
|
|
||||||
|
// open directory
|
||||||
|
if ((dir = opendir(path)) == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// read directory
|
||||||
|
for (struct dirent *ent; (ent = readdir(dir)) != NULL;) {
|
||||||
|
if (streq(ent->d_name, ".") || streq(ent->d_name, ".."))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
snprintf(buf, sizeof(buf), "%s/%s", path, ent->d_name);
|
||||||
|
if (rm_rf(buf) != 0) {
|
||||||
|
closedir(dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close and remove directory
|
||||||
|
closedir(dir);
|
||||||
|
return rmdir(path);
|
||||||
|
} else {
|
||||||
|
// other - not supported
|
||||||
|
errno = ENOTSUP;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long fsize(FILE *file) {
|
||||||
|
long cur_pos, len;
|
||||||
|
if ((cur_pos = ftell(file)) == -1)
|
||||||
|
return -1;
|
||||||
|
if (fseek(file, 0, SEEK_END) != 0)
|
||||||
|
return -1;
|
||||||
|
if ((len = ftell(file)) == -1)
|
||||||
|
return -1;
|
||||||
|
if (fseek(file, cur_pos, SEEK_SET) != 0)
|
||||||
|
return -1;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
long flines(FILE *file) {
|
||||||
|
long cur_pos, lines = 0;
|
||||||
|
if ((cur_pos = ftell(file)) == -1)
|
||||||
|
return -1;
|
||||||
|
if (fseek(file, 0, SEEK_SET) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
for (int ch; (ch = fgetc(file)) != EOF;) {
|
||||||
|
if (ch == '\n') lines++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fseek(file, cur_pos, SEEK_SET) != 0)
|
||||||
|
return -1;
|
||||||
|
return lines + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long file_get_line_pos(FILE *file, long line_num) {
|
||||||
|
if (line_num < 1) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long cur_pos;
|
||||||
|
if ((cur_pos = ftell(file)) == -1)
|
||||||
|
return -1;
|
||||||
|
if (fseek(file, 0, SEEK_SET) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
long lines = 0, pos = 0;
|
||||||
|
for (int ch; lines < line_num - 1 && (ch = fgetc(file)) != EOF; pos++) {
|
||||||
|
if (ch == '\n') lines++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fseek(file, cur_pos, SEEK_SET) != 0)
|
||||||
|
return -1;
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fseekl(FILE *file, long line_num) {
|
||||||
|
if (line_num < 1) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fseek(file, 0, SEEK_SET) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
long lines = 0;
|
||||||
|
for (int ch; lines < line_num - 1 && (ch = fgetc(file)) != EOF;) {
|
||||||
|
if (ch == '\n') lines++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long ftelll(FILE *file) {
|
||||||
|
long cur_pos;
|
||||||
|
if ((cur_pos = ftell(file)) == -1)
|
||||||
|
return -1;
|
||||||
|
if (fseek(file, 0, SEEK_SET) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
long lines = 0, pos = 0;
|
||||||
|
for (int ch; pos < cur_pos && (ch = fgetc(file)) != EOF; pos++) {
|
||||||
|
if (ch == '\n') lines++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fseek(file, cur_pos, SEEK_SET) != 0)
|
||||||
|
return -1;
|
||||||
|
return lines + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
long parse_chunk_header(const char *buf, size_t len, size_t *ret_len) {
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
char ch = buf[i];
|
||||||
|
if (ch == '\r') {
|
||||||
|
continue;
|
||||||
|
} else if (ch == '\n' && i > 1 && buf[i - 1] == '\r') {
|
||||||
|
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'))) {
|
||||||
|
errno = EPROTO;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Necronda Web Server
|
* sesimos - secure, simple, modern web server
|
||||||
* Utilities (header file)
|
* @brief Utilities (header file)
|
||||||
* src/lib/utils.h
|
* @file src/lib/utils.h
|
||||||
* Lorenz Stechauner, 2020-12-03
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-03
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_UTILS_H
|
#ifndef SESIMOS_UTILS_H
|
||||||
#define NECRONDA_SERVER_UTILS_H
|
#define SESIMOS_UTILS_H
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@ -18,28 +19,52 @@
|
|||||||
#define HTTP_STR "\x1B[1;31m"
|
#define HTTP_STR "\x1B[1;31m"
|
||||||
#define HTTPS_STR "\x1B[1;32m"
|
#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);
|
char *format_duration(unsigned long micros, char *buf);
|
||||||
|
|
||||||
int url_encode_component(const char *str, char *enc, long *size);
|
int url_encode_component(const void *in, size_t size_in, char *out, size_t size_out);
|
||||||
|
|
||||||
int url_encode(const char *str, char *enc, long *size);
|
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 url_decode(const char *str, char *dec, long *size);
|
||||||
|
|
||||||
int mime_is_compressible(const char *type);
|
int mime_is_compressible(const char *restrict type);
|
||||||
|
|
||||||
int strcpy_rem_webroot(char *dst, const char *str, long len, const char *webroot);
|
int mime_is_text(const char *restrict type);
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_UTILS_H
|
int strcpy_rem_webroot(char *dst, const char *str, const char *webroot);
|
||||||
|
|
||||||
|
int str_trim(char **start, char **end);
|
||||||
|
|
||||||
|
int str_trim_lws(char **start, char **end);
|
||||||
|
|
||||||
|
int streq(const char *restrict str1, const char *restrict str2);
|
||||||
|
|
||||||
|
int strcontains(const char *restrict haystack, const char *restrict needle);
|
||||||
|
|
||||||
|
int strstarts(const char *restrict str, const char *restrict prefix);
|
||||||
|
|
||||||
|
int strends(const char *restrict str, const char *restrict suffix);
|
||||||
|
|
||||||
|
int base64_encode(void *data, unsigned long data_len, char *output, unsigned long *output_len);
|
||||||
|
|
||||||
|
long clock_micros(void);
|
||||||
|
|
||||||
|
long clock_cpu(void);
|
||||||
|
|
||||||
|
long stat_mtime(const char *filename);
|
||||||
|
|
||||||
|
int rm_rf(const char *path);
|
||||||
|
|
||||||
|
long fsize(FILE *file);
|
||||||
|
|
||||||
|
long flines(FILE *file);
|
||||||
|
|
||||||
|
long file_get_line_pos(FILE *file, long line_num);
|
||||||
|
|
||||||
|
int fseekl(FILE *file, long line_num);
|
||||||
|
|
||||||
|
long ftelll(FILE *file);
|
||||||
|
|
||||||
|
long parse_chunk_header(const char *buf, size_t len, size_t *ret_len);
|
||||||
|
|
||||||
|
#endif //SESIMOS_UTILS_H
|
||||||
|
130
src/lib/websocket.c
Normal file
130
src/lib/websocket.c
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief WebSocket reverse proxy
|
||||||
|
* @file src/lib/websocket.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-08-16
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "websocket.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
|
|
||||||
|
static const char ws_key_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
if (sock_recv_x(s, buf, 2, 0) == -1) {
|
||||||
|
error("Unable to receive from socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sock_recv_x(s, buf, remaining, 0) == -1) {
|
||||||
|
error("Unable to receive from socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_x(s, buf, ptr - buf, frame->len != 0 ? MSG_MORE : 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
error("Unable to send to socket");
|
||||||
|
return -1;
|
||||||
|
} else if (ret != ptr - buf) {
|
||||||
|
error("Unable to send to socket");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
33
src/lib/websocket.h
Normal file
33
src/lib/websocket.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
#endif //SESIMOS_WEBSOCKET_H
|
305
src/logger.c
Normal file
305
src/logger.c
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Logger
|
||||||
|
* @file src/logger.h
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-10
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "lib/utils.h"
|
||||||
|
#include "lib/error.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#define LOG_MAX_MSG_SIZE 2048
|
||||||
|
#define LOG_BUF_SIZE 256
|
||||||
|
#define LOG_NAME_LEN 12
|
||||||
|
#define LOG_PREFIX_LEN 256
|
||||||
|
|
||||||
|
#define LOG_PREFIX "[%8s][%-8s][%-6s]"
|
||||||
|
#define LOG_TIME_BUF_SIZE 9
|
||||||
|
#define LOG_TIME_FMT "%H:%M:%S"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
log_lvl_t lvl;
|
||||||
|
long time;
|
||||||
|
char name[LOG_NAME_LEN];
|
||||||
|
char prefix[LOG_PREFIX_LEN];
|
||||||
|
char txt[LOG_MAX_MSG_SIZE];
|
||||||
|
} log_msg_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int rd;
|
||||||
|
int wr;
|
||||||
|
log_msg_t msgs[LOG_BUF_SIZE];
|
||||||
|
} buf_t;
|
||||||
|
|
||||||
|
static pthread_t thread;
|
||||||
|
static volatile sig_atomic_t alive = 0;
|
||||||
|
static sem_t sem_buf, sem_buf_free, sem_buf_used;
|
||||||
|
static buf_t buffer;
|
||||||
|
|
||||||
|
static pthread_key_t key_name = -1, key_prefix = -1;
|
||||||
|
static char global_name[LOG_NAME_LEN] = "", global_prefix[LOG_PREFIX_LEN] = "";
|
||||||
|
|
||||||
|
static const char *level_keywords[] = {
|
||||||
|
"EMERG",
|
||||||
|
"ALERT",
|
||||||
|
"CRIT",
|
||||||
|
"ERROR",
|
||||||
|
"WARN",
|
||||||
|
"NOTICE",
|
||||||
|
"INFO",
|
||||||
|
"DEBUG"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *timestr(time_t ts, char *buf) {
|
||||||
|
struct tm time_info;
|
||||||
|
strftime(buf, LOG_TIME_BUF_SIZE, LOG_TIME_FMT, localtime_r(&ts, &time_info));
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void err(const char *restrict msg) {
|
||||||
|
char err_buf[64], time_buf[LOG_TIME_BUF_SIZE];
|
||||||
|
fprintf(stderr, ERR_STR LOG_PREFIX " %s: %s" CLR_STR "\n", timestr(time(NULL), time_buf), "logger",
|
||||||
|
level_keywords[LOG_CRITICAL], msg, error_str(errno, err_buf, sizeof(err_buf)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void logmsgf(log_lvl_t level, const char *restrict format, ...) {
|
||||||
|
char buf[256], err_buf[256], time_buf[LOG_TIME_BUF_SIZE];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
|
||||||
|
const char *color = (level <= LOG_ERROR) ? ERR_STR : ((level <= LOG_WARNING) ? WRN_STR : "");
|
||||||
|
if (errno != 0) {
|
||||||
|
snprintf(buf, sizeof(buf), "%s%s: %s" CLR_STR, color, format, error_str(errno, err_buf, sizeof(err_buf)));
|
||||||
|
} else {
|
||||||
|
snprintf(buf, sizeof(buf), "%s%s" CLR_STR, color, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *name = pthread_getspecific(key_name);
|
||||||
|
if (name == NULL && global_name[0] != 0) name = global_name;
|
||||||
|
void *prefix = pthread_getspecific(key_prefix);
|
||||||
|
if (prefix == NULL && global_prefix[0] != 0) prefix = global_prefix;
|
||||||
|
|
||||||
|
if (!alive) {
|
||||||
|
// no logger thread running
|
||||||
|
// simply write to stdout without synchronization
|
||||||
|
printf("%s" LOG_PREFIX "%s%s ", color,
|
||||||
|
timestr(time(NULL), time_buf),
|
||||||
|
(name != NULL) ? (char *) name : "",
|
||||||
|
level_keywords[level], CLR_STR,
|
||||||
|
(prefix != NULL) ? (char *) prefix : "");
|
||||||
|
vprintf(buf, args);
|
||||||
|
printf("\n");
|
||||||
|
} else {
|
||||||
|
// wait for free slot in buffer
|
||||||
|
while (sem_wait(&sem_buf_free) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
err("Unable to lock semaphore");
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
// cleanup
|
||||||
|
va_end(args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to lock buffer
|
||||||
|
while (sem_wait(&sem_buf) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
err("Unable to lock semaphore");
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
// cleanup
|
||||||
|
sem_post(&sem_buf_free);
|
||||||
|
va_end(args);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write message to buffer
|
||||||
|
log_msg_t *msg = &buffer.msgs[buffer.rd];
|
||||||
|
buffer.rd = (buffer.rd + 1) % LOG_BUF_SIZE;
|
||||||
|
|
||||||
|
vsnprintf(msg->txt, sizeof(msg->txt), buf, args);
|
||||||
|
msg->lvl = level;
|
||||||
|
msg->time = time(NULL);
|
||||||
|
|
||||||
|
if (name != NULL) {
|
||||||
|
snprintf(msg->name, sizeof(msg->name), "%s", (char *) name);
|
||||||
|
} else {
|
||||||
|
msg->name[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix != NULL) {
|
||||||
|
snprintf(msg->prefix, sizeof(msg->prefix), "%s", (char *) prefix);
|
||||||
|
} else {
|
||||||
|
msg->prefix[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlock buffer
|
||||||
|
sem_post(&sem_buf);
|
||||||
|
|
||||||
|
// unlock slot in buffer for logger
|
||||||
|
sem_post(&sem_buf_used);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logger_destroy(void) {
|
||||||
|
sem_destroy(&sem_buf);
|
||||||
|
sem_destroy(&sem_buf_free);
|
||||||
|
sem_destroy(&sem_buf_used);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int logger_remaining(void) {
|
||||||
|
int val = 0;
|
||||||
|
sem_getvalue(&sem_buf_used, &val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_set_name(const char *restrict format, ...) {
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
void *ptr;
|
||||||
|
if (key_name == -1) {
|
||||||
|
// not initialized
|
||||||
|
va_start(args, format);
|
||||||
|
vsnprintf(global_name, sizeof(global_name), format, args);
|
||||||
|
ptr = global_name;
|
||||||
|
} else {
|
||||||
|
int ret;
|
||||||
|
if ((ptr = pthread_getspecific(key_name)) == NULL) {
|
||||||
|
ptr = malloc(LOG_NAME_LEN);
|
||||||
|
if ((ret = pthread_setspecific(key_name, ptr)) != 0) {
|
||||||
|
errno = ret;
|
||||||
|
err("Unable to set thread specific values");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
va_start(args, format);
|
||||||
|
vsnprintf(ptr, LOG_NAME_LEN, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set thread name
|
||||||
|
// warning: max length is 16 (incl. terminating null byte)
|
||||||
|
pthread_setname_np(pthread_self(), ptr);
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_set_prefix(const char *restrict format, ...) {
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
if (key_prefix == -1) {
|
||||||
|
// not initialized
|
||||||
|
va_start(args, format);
|
||||||
|
vsnprintf(global_prefix, sizeof(global_prefix), format, args);
|
||||||
|
} else {
|
||||||
|
int ret;
|
||||||
|
void *ptr = pthread_getspecific(key_prefix);
|
||||||
|
if (!ptr) {
|
||||||
|
ptr = malloc(LOG_PREFIX_LEN);
|
||||||
|
if ((ret = pthread_setspecific(key_prefix, ptr)) != 0) {
|
||||||
|
errno = ret;
|
||||||
|
err("Unable to set thread specific values");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
va_start(args, format);
|
||||||
|
vsnprintf(ptr, LOG_PREFIX_LEN, format, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *logger_thread(void *arg) {
|
||||||
|
char time_buf[LOG_TIME_BUF_SIZE];
|
||||||
|
|
||||||
|
logger_set_name("logger");
|
||||||
|
alive = 1;
|
||||||
|
|
||||||
|
while (alive || logger_remaining() > 0) {
|
||||||
|
// wait for buffer to be filled
|
||||||
|
if (sem_wait(&sem_buf_used) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
err("Unable to lock semaphore");
|
||||||
|
errno = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_msg_t *msg = &buffer.msgs[buffer.wr];
|
||||||
|
buffer.wr = (buffer.wr + 1) % LOG_BUF_SIZE;
|
||||||
|
|
||||||
|
printf("%s" LOG_PREFIX "%s%s %s\n",
|
||||||
|
(msg->lvl <= LOG_ERROR) ? ERR_STR : ((msg->lvl <= LOG_WARNING) ? WRN_STR : ""),
|
||||||
|
(timestr(msg->time, time_buf)),
|
||||||
|
(msg->name[0] != 0) ? (char *) msg->name : "", level_keywords[msg->lvl], CLR_STR,
|
||||||
|
(msg->prefix[0] != 0) ? (char *) msg->prefix : "", msg->txt);
|
||||||
|
|
||||||
|
// unlock slot in buffer
|
||||||
|
sem_post(&sem_buf_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_destroy();
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int logger_init(void) {
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
// try to initialize all three semaphores
|
||||||
|
if (sem_init(&sem_buf, 0, 1) != 0 || sem_init(&sem_buf_free, 0, LOG_BUF_SIZE) != 0 || sem_init(&sem_buf_used, 0, 0) != 0) {
|
||||||
|
err("Unable to initialize semaphore");
|
||||||
|
logger_destroy();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize read/write heads
|
||||||
|
buffer.rd = 0;
|
||||||
|
buffer.wr = 0;
|
||||||
|
|
||||||
|
// initialize thread specific values (keys)
|
||||||
|
if ((ret = pthread_key_create(&key_name, free)) != 0 || (ret = pthread_key_create(&key_prefix, free)) != 0) {
|
||||||
|
errno = ret;
|
||||||
|
err("Unable to initialize thread specific values");
|
||||||
|
logger_destroy();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_create(&thread, NULL, logger_thread, NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_stop(void) {
|
||||||
|
alive = 0;
|
||||||
|
pthread_kill(thread, SIGUSR1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int logger_join(void) {
|
||||||
|
return pthread_join(thread, NULL);
|
||||||
|
}
|
42
src/logger.h
Normal file
42
src/logger.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Logger (header file)
|
||||||
|
* @file src/logger.h
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-10
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SESIMOS_LOGGER_H
|
||||||
|
#define SESIMOS_LOGGER_H
|
||||||
|
|
||||||
|
#define LOG_DEBUG 7
|
||||||
|
#define LOG_INFO 6
|
||||||
|
#define LOG_NOTICE 5
|
||||||
|
#define LOG_WARNING 4
|
||||||
|
#define LOG_ERROR 3
|
||||||
|
#define LOG_CRITICAL 2
|
||||||
|
#define LOG_ALERT 1
|
||||||
|
|
||||||
|
typedef unsigned char log_lvl_t;
|
||||||
|
|
||||||
|
#define debug(...) logmsgf(LOG_DEBUG, __VA_ARGS__)
|
||||||
|
#define info(...) logmsgf(LOG_INFO, __VA_ARGS__)
|
||||||
|
#define notice(...) logmsgf(LOG_NOTICE, __VA_ARGS__)
|
||||||
|
#define warning(...) logmsgf(LOG_WARNING, __VA_ARGS__)
|
||||||
|
#define error(...) logmsgf(LOG_ERROR, __VA_ARGS__)
|
||||||
|
#define critical(...) logmsgf(LOG_CRITICAL, __VA_ARGS__)
|
||||||
|
#define alert(...) logmsgf(LOG_ALERT, __VA_ARGS__)
|
||||||
|
|
||||||
|
void logmsgf(log_lvl_t level, const char *restrict format, ...);
|
||||||
|
|
||||||
|
void logger_set_name(const char *restrict format, ...);
|
||||||
|
|
||||||
|
void logger_set_prefix(const char *restrict format, ...);
|
||||||
|
|
||||||
|
int logger_init(void);
|
||||||
|
|
||||||
|
void logger_stop(void);
|
||||||
|
|
||||||
|
int logger_join(void);
|
||||||
|
|
||||||
|
#endif //SESIMOS_LOGGER_H
|
@ -1,419 +0,0 @@
|
|||||||
/**
|
|
||||||
* Necronda Web Server
|
|
||||||
* Main executable
|
|
||||||
* src/necronda-server.c
|
|
||||||
* Lorenz Stechauner, 2020-12-03
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define _POSIX_C_SOURCE 199309L
|
|
||||||
|
|
||||||
#include "necronda.h"
|
|
||||||
#include "necronda-server.h"
|
|
||||||
#include "client.c"
|
|
||||||
|
|
||||||
#include "lib/cache.h"
|
|
||||||
#include "lib/config.h"
|
|
||||||
#include "lib/sock.h"
|
|
||||||
#include "lib/rev_proxy.h"
|
|
||||||
#include "lib/geoip.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <wait.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/ssl.h>
|
|
||||||
#include <openssl/conf.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
|
|
||||||
int active = 1;
|
|
||||||
const char *config_file;
|
|
||||||
int sockets[NUM_SOCKETS];
|
|
||||||
pid_t children[MAX_CHILDREN];
|
|
||||||
MMDB_s mmdbs[MAX_MMDB];
|
|
||||||
SSL_CTX *contexts[CONFIG_MAX_CERT_CONFIG];
|
|
||||||
|
|
||||||
void openssl_init() {
|
|
||||||
SSL_library_init();
|
|
||||||
SSL_load_error_strings();
|
|
||||||
ERR_load_BIO_strings();
|
|
||||||
OpenSSL_add_all_algorithms();
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ssl_servername_cb(SSL *ssl, int *ad, void *arg) {
|
|
||||||
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
|
||||||
if (servername != NULL) {
|
|
||||||
const host_config *conf = get_host_config(servername);
|
|
||||||
if (conf != NULL) SSL_set_SSL_CTX(ssl, contexts[conf->cert]);
|
|
||||||
}
|
|
||||||
return SSL_TLSEXT_ERR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy() {
|
|
||||||
fprintf(stderr, "\n" ERR_STR "Terminating forcefully!" CLR_STR "\n");
|
|
||||||
int status = 0;
|
|
||||||
int ret;
|
|
||||||
int kills = 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));
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
kill(children[i], SIGKILL);
|
|
||||||
kills++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (kills > 0) {
|
|
||||||
fprintf(stderr, ERR_STR "Killed %i child process(es)" CLR_STR "\n", kills);
|
|
||||||
}
|
|
||||||
cache_unload();
|
|
||||||
config_unload();
|
|
||||||
exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void terminate() {
|
|
||||||
fprintf(stderr, "\nTerminating gracefully...\n");
|
|
||||||
active = 0;
|
|
||||||
|
|
||||||
signal(SIGINT, destroy);
|
|
||||||
signal(SIGTERM, destroy);
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
|
||||||
shutdown(sockets[i], SHUT_RDWR);
|
|
||||||
close(sockets[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
int status = 0;
|
|
||||||
int wait_num = 0;
|
|
||||||
int ret;
|
|
||||||
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));
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
kill(children[i], SIGTERM);
|
|
||||||
wait_num++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wait_num > 0) {
|
|
||||||
fprintf(stderr, "Waiting for %i child process(es)...\n", wait_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < MAX_CHILDREN; i++) {
|
|
||||||
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));
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wait_num > 0) {
|
|
||||||
// Wait another 50 ms to let child processes write to stdout/stderr
|
|
||||||
signal(SIGINT, SIG_IGN);
|
|
||||||
signal(SIGTERM, SIG_IGN);
|
|
||||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 50000000};
|
|
||||||
nanosleep(&ts, &ts);
|
|
||||||
fprintf(stderr, "\nGoodbye\n");
|
|
||||||
} else {
|
|
||||||
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;
|
|
||||||
int ready_sockets_num;
|
|
||||||
long client_num = 0;
|
|
||||||
char buf[1024];
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
int client_fd;
|
|
||||||
sock client;
|
|
||||||
struct sockaddr_in6 client_addr;
|
|
||||||
unsigned int client_addr_len = sizeof(client_addr);
|
|
||||||
|
|
||||||
memset(sockets, 0, sizeof(sockets));
|
|
||||||
memset(children, 0, sizeof(children));
|
|
||||||
memset(mmdbs, 0, sizeof(mmdbs));
|
|
||||||
|
|
||||||
struct timeval timeout;
|
|
||||||
|
|
||||||
const struct sockaddr_in6 addresses[2] = {
|
|
||||||
{.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, _IOLBF, 0) != 0) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to set stdout to line-buffered mode: %s" CLR_STR, strerror(errno));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
printf("Necronda Web Server " NECRONDA_VERSION "\n");
|
|
||||||
|
|
||||||
ret = config_init();
|
|
||||||
if (ret != 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
config_file = NULL;
|
|
||||||
for (int i = 1; i < argc; i++) {
|
|
||||||
const char *arg = argv[i];
|
|
||||||
if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
|
|
||||||
printf("Usage: necronda-server [-h] [-c <CONFIG-FILE>]\n"
|
|
||||||
"\n"
|
|
||||||
"Options:\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, "-c") == 0 || strcmp(arg, "--config") == 0) {
|
|
||||||
if (i == argc - 1) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --config <CONFIG-FILE>" CLR_STR "\n", arg);
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
config_file = argv[++i];
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to parse argument '%s'" CLR_STR "\n", arg);
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = config_load(config_file == NULL ? DEFAULT_CONFIG_FILE : config_file);
|
|
||||||
if (ret != 0) {
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sockets[0] = socket(AF_INET6, SOCK_STREAM, 0);
|
|
||||||
if (sockets[0] < 0) goto socket_err;
|
|
||||||
sockets[1] = socket(AF_INET6, SOCK_STREAM, 0);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bind(sockets[0], (struct sockaddr *) &addresses[0], sizeof(addresses[0])) < 0) goto bind_err;
|
|
||||||
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[0] != 0) {
|
|
||||||
DIR *geoip = opendir(geoip_dir);
|
|
||||||
if (geoip == NULL) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to open GeoIP dir: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
struct dirent *dir;
|
|
||||||
int i = 0;
|
|
||||||
while ((dir = readdir(geoip)) != NULL) {
|
|
||||||
if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".mmdb") != 0) continue;
|
|
||||||
if (i >= MAX_MMDB) {
|
|
||||||
fprintf(stderr, ERR_STR "Too many .mmdb files" CLR_STR "\n");
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
sprintf(buf, "%s/%s", geoip_dir, dir->d_name);
|
|
||||||
ret = MMDB_open(buf, 0, &mmdbs[i]);
|
|
||||||
if (ret != MMDB_SUCCESS) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to open .mmdb file: %s" CLR_STR "\n", MMDB_strerror(ret));
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
if (i == 0) {
|
|
||||||
fprintf(stderr, ERR_STR "No .mmdb files found in %s" CLR_STR "\n", geoip_dir);
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
closedir(geoip);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) {
|
|
||||||
const cert_config *conf = &config->certs[i];
|
|
||||||
if (conf->name[0] == 0) break;
|
|
||||||
|
|
||||||
contexts[i] = SSL_CTX_new(TLS_server_method());
|
|
||||||
SSL_CTX *ctx = contexts[i];
|
|
||||||
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
|
|
||||||
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
|
|
||||||
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
|
||||||
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
|
||||||
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
|
|
||||||
SSL_CTX_set_ecdh_auto(ctx, 1);
|
|
||||||
SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
|
|
||||||
|
|
||||||
if (SSL_CTX_use_certificate_chain_file(ctx, conf->full_chain) != 1) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n",
|
|
||||||
ERR_reason_error_string(ERR_get_error()), conf->full_chain);
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (SSL_CTX_use_PrivateKey_file(ctx, conf->priv_key, SSL_FILETYPE_PEM) != 1) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to load private key file: %s: %s" CLR_STR "\n",
|
|
||||||
ERR_reason_error_string(ERR_get_error()), conf->priv_key);
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.ctx = contexts[0];
|
|
||||||
|
|
||||||
|
|
||||||
rev_proxy_preload();
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
|
||||||
if (listen(sockets[i], LISTEN_BACKLOG) < 0) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to listen on socket %i: %s" CLR_STR "\n", i, strerror(errno));
|
|
||||||
config_unload();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FD_ZERO(&socket_fds);
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
if (ready_sockets_num < 0) {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to select sockets: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
terminate();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_SOCKETS; i++) {
|
|
||||||
if (FD_ISSET(sockets[i], &read_socket_fds)) {
|
|
||||||
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));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (pid == 0) {
|
|
||||||
// child
|
|
||||||
signal(SIGINT, SIG_IGN);
|
|
||||||
signal(SIGTERM, SIG_IGN);
|
|
||||||
|
|
||||||
client.socket = client_fd;
|
|
||||||
client.enc = i == 1;
|
|
||||||
return client_handler(&client, client_num, &client_addr);
|
|
||||||
} else if (pid > 0) {
|
|
||||||
// parent
|
|
||||||
client_num++;
|
|
||||||
close(client_fd);
|
|
||||||
for (int j = 0; j < MAX_CHILDREN; j++) {
|
|
||||||
if (children[j] == 0) {
|
|
||||||
children[j] = pid;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
* Necronda Web Server
|
|
||||||
* Main executable (header file)
|
|
||||||
* src/necronda-server.h
|
|
||||||
* Lorenz Stechauner, 2020-12-03
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_NECRONDA_SERVER_H
|
|
||||||
#define NECRONDA_SERVER_NECRONDA_SERVER_H
|
|
||||||
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <maxminddb.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
|
|
||||||
|
|
||||||
#define CHUNK_SIZE 8192
|
|
||||||
|
|
||||||
extern int sockets[NUM_SOCKETS];
|
|
||||||
extern pid_t children[MAX_CHILDREN];
|
|
||||||
extern MMDB_s mmdbs[MAX_MMDB];
|
|
||||||
|
|
||||||
extern int server_keep_alive;
|
|
||||||
extern char *log_client_prefix, *log_conn_prefix, *log_req_prefix, *client_geoip;
|
|
||||||
extern char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr, *client_host_str;
|
|
||||||
extern struct timeval client_timeout;
|
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_NECRONDA_SERVER_H
|
|
@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* Necronda Web Server
|
|
||||||
* Definitions
|
|
||||||
* src/necronda.h
|
|
||||||
* Lorenz Stechauner, 2021-05-04
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef NECRONDA_SERVER_NECRONDA_H
|
|
||||||
#define NECRONDA_SERVER_NECRONDA_H
|
|
||||||
|
|
||||||
#define NECRONDA_VERSION "4.5"
|
|
||||||
#define SERVER_STR "Necronda/" NECRONDA_VERSION
|
|
||||||
#define SERVER_STR_HTML "Necronda web server " NECRONDA_VERSION
|
|
||||||
|
|
||||||
#ifndef DEFAULT_HOST
|
|
||||||
# define DEFAULT_HOST "www.necronda.net"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef SERVER_NAME
|
|
||||||
# define SERVER_NAME DEFAULT_HOST
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif //NECRONDA_SERVER_NECRONDA_H
|
|
439
src/server.c
Normal file
439
src/server.c
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
/**
|
||||||
|
* Sesimos - secure, simple, modern web server
|
||||||
|
* @brief Main executable
|
||||||
|
* @file src/server.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2020-12-03
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "defs.h"
|
||||||
|
#include "server.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "async.h"
|
||||||
|
|
||||||
|
#include "cache_handler.h"
|
||||||
|
#include "lib/config.h"
|
||||||
|
#include "lib/proxy.h"
|
||||||
|
#include "lib/geoip.h"
|
||||||
|
#include "workers.h"
|
||||||
|
#include "worker/func.h"
|
||||||
|
#include "lib/list.h"
|
||||||
|
#include "lib/utils.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/conf.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
|
||||||
|
const char *config_file;
|
||||||
|
static int sockets[NUM_SOCKETS];
|
||||||
|
static SSL_CTX *contexts[CONFIG_MAX_CERT_CONFIG];
|
||||||
|
static client_ctx_t **clients;
|
||||||
|
static sem_t sem_clients_lock;
|
||||||
|
|
||||||
|
static void clean(void) {
|
||||||
|
notice("Cleaning sesimos cache and metadata files...");
|
||||||
|
|
||||||
|
// remove legacy files
|
||||||
|
// /.../server/, /.../server/cache
|
||||||
|
if (rm_rf("/var/sesimos/server") != 0) {
|
||||||
|
error("Unable to remove /var/sesimos/server/");
|
||||||
|
} else if (!errno) {
|
||||||
|
notice("Successfully removed /var/sesimos/server/");
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
|
// remove cache and metadata files
|
||||||
|
char buf[512];
|
||||||
|
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
|
||||||
|
host_config_t *hc = &config.hosts[i];
|
||||||
|
if (hc->type == CONFIG_TYPE_UNSET) break;
|
||||||
|
if (hc->type != CONFIG_TYPE_LOCAL) continue;
|
||||||
|
|
||||||
|
snprintf(buf, sizeof(buf), "%s/.sesimos", hc->local.webroot);
|
||||||
|
if (rm_rf(buf) != 0) {
|
||||||
|
error("Unable to remove %s/", buf);
|
||||||
|
} else if (!errno) {
|
||||||
|
notice("Successfully removed %s/", buf);
|
||||||
|
}
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
notice("Cleaned all sesimos cache and metadata files!");
|
||||||
|
}
|
||||||
|
|
||||||
|
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_t *conf = get_host_config(servername);
|
||||||
|
if (conf != NULL) SSL_set_SSL_CTX(ssl, contexts[conf->cert]);
|
||||||
|
}
|
||||||
|
return SSL_TLSEXT_ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void server_free_client(client_ctx_t *ctx) {
|
||||||
|
// try to lock clients list
|
||||||
|
while (sem_wait(&sem_clients_lock) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
critical("Unable to lock clients list");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete from list
|
||||||
|
clients = list_delete(clients, &ctx);
|
||||||
|
if (clients == NULL) {
|
||||||
|
critical("Unable to delete context from list");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlock clients list
|
||||||
|
sem_post(&sem_clients_lock);
|
||||||
|
|
||||||
|
// free data
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ssl_free() {
|
||||||
|
for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) {
|
||||||
|
const cert_config_t *conf = &config.certs[i];
|
||||||
|
if (conf->name[0] == 0) break;
|
||||||
|
SSL_CTX_free(contexts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void accept_cb(void *arg) {
|
||||||
|
int i = (int) (((int *) arg) - sockets);
|
||||||
|
int fd = sockets[i];
|
||||||
|
|
||||||
|
client_ctx_t *client_ctx = malloc(sizeof(client_ctx_t));
|
||||||
|
if (client_ctx == NULL) {
|
||||||
|
critical("Unable to allocate memory for client context");
|
||||||
|
errno = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sock *client = &client_ctx->socket;
|
||||||
|
|
||||||
|
client->ctx = contexts[0];
|
||||||
|
socklen_t addr_len = sizeof(client->_addr);
|
||||||
|
int client_fd = accept(fd, &client->_addr.sock, &addr_len);
|
||||||
|
if (client_fd < 0) {
|
||||||
|
critical("Unable to accept connection");
|
||||||
|
free(client_ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sock_init(client, client_fd, (i == 1) ? SOCK_ENCRYPTED : 0);
|
||||||
|
client_ctx->cnx_s = client->ts_start;
|
||||||
|
client_ctx->cnx_e = -1, client_ctx->req_s = -1, client_ctx->req_e = -1, client_ctx->res_ts = -1;
|
||||||
|
|
||||||
|
// try to lock clients list
|
||||||
|
while (sem_wait(&sem_clients_lock) != 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
errno = 0;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
critical("Unable to lock clients list");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// append to list
|
||||||
|
clients = list_append(clients, &client_ctx);
|
||||||
|
if (clients == NULL) {
|
||||||
|
critical("Unable to add client context to list");
|
||||||
|
free(client_ctx);
|
||||||
|
errno = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlock clients list
|
||||||
|
sem_post(&sem_clients_lock);
|
||||||
|
|
||||||
|
tcp_accept(client_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void accept_err_cb(void *arg) {
|
||||||
|
int i = (int) (((int *) arg) - sockets);
|
||||||
|
int fd = sockets[i];
|
||||||
|
// TODO accept error callback
|
||||||
|
}
|
||||||
|
|
||||||
|
static void terminate_forcefully(int sig) {
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
notice("Terminating forcefully!");
|
||||||
|
|
||||||
|
geoip_free();
|
||||||
|
|
||||||
|
notice("Goodbye");
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void terminate_gracefully(int sig) {
|
||||||
|
if (sig != 0) fprintf(stderr, "\n");
|
||||||
|
notice("Terminating gracefully...");
|
||||||
|
|
||||||
|
struct sigaction act = {0};
|
||||||
|
act.sa_handler = terminate_forcefully;
|
||||||
|
sigaction(SIGINT, &act, NULL);
|
||||||
|
sigaction(SIGTERM, &act, NULL);
|
||||||
|
|
||||||
|
debug("Closing listening sockets...");
|
||||||
|
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||||
|
close(sockets[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Stopping workers...");
|
||||||
|
cache_stop();
|
||||||
|
workers_stop();
|
||||||
|
debug("Destroying workers...");
|
||||||
|
workers_destroy();
|
||||||
|
|
||||||
|
logger_set_prefix("");
|
||||||
|
debug("Closing proxy connections...");
|
||||||
|
proxy_close_all();
|
||||||
|
|
||||||
|
debug("Closing client connections...");
|
||||||
|
while (list_size(clients) > 0)
|
||||||
|
tcp_close(clients[0]);
|
||||||
|
logger_set_prefix("");
|
||||||
|
|
||||||
|
debug("Stopping async loop...");
|
||||||
|
async_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nothing(int sig) {}
|
||||||
|
|
||||||
|
int main(int argc, char *const argv[]) {
|
||||||
|
const int YES = 1;
|
||||||
|
int ret;
|
||||||
|
int mode = 0;
|
||||||
|
|
||||||
|
memset(sockets, 0, sizeof(sockets));
|
||||||
|
|
||||||
|
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)}
|
||||||
|
};
|
||||||
|
|
||||||
|
logger_set_name("server");
|
||||||
|
|
||||||
|
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0 || setvbuf(stderr, NULL, _IOLBF, 0) != 0) {
|
||||||
|
critical("Unable to set stdout/stderr to line-buffered mode");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("sesimos web server " SERVER_VERSION "\n");
|
||||||
|
|
||||||
|
static const struct option long_opts[] = {
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{"clean", no_argument, 0, 'C'},
|
||||||
|
{"config", required_argument, 0, 'c'},
|
||||||
|
{ 0, 0, 0, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
config_file = NULL;
|
||||||
|
for (int c, opt_idx; (c = getopt_long(argc, argv, "hCc:", long_opts, &opt_idx)) != -1;) {
|
||||||
|
switch (c) {
|
||||||
|
case 'h':
|
||||||
|
fprintf(stderr,
|
||||||
|
"Usage: sesimos [-h] [-c <CONFIG FILE>]\n"
|
||||||
|
"\n"
|
||||||
|
"Options:\n"
|
||||||
|
" -c, --config <CONFIG-FILE> path to the config file. If not provided, default will be used\n"
|
||||||
|
" -C, --clean clear cached files and other metadata\n"
|
||||||
|
" -h, --help print this dialogue\n");
|
||||||
|
return 0;
|
||||||
|
case 'c':
|
||||||
|
config_file = optarg;
|
||||||
|
break;
|
||||||
|
case 'C':
|
||||||
|
mode = 1;
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
default:
|
||||||
|
critical("Unable to parse arguments");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (optind != argc) {
|
||||||
|
critical("No positional arguments expected");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_load(config_file == NULL ? DEFAULT_CONFIG_FILE : config_file) != 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (mode == 1) {
|
||||||
|
clean();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((sockets[0] = socket(AF_INET6, SOCK_STREAM, 0)) == -1 ||
|
||||||
|
(sockets[1] = socket(AF_INET6, SOCK_STREAM, 0)) == -1)
|
||||||
|
{
|
||||||
|
critical("Unable to create socket");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||||
|
if (setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(YES)) < 0) {
|
||||||
|
critical("Unable to set options for socket %i", i);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind(sockets[0], (struct sockaddr *) &addresses[0], sizeof(addresses[0])) == -1 ||
|
||||||
|
bind(sockets[1], (struct sockaddr *) &addresses[1], sizeof(addresses[1])) == -1)
|
||||||
|
{
|
||||||
|
critical("Unable to bind socket to address");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigaction act = {0};
|
||||||
|
act.sa_handler = terminate_gracefully;
|
||||||
|
sigaction(SIGINT, &act, NULL);
|
||||||
|
sigaction(SIGTERM, &act, NULL);
|
||||||
|
act.sa_handler = nothing;
|
||||||
|
sigaction(SIGUSR1, &act, NULL);
|
||||||
|
sigaction(SIGPIPE, &act, NULL);
|
||||||
|
|
||||||
|
if ((ret = geoip_init(config.geoip_dir)) != 0) {
|
||||||
|
if (ret == -1) {
|
||||||
|
critical("Unable to initialize geoip");
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) {
|
||||||
|
const cert_config_t *conf = &config.certs[i];
|
||||||
|
if (conf->name[0] == 0) break;
|
||||||
|
|
||||||
|
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_MODE_AUTO_RETRY);
|
||||||
|
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
|
||||||
|
SSL_CTX_set_ecdh_auto(ctx, 1);
|
||||||
|
SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
|
||||||
|
|
||||||
|
if (SSL_CTX_use_certificate_chain_file(ctx, conf->full_chain) != 1) {
|
||||||
|
critical("Unable to load certificate chain file: %s: %s", ERR_reason_error_string(ERR_get_error()), conf->full_chain);
|
||||||
|
geoip_free();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (SSL_CTX_use_PrivateKey_file(ctx, conf->priv_key, SSL_FILETYPE_PEM) != 1) {
|
||||||
|
critical("Unable to load private key file: %s: %s", ERR_reason_error_string(ERR_get_error()), conf->priv_key);
|
||||||
|
geoip_free();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clients = list_create(sizeof(client_ctx_t *), 64);
|
||||||
|
if (clients == NULL) {
|
||||||
|
critical("Unable to initialize client list");
|
||||||
|
ssl_free();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sem_init(&sem_clients_lock, 0, 1) != 0) {
|
||||||
|
critical("Unable to create clients lock semaphore");
|
||||||
|
ssl_free();
|
||||||
|
list_free(clients);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (async_init() != 0) {
|
||||||
|
critical("Unable to initialize async thread");
|
||||||
|
ssl_free();
|
||||||
|
geoip_free();
|
||||||
|
list_free(clients);
|
||||||
|
sem_destroy(&sem_clients_lock);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proxy_preload() != 0) {
|
||||||
|
critical("Unable to initialize proxy");
|
||||||
|
ssl_free();
|
||||||
|
geoip_free();
|
||||||
|
list_free(clients);
|
||||||
|
sem_destroy(&sem_clients_lock);
|
||||||
|
async_free();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||||
|
if (listen(sockets[i], LISTEN_BACKLOG) < 0) {
|
||||||
|
critical("Unable to listen on socket %i", i);
|
||||||
|
ssl_free();
|
||||||
|
geoip_free();
|
||||||
|
list_free(clients);
|
||||||
|
sem_destroy(&sem_clients_lock);
|
||||||
|
async_free();
|
||||||
|
proxy_unload();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_init();
|
||||||
|
|
||||||
|
if ((ret = cache_init()) != 0) {
|
||||||
|
if (ret == -1) critical("Unable to initialize cache");
|
||||||
|
ssl_free();
|
||||||
|
geoip_free();
|
||||||
|
list_free(clients);
|
||||||
|
sem_destroy(&sem_clients_lock);
|
||||||
|
async_free();
|
||||||
|
proxy_unload();
|
||||||
|
logger_stop();
|
||||||
|
logger_join();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_set_name("main");
|
||||||
|
|
||||||
|
workers_init();
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_SOCKETS; i++) {
|
||||||
|
async_fd(sockets[i], ASYNC_WAIT_READ, ASYNC_KEEP, &sockets[i], accept_cb, accept_err_cb, accept_err_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
notice("Ready to accept connections");
|
||||||
|
|
||||||
|
int error = 0;
|
||||||
|
async_thread();
|
||||||
|
if (errno != 0) {
|
||||||
|
errno = 0;
|
||||||
|
error = 2;
|
||||||
|
terminate_gracefully(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
notice("Goodbye!");
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
ssl_free();
|
||||||
|
list_free(clients);
|
||||||
|
sem_destroy(&sem_clients_lock);
|
||||||
|
geoip_free();
|
||||||
|
proxy_unload();
|
||||||
|
cache_join();
|
||||||
|
async_free();
|
||||||
|
logger_stop();
|
||||||
|
logger_join();
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
29
src/server.h
Normal file
29
src/server.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 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 "worker/func.h"
|
||||||
|
|
||||||
|
#define NUM_SOCKETS 2
|
||||||
|
#define LISTEN_BACKLOG 16
|
||||||
|
#define REQ_PER_CONNECTION 200
|
||||||
|
|
||||||
|
#define SOCKET_TIMEOUT 1
|
||||||
|
#define CLIENT_TIMEOUT 3600
|
||||||
|
#define SERVER_SOCKET_TIMEOUT_INIT 5
|
||||||
|
#define SERVER_SOCKET_TIMEOUT_RES 60
|
||||||
|
#define SERVER_TIMEOUT 3600
|
||||||
|
|
||||||
|
#define CNX_HANDLER_WORKERS 8
|
||||||
|
#define REQ_HANDLER_WORKERS 16
|
||||||
|
|
||||||
|
void server_free_client(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
#endif //SESIMOS_SERVER_H
|
50
src/worker/chunk_handler.c
Normal file
50
src/worker/chunk_handler.c
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief FastCGI frame handler
|
||||||
|
* @file src/worker/fcgi_frame_handler.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2023-01-22
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "func.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "../workers.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
void chunk_handler_func(chunk_ctx_t *ctx) {
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
|
||||||
|
|
||||||
|
char buf[CHUNK_SIZE];
|
||||||
|
long sent = sock_splice_chunked(&ctx->client->socket, ctx->socket, buf, sizeof(buf), ctx->flags | SOCK_SINGLE_CHUNK);
|
||||||
|
if (sent < 0) {
|
||||||
|
// error
|
||||||
|
error("Unable to splice chunk");
|
||||||
|
errno = 0;
|
||||||
|
ctx->err_cb(ctx);
|
||||||
|
} else if (sent == 0) {
|
||||||
|
// last chunk
|
||||||
|
ctx->client->chunks_transferred = 1;
|
||||||
|
ctx->next_cb(ctx);
|
||||||
|
} else {
|
||||||
|
// next chunk
|
||||||
|
handle_chunk(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int handle_chunks(client_ctx_t *ctx, sock *socket, int flags, void (*next_cb)(chunk_ctx_t *), void (*err_cb)(chunk_ctx_t *)) {
|
||||||
|
chunk_ctx_t *a = malloc(sizeof(chunk_ctx_t));
|
||||||
|
|
||||||
|
a->client = ctx;
|
||||||
|
a->socket = socket;
|
||||||
|
a->flags = flags;
|
||||||
|
a->next_cb = (void (*)(void *)) next_cb;
|
||||||
|
a->err_cb = (void (*)(void *)) err_cb;
|
||||||
|
|
||||||
|
handle_chunk(a);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
76
src/worker/fastcgi_frame_handler.c
Normal file
76
src/worker/fastcgi_frame_handler.c
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief FastCGI frame handler
|
||||||
|
* @file src/worker/fastcgi_frame_handler.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2023-01-22
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "func.h"
|
||||||
|
#include "../lib/fastcgi.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "../workers.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
void fastcgi_frame_handler_func(fastcgi_ctx_t *ctx) {
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
|
||||||
|
|
||||||
|
switch (fastcgi_recv_frame(&ctx->cnx)) {
|
||||||
|
case FCGI_STDOUT:
|
||||||
|
case FCGI_STDERR:
|
||||||
|
fastcgi_handle_frame(ctx);
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
error("Unable to receive FastCGI frame");
|
||||||
|
ctx->client->s_keep_alive = 0;
|
||||||
|
fastcgi_close(ctx);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// end of request received
|
||||||
|
write(ctx->cnx.fd_out, "\0\0\0\0\0\0\0\0\r\n", 10);
|
||||||
|
fastcgi_close(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int fastcgi_handle_connection(client_ctx_t *ctx, fastcgi_cnx_t **cnx) {
|
||||||
|
sock_set_timeout(&(*cnx)->socket, FASTCGI_TIMEOUT);
|
||||||
|
sock_set_socket_timeout(&(*cnx)->socket, FASTCGI_SOCKET_TIMEOUT);
|
||||||
|
|
||||||
|
fastcgi_ctx_t *a = malloc(sizeof(fastcgi_ctx_t));
|
||||||
|
a->closed = 0;
|
||||||
|
a->client = ctx;
|
||||||
|
memcpy(&a->cnx, *cnx, sizeof(fastcgi_cnx_t));
|
||||||
|
ctx->fcgi_ctx = a;
|
||||||
|
fastcgi_handle_frame(a);
|
||||||
|
*cnx = &a->cnx;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fastcgi_close(fastcgi_ctx_t *ctx) {
|
||||||
|
ctx->closed++;
|
||||||
|
if (ctx->closed != 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
|
||||||
|
|
||||||
|
fastcgi_php_error(&ctx->cnx, NULL);
|
||||||
|
|
||||||
|
if (ctx->cnx.app_status != 0)
|
||||||
|
error("FastCGI app terminated with exit code %i", ctx->cnx.app_status);
|
||||||
|
|
||||||
|
debug("Closing FastCGI connection");
|
||||||
|
|
||||||
|
fastcgi_close_cnx(&ctx->cnx);
|
||||||
|
ctx->client->fcgi_ctx = NULL;
|
||||||
|
free(ctx);
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fastcgi_close_error(fastcgi_ctx_t *ctx) {
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
|
||||||
|
fastcgi_close_cnx(&ctx->cnx);
|
||||||
|
}
|
200
src/worker/fastcgi_handler.c
Normal file
200
src/worker/fastcgi_handler.c
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief FastCGI handler
|
||||||
|
* @file src/worker/fastcgi_handler.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-28
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "func.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "../lib/utils.h"
|
||||||
|
#include "../workers.h"
|
||||||
|
#include "../lib/fastcgi.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
static int fastcgi_handler_1(client_ctx_t *ctx, fastcgi_cnx_t **fcgi_cnx);
|
||||||
|
static int fastcgi_handler_2(client_ctx_t *ctx, fastcgi_cnx_t *fcgi_cnx);
|
||||||
|
|
||||||
|
void fastcgi_handler_func(client_ctx_t *ctx) {
|
||||||
|
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->req_host, CLR_STR, ctx->log_prefix);
|
||||||
|
|
||||||
|
if (!ctx->chunks_transferred) {
|
||||||
|
fastcgi_cnx_t *fcgi_cnx = NULL;
|
||||||
|
int ret = fastcgi_handler_1(ctx, &fcgi_cnx);
|
||||||
|
respond(ctx);
|
||||||
|
if (ret == 0) {
|
||||||
|
fastcgi_handler_2(ctx, fcgi_cnx);
|
||||||
|
return;
|
||||||
|
} else if (ctx->fcgi_ctx != NULL) {
|
||||||
|
fastcgi_close(ctx->fcgi_ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request_complete(ctx);
|
||||||
|
handle_request(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fastcgi_handler_1(client_ctx_t *ctx, fastcgi_cnx_t **fcgi_cnx) {
|
||||||
|
http_res *res = &ctx->res;
|
||||||
|
http_req *req = &ctx->req;
|
||||||
|
http_uri *uri = &ctx->uri;
|
||||||
|
sock *client = &ctx->socket;
|
||||||
|
char *err_msg = ctx->err_msg;
|
||||||
|
char buf[1024];
|
||||||
|
|
||||||
|
int mode, ret;
|
||||||
|
if (strends(uri->filename, ".php")) {
|
||||||
|
mode = FASTCGI_BACKEND_PHP;
|
||||||
|
} else {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
error("Invalid FastCGI extension: %s", uri->filename);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
fastcgi_cnx_t fcgi_cnx_buf;
|
||||||
|
sock_init(&fcgi_cnx_buf.socket, -1, 0);
|
||||||
|
fcgi_cnx_buf.req_id = 0;
|
||||||
|
fcgi_cnx_buf.r_addr = ctx->socket.addr;
|
||||||
|
fcgi_cnx_buf.r_host = (ctx->host[0] != 0) ? ctx->host : NULL;
|
||||||
|
|
||||||
|
struct stat statbuf;
|
||||||
|
stat(uri->filename, &statbuf);
|
||||||
|
char *last_modified = http_format_date(statbuf.st_mtime, buf, sizeof(buf));
|
||||||
|
http_add_header_field(&res->hdr, "Last-Modified", last_modified);
|
||||||
|
|
||||||
|
res->status = http_get_status(200);
|
||||||
|
if (fastcgi_init(&fcgi_cnx_buf, mode, ctx->req_num, client, req, uri) != 0) {
|
||||||
|
fastcgi_close_cnx(&fcgi_cnx_buf);
|
||||||
|
res->status = http_get_status(503);
|
||||||
|
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*fcgi_cnx) = &fcgi_cnx_buf;
|
||||||
|
fastcgi_handle_connection(ctx, fcgi_cnx);
|
||||||
|
|
||||||
|
int expect_100_continue = 0;
|
||||||
|
const char *client_expect = http_get_header_field(&req->hdr, "Expect");
|
||||||
|
if (client_expect != NULL && strcasecmp(client_expect, "100-continue") == 0) {
|
||||||
|
expect_100_continue = 1;
|
||||||
|
} else if (client_expect != NULL) {
|
||||||
|
fastcgi_close_cnx((&fcgi_cnx_buf));
|
||||||
|
res->status = http_get_status(417);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
const char *client_content_length = http_get_header_field(&req->hdr, "Content-Length");
|
||||||
|
const char *client_transfer_encoding = http_get_header_field(&req->hdr, "Transfer-Encoding");
|
||||||
|
if (client_content_length != NULL) {
|
||||||
|
if (expect_100_continue) {
|
||||||
|
info(HTTP_1XX_STR "100 Continue" CLR_STR);
|
||||||
|
http_send_100_continue(client);
|
||||||
|
}
|
||||||
|
unsigned long client_content_len = strtoul(client_content_length, NULL, 10);
|
||||||
|
ret = fastcgi_receive(*fcgi_cnx, client, client_content_len);
|
||||||
|
} else if (strcontains(client_transfer_encoding, "chunked")) {
|
||||||
|
if (expect_100_continue) {
|
||||||
|
info(HTTP_1XX_STR "100 Continue" CLR_STR);
|
||||||
|
http_send_100_continue(client);
|
||||||
|
}
|
||||||
|
ret = fastcgi_receive_chunked(*fcgi_cnx, client);
|
||||||
|
} else if (expect_100_continue) {
|
||||||
|
fastcgi_close_cnx((&fcgi_cnx_buf));
|
||||||
|
res->status = http_get_status(417);
|
||||||
|
return 3;
|
||||||
|
} else {
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
if (ret != 0) {
|
||||||
|
if (ret < 0) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
|
||||||
|
}
|
||||||
|
res->status = http_get_status(502);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
fastcgi_close_stdin(*fcgi_cnx);
|
||||||
|
|
||||||
|
if ((ret = fastcgi_header(*fcgi_cnx, res, err_msg)) != 0) {
|
||||||
|
if (ret == -1) res->status = http_get_status(502);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *status_hdr = http_get_header_field(&res->hdr, "Status");
|
||||||
|
if (status_hdr != NULL) {
|
||||||
|
int status_code = (int) strtoul(status_hdr, NULL, 10);
|
||||||
|
res->status = http_get_status(status_code);
|
||||||
|
http_remove_header_field(&res->hdr, "Status", HTTP_REMOVE_ALL);
|
||||||
|
if (res->status == NULL && status_code >= 100 && status_code <= 999) {
|
||||||
|
ctx->custom_status.code = status_code;
|
||||||
|
ctx->custom_status.type = 0;
|
||||||
|
strcpy(ctx->custom_status.msg, status_hdr + 4);
|
||||||
|
res->status = &ctx->custom_status;
|
||||||
|
} else if (res->status == NULL) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
sprintf(err_msg, "The status_hdr code was set to an invalid or unknown value.");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *content_length_f = http_get_header_field(&res->hdr, "Content-Length");
|
||||||
|
ctx->content_length = (content_length_f == NULL) ? -1 : strtol(content_length_f, NULL, 10);
|
||||||
|
|
||||||
|
const char *content_type = http_get_header_field(&res->hdr, "Content-Type");
|
||||||
|
const char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
|
||||||
|
if (content_encoding == NULL &&
|
||||||
|
content_type != NULL &&
|
||||||
|
strstarts(content_type, "text/html") &&
|
||||||
|
ctx->content_length != -1 &&
|
||||||
|
ctx->content_length <= sizeof(ctx->msg_content) - 1)
|
||||||
|
{
|
||||||
|
fastcgi_dump(*fcgi_cnx, ctx->msg_content, sizeof(ctx->msg_content));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->use_fastcgi = 1;
|
||||||
|
ctx->content_length = -1;
|
||||||
|
|
||||||
|
if (http_get_header_field(&res->hdr, "Content-Length") == NULL) {
|
||||||
|
http_add_header_field(&res->hdr, "Transfer-Encoding", "chunked");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fastcgi_next_cb(chunk_ctx_t *ctx) {
|
||||||
|
if (ctx->client->fcgi_ctx) {
|
||||||
|
fastcgi_close(ctx->client->fcgi_ctx);
|
||||||
|
ctx->client->fcgi_ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fastcgi_handle(ctx->client);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fastcgi_error_cb(chunk_ctx_t *ctx) {
|
||||||
|
if (ctx->client->chunks_transferred)
|
||||||
|
return;
|
||||||
|
|
||||||
|
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->client->req_host, CLR_STR, ctx->client->log_prefix);
|
||||||
|
|
||||||
|
// FIXME segfault on error_cb
|
||||||
|
warning("Closing connection due to FastCGI error");
|
||||||
|
if(ctx->client->fcgi_ctx) {
|
||||||
|
fastcgi_close_error(ctx->client->fcgi_ctx);
|
||||||
|
ctx->client->fcgi_ctx = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tcp_close(ctx->client);
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fastcgi_handler_2(client_ctx_t *ctx, fastcgi_cnx_t *fcgi_cnx) {
|
||||||
|
int chunked = strcontains(http_get_header_field(&ctx->res.hdr, "Transfer-Encoding"), "chunked");
|
||||||
|
handle_chunks(ctx, &fcgi_cnx->out, chunked ? SOCK_CHUNKED : 0, fastcgi_next_cb, fastcgi_error_cb);
|
||||||
|
return 1;
|
||||||
|
}
|
100
src/worker/func.h
Normal file
100
src/worker/func.h
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Worker function header file
|
||||||
|
* @file src/worker/func.h
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-29
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SESIMOS_FUNC_H
|
||||||
|
#define SESIMOS_FUNC_H
|
||||||
|
|
||||||
|
#include "../defs.h"
|
||||||
|
#include "../lib/sock.h"
|
||||||
|
#include "../lib/http.h"
|
||||||
|
#include "../lib/uri.h"
|
||||||
|
#include "../lib/config.h"
|
||||||
|
#include "../lib/proxy.h"
|
||||||
|
#include "../lib/fastcgi.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
sock socket;
|
||||||
|
int req_num;
|
||||||
|
unsigned char s_keep_alive:1, c_keep_alive:1, use_fastcgi:4, use_proxy:2, ws_close:2, chunks_transferred:1;
|
||||||
|
char cc[3], host[256];
|
||||||
|
char req_host[256], err_msg[256];
|
||||||
|
char log_prefix[128];
|
||||||
|
char _c_addr[INET6_ADDRSTRLEN + 1], _s_addr[INET6_ADDRSTRLEN + 1];
|
||||||
|
long cnx_s, cnx_e, req_s, res_ts, req_e;
|
||||||
|
http_req req;
|
||||||
|
http_res res;
|
||||||
|
http_uri uri;
|
||||||
|
http_status_ctx status;
|
||||||
|
http_status custom_status;
|
||||||
|
host_config_t *conf;
|
||||||
|
FILE *file;
|
||||||
|
long content_length;
|
||||||
|
char *msg_buf, *msg_buf_ptr, msg_content[1024];
|
||||||
|
proxy_ctx_t *proxy;
|
||||||
|
void *fcgi_ctx;
|
||||||
|
} client_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
client_ctx_t *client;
|
||||||
|
sock *socket;
|
||||||
|
void *other;
|
||||||
|
} ws_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char closed:4;
|
||||||
|
client_ctx_t *client;
|
||||||
|
fastcgi_cnx_t cnx;
|
||||||
|
} fastcgi_ctx_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
client_ctx_t *client;
|
||||||
|
sock *socket;
|
||||||
|
int flags;
|
||||||
|
void (*next_cb)(void *);
|
||||||
|
void (*err_cb)(void *);
|
||||||
|
} chunk_ctx_t;
|
||||||
|
|
||||||
|
void tcp_acceptor_func(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void request_handler_func(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void local_handler_func(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void fastcgi_handler_func(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void fastcgi_frame_handler_func(fastcgi_ctx_t *ctx);
|
||||||
|
|
||||||
|
void proxy_handler_func(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void proxy_peer_handler_func(proxy_ctx_t *ctx);
|
||||||
|
|
||||||
|
void ws_frame_handler_func(ws_ctx_t *ctx);
|
||||||
|
|
||||||
|
void chunk_handler_func(chunk_ctx_t *ctx);
|
||||||
|
|
||||||
|
int respond(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void request_complete(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void timeout_request(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void tcp_close(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
int ws_handle_connection(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void ws_close(ws_ctx_t *ctx);
|
||||||
|
|
||||||
|
int handle_chunks(client_ctx_t *ctx, sock *socket, int flags, void (*next_cb)(chunk_ctx_t *), void (*err_cb)(chunk_ctx_t *));
|
||||||
|
|
||||||
|
int fastcgi_handle_connection(client_ctx_t *ctx, fastcgi_cnx_t **cnx);
|
||||||
|
|
||||||
|
void fastcgi_close(fastcgi_ctx_t *ctx);
|
||||||
|
|
||||||
|
void fastcgi_close_error(fastcgi_ctx_t *ctx);
|
||||||
|
|
||||||
|
#endif //SESIMOS_FUNC_H
|
257
src/worker/local_handler.c
Normal file
257
src/worker/local_handler.c
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Local filesystem handler
|
||||||
|
* @file src/worker/local_handler.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-29
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "func.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "../lib/utils.h"
|
||||||
|
#include "../lib/compress.h"
|
||||||
|
#include "../workers.h"
|
||||||
|
#include "../lib/list.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
static int local_handler(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void local_handler_func(client_ctx_t *ctx) {
|
||||||
|
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->req_host, CLR_STR, ctx->log_prefix);
|
||||||
|
|
||||||
|
switch (local_handler(ctx)) {
|
||||||
|
case 0:
|
||||||
|
respond(ctx);
|
||||||
|
request_complete(ctx);
|
||||||
|
handle_request(ctx);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
fastcgi_handle(ctx);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tcp_close(ctx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int range_handler(client_ctx_t *ctx) {
|
||||||
|
char buf[64];
|
||||||
|
long num0, num1, num2;
|
||||||
|
char *ptr;
|
||||||
|
int mode;
|
||||||
|
const char *range = http_get_header_field(&ctx->req.hdr, "Range");
|
||||||
|
|
||||||
|
if (strcontains(range, ","))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
ctx->file = fopen(ctx->uri.filename, "rb");
|
||||||
|
|
||||||
|
if (strstarts(range, "bytes=")) {
|
||||||
|
mode = 0;
|
||||||
|
range += 6;
|
||||||
|
num0 = fsize(ctx->file), num1 = 0, num2 = num0 - 1;
|
||||||
|
sprintf(buf, "bytes */%li", num0);
|
||||||
|
} else if (strstarts(range, "lines=") && mime_is_text(ctx->uri.meta->type)) {
|
||||||
|
mode = 1;
|
||||||
|
range += 6;
|
||||||
|
num0 = flines(ctx->file), num1 = 1, num2 = num0;
|
||||||
|
sprintf(buf, "lines */%li", num0);
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_add_header_field(&ctx->res.hdr, "Content-Range", buf);
|
||||||
|
|
||||||
|
if ((ptr = strchr(range, '-')) == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (num0 == 0) {
|
||||||
|
ctx->content_length = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr[1] != 0) num2 = (long) strtoul(ptr + 1, NULL, 10);
|
||||||
|
if (ptr != range) {
|
||||||
|
num1 = (long) strtoul(range, NULL, 10);
|
||||||
|
} else {
|
||||||
|
if (mode == 0) {
|
||||||
|
num1 = num0 - num2 - 1;
|
||||||
|
num2 = num0 - 1;
|
||||||
|
} else if (mode == 1) {
|
||||||
|
num1 = num0 - num2 + 1;
|
||||||
|
num2 = num0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mode == 0 && (num1 >= num0 || num2 >= num0 || num1 > num2 || num1 < 0 || num2 < 0)) ||
|
||||||
|
(mode == 1 && (num1 > num0 || num2 > num0 || num1 > num2 || num1 <= 0 || num2 <= 0)))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(buf, "%s %li-%li/%li", (mode == 0) ? "bytes" : "lines", num1, num2, num0);
|
||||||
|
http_remove_header_field(&ctx->res.hdr, "Content-Range", HTTP_REMOVE_ALL);
|
||||||
|
http_add_header_field(&ctx->res.hdr, "Content-Range", buf);
|
||||||
|
|
||||||
|
if (mode == 0) {
|
||||||
|
fseek(ctx->file, num1, SEEK_SET);
|
||||||
|
ctx->content_length = num2 - num1 + 1;
|
||||||
|
} else if (mode == 1) {
|
||||||
|
fseekl(ctx->file, num1);
|
||||||
|
ctx->content_length = file_get_line_pos(ctx->file, num2 + 1) - file_get_line_pos(ctx->file, num1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int local_handler(client_ctx_t *ctx) {
|
||||||
|
http_res *res = &ctx->res;
|
||||||
|
http_req *req = &ctx->req;
|
||||||
|
http_uri *uri = &ctx->uri;
|
||||||
|
char *err_msg = ctx->err_msg;
|
||||||
|
|
||||||
|
char buf1[1024], buf2[1024];
|
||||||
|
int accept_if_modified_since = 0;
|
||||||
|
|
||||||
|
if (streq(req->method, "TRACE")) {
|
||||||
|
res->status = http_get_status(200);
|
||||||
|
http_add_header_field(&res->hdr, "Content-Type", "message/http");
|
||||||
|
|
||||||
|
ctx->msg_buf_ptr = malloc(4096);
|
||||||
|
ctx->msg_buf = ctx->msg_buf_ptr;
|
||||||
|
ctx->content_length = snprintf(ctx->msg_buf, 4096 - ctx->content_length, "%s %s HTTP/%s\r\n", req->method, req->uri, req->version);
|
||||||
|
for (int i = 0; i < list_size(req->hdr.fields); i++) {
|
||||||
|
const http_field *f = &req->hdr.fields[i];
|
||||||
|
ctx->content_length += snprintf(ctx->msg_buf + ctx->content_length, 4096 - ctx->content_length, "%s: %s\r\n", http_field_get_name(f), http_field_get_value(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstarts(uri->req_path, "/.well-known/")) {
|
||||||
|
http_add_header_field(&res->hdr, "Access-Control-Allow-Origin", "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!strstarts(uri->req_path, "/.well-known/") && strcontains(uri->path, "/.")) || strends(uri->filename, ".inc") || strends(uri->filename, ".inc.php")) {
|
||||||
|
res->status = http_get_status(403);
|
||||||
|
sprintf(err_msg, "Parts of this URI are hidden.");
|
||||||
|
return 0;
|
||||||
|
} else if (uri->filename == NULL && (int) uri->is_static && (int) uri->is_dir && strlen(uri->pathinfo) == 0) {
|
||||||
|
res->status = http_get_status(403);
|
||||||
|
sprintf(err_msg, "It is not allowed to list the contents of this directory.");
|
||||||
|
return 0;
|
||||||
|
} else if (uri->filename == NULL && (int) !uri->is_static && (int) uri->is_dir && strlen(uri->pathinfo) == 0) {
|
||||||
|
// TODO list directory contents
|
||||||
|
res->status = http_get_status(501);
|
||||||
|
sprintf(err_msg, "Listing contents of an directory is currently not implemented.");
|
||||||
|
return 0;
|
||||||
|
} else if (uri->filename == NULL || (strlen(uri->pathinfo) > 0 && (int) uri->is_static)) {
|
||||||
|
res->status = http_get_status(404);
|
||||||
|
return 0;
|
||||||
|
} else if (strlen(uri->pathinfo) != 0 && ctx->conf->local.dir_mode != URI_DIR_MODE_INFO) {
|
||||||
|
res->status = http_get_status(404);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uri->is_static) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *client_expect = http_get_header_field(&req->hdr, "Expect");
|
||||||
|
if (client_expect != NULL && strcasecmp(client_expect, "100-continue") != 0) {
|
||||||
|
res->status = http_get_status(417);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
res->status = http_get_status(200);
|
||||||
|
cache_init_uri(ctx->conf->cache, uri);
|
||||||
|
|
||||||
|
http_add_header_field(&res->hdr, "Accept-Ranges", mime_is_text(uri->meta->type) ? "bytes, lines" : "bytes");
|
||||||
|
|
||||||
|
if (!streq(req->method, "GET") && !streq(req->method, "HEAD")) {
|
||||||
|
res->status = http_get_status(405);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http_get_header_field(&req->hdr, "Content-Length") != NULL || http_get_header_field(&req->hdr, "Transfer-Encoding") != NULL) {
|
||||||
|
res->status = http_get_status(400);
|
||||||
|
sprintf(err_msg, "A GET request must not contain a payload");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *last_modified = http_format_date(uri->meta->mtime, buf1, sizeof(buf1));
|
||||||
|
http_add_header_field(&res->hdr, "Last-Modified", last_modified);
|
||||||
|
sprintf(buf2, "%s; charset=%s", uri->meta->type, uri->meta->charset);
|
||||||
|
http_add_header_field(&res->hdr, "Content-Type", buf2);
|
||||||
|
|
||||||
|
const char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
|
||||||
|
int enc = 0;
|
||||||
|
if (accept_encoding != NULL) {
|
||||||
|
if (uri->meta->filename_comp_br[0] != 0 && strcontains(accept_encoding, "br")) {
|
||||||
|
ctx->file = fopen(uri->meta->filename_comp_br, "rb");
|
||||||
|
if (ctx->file == NULL) {
|
||||||
|
cache_mark_dirty(ctx->conf->cache, uri->filename);
|
||||||
|
errno = 0;
|
||||||
|
} else {
|
||||||
|
http_add_header_field(&res->hdr, "Content-Encoding", "br");
|
||||||
|
enc = COMPRESS_BR;
|
||||||
|
}
|
||||||
|
} else if (uri->meta->filename_comp_gz[0] != 0 && strcontains(accept_encoding, "gzip")) {
|
||||||
|
ctx->file = fopen(uri->meta->filename_comp_gz, "rb");
|
||||||
|
if (ctx->file == NULL) {
|
||||||
|
cache_mark_dirty(ctx->conf->cache, uri->filename);
|
||||||
|
errno = 0;
|
||||||
|
} else {
|
||||||
|
http_add_header_field(&res->hdr, "Content-Encoding", "gzip");
|
||||||
|
enc = COMPRESS_GZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (enc != 0) {
|
||||||
|
http_add_header_field(&res->hdr, "Vary", "Accept-Encoding");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri->meta->etag[0] != 0) {
|
||||||
|
strcpy(buf1, uri->meta->etag);
|
||||||
|
if (enc) {
|
||||||
|
strcat(buf1, "-");
|
||||||
|
strcat(buf1, (enc & COMPRESS_BR) ? "br" : (enc & COMPRESS_GZ) ? "gzip" : "");
|
||||||
|
}
|
||||||
|
http_add_header_field(&res->hdr, "ETag", buf1);
|
||||||
|
}
|
||||||
|
|
||||||
|
http_add_header_field(&res->hdr, "Cache-Control", mime_is_text(uri->meta->type) ? "public, max-age=3600" : "public, max-age=86400");
|
||||||
|
|
||||||
|
const char *if_modified_since = http_get_header_field(&req->hdr, "If-Modified-Since");
|
||||||
|
const char *if_none_match = http_get_header_field(&req->hdr, "If-None-Match");
|
||||||
|
if ((if_none_match != NULL && !strcontains(if_none_match, uri->meta->etag)) ||
|
||||||
|
(accept_if_modified_since && streq(if_modified_since, last_modified)))
|
||||||
|
{
|
||||||
|
res->status = http_get_status(304);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (http_get_header_field(&req->hdr, "Range") != NULL) {
|
||||||
|
if (range_handler(ctx) == 0) {
|
||||||
|
res->status = http_get_status(206);
|
||||||
|
} else {
|
||||||
|
if (ctx->file) {
|
||||||
|
fclose(ctx->file);
|
||||||
|
ctx->file = NULL;
|
||||||
|
}
|
||||||
|
http_remove_header_field(&res->hdr, "Content-Type", HTTP_REMOVE_ALL);
|
||||||
|
http_remove_header_field(&res->hdr, "Last-Modified", HTTP_REMOVE_ALL);
|
||||||
|
http_remove_header_field(&res->hdr, "ETag", HTTP_REMOVE_ALL);
|
||||||
|
http_remove_header_field(&res->hdr, "Cache-Control", HTTP_REMOVE_ALL);
|
||||||
|
res->status = http_get_status(416);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->file == NULL) ctx->file = fopen(uri->filename, "rb");
|
||||||
|
ctx->content_length = fsize(ctx->file);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
157
src/worker/proxy_handler.c
Normal file
157
src/worker/proxy_handler.c
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Proxy handler
|
||||||
|
* @file src/worker/proxy_handler.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-29
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "func.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "../lib/utils.h"
|
||||||
|
#include "../lib/proxy.h"
|
||||||
|
#include "../lib/websocket.h"
|
||||||
|
#include "../workers.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static int proxy_handler_1(client_ctx_t *ctx);
|
||||||
|
static int proxy_handler_2(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void proxy_handler_func(client_ctx_t *ctx) {
|
||||||
|
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->req_host, CLR_STR, ctx->log_prefix);
|
||||||
|
|
||||||
|
// TODO handle 1xx responses
|
||||||
|
|
||||||
|
int ret = proxy_handler_1(ctx);
|
||||||
|
respond(ctx);
|
||||||
|
|
||||||
|
if (ret == 1) {
|
||||||
|
// error status code
|
||||||
|
if (proxy_unlock_ctx(ctx->proxy) == 1)
|
||||||
|
proxy_peer_handle(ctx->proxy);
|
||||||
|
} else if (ctx->use_proxy == 0) {
|
||||||
|
// proxy not used
|
||||||
|
proxy_close(ctx->proxy);
|
||||||
|
proxy_unlock_ctx(ctx->proxy);
|
||||||
|
} else if (ctx->use_proxy == 1) {
|
||||||
|
// proxy is used
|
||||||
|
if (proxy_handler_2(ctx) == 1) {
|
||||||
|
// chunked
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (proxy_unlock_ctx(ctx->proxy) == 1)
|
||||||
|
proxy_peer_handle(ctx->proxy);
|
||||||
|
} else if (ctx->use_proxy == 2) {
|
||||||
|
// WebSocket
|
||||||
|
ws_handle_connection(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->proxy = NULL;
|
||||||
|
request_complete(ctx);
|
||||||
|
handle_request(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int proxy_handler_1(client_ctx_t *ctx) {
|
||||||
|
http_res *res = &ctx->res;
|
||||||
|
http_status_ctx *status = &ctx->status;
|
||||||
|
|
||||||
|
char buf[1024];
|
||||||
|
|
||||||
|
info("Reverse proxy for " BLD_STR "[%s]:%i" CLR_STR, ctx->conf->proxy.hostname, ctx->conf->proxy.port);
|
||||||
|
http_remove_header_field(&res->hdr, "Date", HTTP_REMOVE_ALL);
|
||||||
|
http_remove_header_field(&res->hdr, "Server", HTTP_REMOVE_ALL);
|
||||||
|
|
||||||
|
ctx->use_proxy = proxy_init(&ctx->proxy, &ctx->req, res, status, ctx->conf, &ctx->socket, &ctx->custom_status, ctx->err_msg) == 0;
|
||||||
|
ctx->proxy->client = ctx;
|
||||||
|
|
||||||
|
if (ctx->use_proxy == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (res->status->code == 101) {
|
||||||
|
const char *connection = http_get_header_field(&res->hdr, "Connection");
|
||||||
|
const char *upgrade = http_get_header_field(&res->hdr, "Upgrade");
|
||||||
|
if (connection != NULL && upgrade != NULL &&
|
||||||
|
(strcontains(connection, "upgrade") || strcontains(connection, "Upgrade")) &&
|
||||||
|
streq(upgrade, "websocket"))
|
||||||
|
{
|
||||||
|
const char *ws_accept = http_get_header_field(&res->hdr, "Sec-WebSocket-Accept");
|
||||||
|
if (ws_calc_accept_key(status->ws_key, buf) == 0) {
|
||||||
|
ctx->use_proxy = streq(buf, ws_accept) ? 2 : 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status->status = 101;
|
||||||
|
status->origin = INTERNAL;
|
||||||
|
res->status = http_get_status(501);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let 300 be formatted by origin server
|
||||||
|
if (ctx->use_proxy && res->status->code >= 301 && res->status->code < 600) {
|
||||||
|
const char *content_type = http_get_header_field(&res->hdr, "Content-Type");
|
||||||
|
const char *content_length_f = http_get_header_field(&res->hdr, "Content-Length");
|
||||||
|
const char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
|
||||||
|
const char *transfer_encoding = http_get_header_field(&res->hdr, "Transfer-Encoding");
|
||||||
|
if (transfer_encoding == NULL && content_encoding == NULL && (
|
||||||
|
content_length_f == NULL ||
|
||||||
|
streq(content_length_f, "0") ||
|
||||||
|
(content_length_f != NULL && strstarts(content_type, "text/html"))))
|
||||||
|
{
|
||||||
|
long content_len = (!streq(ctx->req.method, "HEAD") && content_length_f != NULL) ? strtol(content_length_f, NULL, 10) : 0;
|
||||||
|
if (content_len < sizeof(ctx->msg_content)) {
|
||||||
|
if (status->status != 101) {
|
||||||
|
status->status = res->status->code;
|
||||||
|
status->origin = res->status->code >= 400 ? SERVER : NONE;
|
||||||
|
}
|
||||||
|
ctx->use_proxy = 0;
|
||||||
|
|
||||||
|
if (content_len > 0)
|
||||||
|
proxy_dump(ctx->proxy, ctx->msg_content, content_len);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return streq(ctx->req.method, "HEAD") ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void proxy_chunk_next_cb(chunk_ctx_t *ctx) {
|
||||||
|
if (proxy_unlock_ctx(ctx->client->proxy) == 1)
|
||||||
|
proxy_peer_handle(ctx->client->proxy);
|
||||||
|
|
||||||
|
ctx->client->proxy = NULL;
|
||||||
|
request_complete(ctx->client);
|
||||||
|
handle_request(ctx->client);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void proxy_chunk_err_cb(chunk_ctx_t *ctx) {
|
||||||
|
ctx->client->c_keep_alive = 0;
|
||||||
|
proxy_close(ctx->client->proxy);
|
||||||
|
proxy_unlock_ctx(ctx->client->proxy);
|
||||||
|
|
||||||
|
ctx->client->proxy = NULL;
|
||||||
|
request_complete(ctx->client);
|
||||||
|
handle_request(ctx->client);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int proxy_handler_2(client_ctx_t *ctx) {
|
||||||
|
const char *transfer_encoding = http_get_header_field(&ctx->res.hdr, "Transfer-Encoding");
|
||||||
|
int chunked = strcontains(transfer_encoding, "chunked");
|
||||||
|
|
||||||
|
const char *content_len = http_get_header_field(&ctx->res.hdr, "Content-Length");
|
||||||
|
unsigned long len_to_send = (content_len != NULL) ? strtol(content_len, NULL, 10) : 0;
|
||||||
|
|
||||||
|
if (chunked) {
|
||||||
|
handle_chunks(ctx, &ctx->proxy->proxy, SOCK_CHUNKED, proxy_chunk_next_cb, proxy_chunk_err_cb);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
if ((ret = proxy_send(ctx->proxy, &ctx->socket, len_to_send, 0)) == -1) {
|
||||||
|
ctx->c_keep_alive = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
17
src/worker/proxy_peer_handler.c
Normal file
17
src/worker/proxy_peer_handler.c
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Proxy peer handler
|
||||||
|
* @file src/worker/proxy_peer_handler.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2023-07-07
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "func.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "../lib/utils.h"
|
||||||
|
|
||||||
|
void proxy_peer_handler_func(proxy_ctx_t *ctx) {
|
||||||
|
if (!ctx->initialized || ctx->in_use) return;
|
||||||
|
logger_set_prefix("[%s%*s%s]", BLD_STR, ADDRSTRLEN, ctx->host, CLR_STR);
|
||||||
|
proxy_close(ctx);
|
||||||
|
}
|
397
src/worker/request_handler.c
Normal file
397
src/worker/request_handler.c
Normal file
@ -0,0 +1,397 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Client request handler
|
||||||
|
* @file src/worker/request_handler.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-28
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../defs.h"
|
||||||
|
#include "func.h"
|
||||||
|
#include "../workers.h"
|
||||||
|
#include "../lib/mpmc.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "../lib/utils.h"
|
||||||
|
#include "../server.h"
|
||||||
|
#include "../lib/res.h"
|
||||||
|
#include "../lib/error.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
static int request_handler(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void request_handler_func(client_ctx_t *ctx) {
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->socket.s_addr, ctx->log_prefix);
|
||||||
|
|
||||||
|
switch (request_handler(ctx)) {
|
||||||
|
case 0:
|
||||||
|
respond(ctx);
|
||||||
|
request_complete(ctx);
|
||||||
|
handle_request(ctx);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
local_handle(ctx);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
proxy_handle(ctx);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tcp_close(ctx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_ctx(client_ctx_t *ctx) {
|
||||||
|
ctx->conf = NULL;
|
||||||
|
ctx->file = NULL;
|
||||||
|
ctx->proxy = NULL;
|
||||||
|
ctx->use_fastcgi = 0;
|
||||||
|
ctx->chunks_transferred = 0;
|
||||||
|
ctx->fcgi_ctx = NULL;
|
||||||
|
ctx->use_proxy = 0;
|
||||||
|
ctx->ws_close = 0;
|
||||||
|
ctx->proxy = NULL;
|
||||||
|
ctx->msg_content[0] = 0;
|
||||||
|
ctx->msg_buf = NULL;
|
||||||
|
ctx->msg_buf_ptr = NULL;
|
||||||
|
ctx->req_host[0] = 0;
|
||||||
|
ctx->err_msg[0] = 0;
|
||||||
|
ctx->req_s = ctx->socket.ts_last;
|
||||||
|
|
||||||
|
memset(&ctx->uri, 0, sizeof(ctx->uri));
|
||||||
|
memset(&ctx->req, 0, sizeof(ctx->req));
|
||||||
|
memset(&ctx->res, 0, sizeof(ctx->res));
|
||||||
|
|
||||||
|
ctx->res.status = http_get_status(501);
|
||||||
|
http_init_hdr(&ctx->res.hdr);
|
||||||
|
sprintf(ctx->res.version, "1.1");
|
||||||
|
|
||||||
|
ctx->status.status = 0;
|
||||||
|
ctx->status.origin = NONE;
|
||||||
|
ctx->status.ws_key = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int request_handler(client_ctx_t *ctx) {
|
||||||
|
sock *client = &ctx->socket;
|
||||||
|
char *err_msg = ctx->err_msg;
|
||||||
|
http_res *res = &ctx->res;
|
||||||
|
|
||||||
|
long ret;
|
||||||
|
char buf0[1024], buf1[1024];
|
||||||
|
|
||||||
|
ctx->req_s = clock_micros();
|
||||||
|
|
||||||
|
init_ctx(ctx);
|
||||||
|
|
||||||
|
http_add_header_field(&res->hdr, "Date", http_get_date(buf0, sizeof(buf0)));
|
||||||
|
http_add_header_field(&res->hdr, "Server", SERVER_STR);
|
||||||
|
/*if (ret <= 0) {
|
||||||
|
if (errno != 0) return 0;
|
||||||
|
|
||||||
|
ctx->c_keep_alive = 0;
|
||||||
|
res->status = http_get_status(408);
|
||||||
|
return 0;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
http_req *req = &ctx->req;
|
||||||
|
ret = http_receive_request(client, req);
|
||||||
|
if (ret != 0) {
|
||||||
|
ctx->c_keep_alive = 0;
|
||||||
|
error("Unable to receive http header");
|
||||||
|
sprintf(err_msg, "Unable to receive http header: %s.", error_str(errno, buf0, sizeof(buf0)));
|
||||||
|
int err = error_get_http();
|
||||||
|
res->status = http_get_status(err == HTTP_ERROR_URI_TOO_LONG ? 414 : (err == HTTP_ERROR_TOO_MANY_HEADER_FIELDS ? 431 : 400));
|
||||||
|
errno = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *hdr_connection = http_get_header_field(&req->hdr, "Connection");
|
||||||
|
ctx->c_keep_alive = (strcontains(hdr_connection, "keep-alive") || strcontains(hdr_connection, "Keep-Alive"));
|
||||||
|
const char *host_ptr = http_get_header_field(&req->hdr, "Host");
|
||||||
|
if (host_ptr != NULL && strlen(host_ptr) > 255) {
|
||||||
|
ctx->req_host[0] = 0;
|
||||||
|
res->status = http_get_status(400);
|
||||||
|
sprintf(err_msg, "Host header field is too long.");
|
||||||
|
return 0;
|
||||||
|
} else if (host_ptr == NULL || strchr(host_ptr, '/') != NULL) {
|
||||||
|
if (strchr(ctx->socket.addr, ':') == NULL) {
|
||||||
|
strcpy(ctx->req_host, ctx->socket.addr);
|
||||||
|
} else {
|
||||||
|
sprintf(ctx->req_host, "[%s]", ctx->socket.addr);
|
||||||
|
}
|
||||||
|
res->status = http_get_status(400);
|
||||||
|
sprintf(err_msg, "The client provided no or an invalid Host header field.");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
strcpy(ctx->req_host, host_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->req_host, CLR_STR, ctx->log_prefix);
|
||||||
|
info(BLD_STR "%s %s", req->method, req->uri);
|
||||||
|
|
||||||
|
if (strstarts(req->uri, "/.sesimos/res/")) {
|
||||||
|
if (!streq(req->method, "GET") && !streq(req->method, "HEAD")) {
|
||||||
|
res->status = http_get_status(405);
|
||||||
|
http_add_header_field(&res->hdr, "Allow", "GET, HEAD");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res_t resources[] = {
|
||||||
|
{"style.css", "text/css; charset=UTF-8", http_style_doc, http_style_doc_size},
|
||||||
|
{"icon-error.svg", "image/svg+xml; charset=UTF-8", http_icon_error, http_icon_error_size},
|
||||||
|
{"icon-info.svg", "image/svg+xml; charset=UTF-8", http_icon_info, http_icon_info_size},
|
||||||
|
{"icon-success.svg", "image/svg+xml; charset=UTF-8", http_icon_success, http_icon_success_size},
|
||||||
|
{"icon-warning.svg", "image/svg+xml; charset=UTF-8", http_icon_warning, http_icon_warning_size},
|
||||||
|
{"globe.svg", "image/svg+xml; charset=UTF-8", http_icon_globe, http_icon_globe_size},
|
||||||
|
};
|
||||||
|
|
||||||
|
res->status = http_get_status(404);
|
||||||
|
for (int i = 0; i < sizeof(resources) / sizeof(res_t); i++) {
|
||||||
|
const res_t *r = &resources[i];
|
||||||
|
if (streq(req->uri + 14, r->name)) {
|
||||||
|
res->status = http_get_status(203);
|
||||||
|
http_add_header_field(&res->hdr, "Content-Type", r->type);
|
||||||
|
http_add_header_field(&res->hdr, "Cache-Control", "public, max-age=86400");
|
||||||
|
ctx->msg_buf = (char *) r->content;
|
||||||
|
ctx->content_length = r->size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->conf = get_host_config(ctx->req_host);
|
||||||
|
if (ctx->conf == NULL) {
|
||||||
|
res->status = http_get_status(421);
|
||||||
|
strcpy(ctx->err_msg, "The requested host name is not configured on the server.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_uri *uri = &ctx->uri;
|
||||||
|
unsigned char dir_mode = (ctx->conf->type == CONFIG_TYPE_LOCAL ? ctx->conf->local.dir_mode : URI_DIR_MODE_NO_VALIDATION);
|
||||||
|
ret = uri_init(uri, ctx->conf->local.webroot, req->uri, dir_mode);
|
||||||
|
if (ret != 0) {
|
||||||
|
if (ret == 1) {
|
||||||
|
sprintf(err_msg, "Invalid URI: has to start with slash.");
|
||||||
|
res->status = http_get_status(400);
|
||||||
|
} else if (ret == 2) {
|
||||||
|
sprintf(err_msg, "Invalid URI: contains relative path change (/../).");
|
||||||
|
res->status = http_get_status(400);
|
||||||
|
} else if (ret == 3) {
|
||||||
|
sprintf(err_msg, "The specified webroot directory does not exist.");
|
||||||
|
res->status = http_get_status(404);
|
||||||
|
} else {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->conf->type == CONFIG_TYPE_LOCAL && streq(req->method, "TRACE")) {
|
||||||
|
return 1;
|
||||||
|
} else if (dir_mode != URI_DIR_MODE_NO_VALIDATION) {
|
||||||
|
ssize_t size = sizeof(buf0);
|
||||||
|
url_decode(req->uri, buf0, &size);
|
||||||
|
int change_proto = (!client->enc && !strstarts(uri->uri, "/.well-known/"));
|
||||||
|
if (!streq(uri->uri, buf0) || change_proto) {
|
||||||
|
res->status = http_get_status(308);
|
||||||
|
size = url_encode(uri->uri, strlen(uri->uri), buf0, sizeof(buf0));
|
||||||
|
if (change_proto) {
|
||||||
|
int p_len = snprintf(buf1, sizeof(buf1), "https://%s%s", ctx->req_host, buf0);
|
||||||
|
if (p_len < 0 || p_len >= sizeof(buf1)) {
|
||||||
|
res->status = http_get_status(500);
|
||||||
|
error("Header field 'Location' too long");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
http_add_header_field(&res->hdr, "Location", buf1);
|
||||||
|
} else {
|
||||||
|
http_add_header_field(&res->hdr, "Location", buf0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if (!client->enc) {
|
||||||
|
res->status = http_get_status(308);
|
||||||
|
sprintf(buf0, "https://%s%s", ctx->req_host, req->uri);
|
||||||
|
http_add_header_field(&res->hdr, "Location", buf0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->conf->type == CONFIG_TYPE_LOCAL) {
|
||||||
|
return 1;
|
||||||
|
} else if (ctx->conf->type == CONFIG_TYPE_REVERSE_PROXY) {
|
||||||
|
return 2;
|
||||||
|
} else {
|
||||||
|
error("Unknown host type: %i", ctx->conf->type);
|
||||||
|
res->status = http_get_status(501);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int respond(client_ctx_t *ctx) {
|
||||||
|
http_req *req = &ctx->req;
|
||||||
|
http_res *res = &ctx->res;
|
||||||
|
sock *client = &ctx->socket;
|
||||||
|
http_status_ctx *status = &ctx->status;
|
||||||
|
char *err_msg = ctx->err_msg;
|
||||||
|
|
||||||
|
long ret = 0;
|
||||||
|
char buf0[1024];
|
||||||
|
char msg_pre_buf_1[4096], msg_pre_buf_2[4096];
|
||||||
|
char buffer[CHUNK_SIZE];
|
||||||
|
|
||||||
|
if (!ctx->use_proxy) {
|
||||||
|
if (ctx->conf != NULL && ctx->conf->type == CONFIG_TYPE_LOCAL && ctx->uri.is_static && res->status->code == 405) {
|
||||||
|
http_add_header_field(&res->hdr, "Allow", "GET, HEAD, TRACE");
|
||||||
|
}
|
||||||
|
if (http_get_header_field(&res->hdr, "Accept-Ranges") == NULL) {
|
||||||
|
http_add_header_field(&res->hdr, "Accept-Ranges", "none");
|
||||||
|
}
|
||||||
|
if (!ctx->use_fastcgi && ctx->file == NULL && ctx->msg_buf == NULL) {
|
||||||
|
http_remove_header_field(&res->hdr, "Date", HTTP_REMOVE_ALL);
|
||||||
|
http_remove_header_field(&res->hdr, "Server", HTTP_REMOVE_ALL);
|
||||||
|
http_remove_header_field(&res->hdr, "Cache-Control", HTTP_REMOVE_ALL);
|
||||||
|
http_remove_header_field(&res->hdr, "Content-Type", HTTP_REMOVE_ALL);
|
||||||
|
http_remove_header_field(&res->hdr, "Content-Encoding", HTTP_REMOVE_ALL);
|
||||||
|
http_add_header_field(&res->hdr, "Date", http_get_date(buf0, sizeof(buf0)));
|
||||||
|
http_add_header_field(&res->hdr, "Server", SERVER_STR);
|
||||||
|
http_add_header_field(&res->hdr, "Cache-Control", "no-cache");
|
||||||
|
http_add_header_field(&res->hdr, "Content-Type", "text/html; charset=UTF-8");
|
||||||
|
|
||||||
|
// TODO list Locations on 3xx Redirects
|
||||||
|
const http_doc_info *http_info = http_get_status_info(res->status->code);
|
||||||
|
const http_status_msg *http_msg = http_get_error_msg(res->status->code);
|
||||||
|
|
||||||
|
if (ctx->msg_content[0] == 0) {
|
||||||
|
if (res->status->code >= 300 && res->status->code < 400) {
|
||||||
|
const char *location = http_get_header_field(&res->hdr, "Location");
|
||||||
|
if (location != NULL) {
|
||||||
|
snprintf(ctx->msg_content, sizeof(ctx->msg_content), " <ul>\n <li><a href=\"%s\">%s</a></li>\n </ul>\n", location, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strstarts(ctx->msg_content, "<!DOCTYPE html>") || strstarts(ctx->msg_content, "<html")) {
|
||||||
|
ctx->msg_content[0] = 0;
|
||||||
|
// TODO let relevant information pass?
|
||||||
|
}
|
||||||
|
|
||||||
|
char *proxy_doc = "";
|
||||||
|
if (ctx->conf != NULL && ctx->conf->type == CONFIG_TYPE_REVERSE_PROXY) {
|
||||||
|
const http_status *status_hdr = http_get_status(status->status);
|
||||||
|
char stat_str[8];
|
||||||
|
sprintf(stat_str, "%03i", status->status);
|
||||||
|
snprintf(msg_pre_buf_2, sizeof(msg_pre_buf_2), http_proxy_doc,
|
||||||
|
" success",
|
||||||
|
(status->origin == CLIENT_REQ) ? " error" : " success",
|
||||||
|
(status->origin == INTERNAL) ? " error" : " success",
|
||||||
|
(status->origin == SERVER_REQ) ? " error" : (status->status == 0 ? "" : " success"),
|
||||||
|
(status->origin == CLIENT_RES) ? " error" : " success",
|
||||||
|
(status->origin == SERVER) ? " error" : (status->status == 0 ? "" : " success"),
|
||||||
|
(status->origin == SERVER_RES) ? " error" : (status->status == 0 ? "" : " success"),
|
||||||
|
(status->origin == INTERNAL) ? " error" : " success",
|
||||||
|
(status->origin == INTERNAL || status->origin == SERVER) ? " error" : " success",
|
||||||
|
res->status->code,
|
||||||
|
res->status->msg,
|
||||||
|
(status->status == 0) ? "???" : stat_str,
|
||||||
|
(status_hdr != NULL) ? status_hdr->msg : "",
|
||||||
|
ctx->req_host, SERVER_NAME);
|
||||||
|
proxy_doc = msg_pre_buf_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->msg_buf_ptr = malloc(4096);
|
||||||
|
ctx->msg_buf = ctx->msg_buf_ptr;
|
||||||
|
snprintf(msg_pre_buf_1, sizeof(msg_pre_buf_1), http_info->doc,
|
||||||
|
res->status->code, res->status->msg, http_msg != NULL ? http_msg->msg : "", err_msg);
|
||||||
|
ctx->content_length = snprintf(ctx->msg_buf, 4096, http_default_doc, res->status->code, res->status->msg,
|
||||||
|
msg_pre_buf_1, http_info->mode, http_info->icon, http_info->color,
|
||||||
|
ctx->req_host, proxy_doc, ctx->msg_content, SERVER_STR_HTML);
|
||||||
|
}
|
||||||
|
if (ctx->content_length >= 0) {
|
||||||
|
sprintf(buf0, "%li", ctx->content_length);
|
||||||
|
http_remove_header_field(&res->hdr, "Content-Length", HTTP_REMOVE_ALL);
|
||||||
|
http_add_header_field(&res->hdr, "Content-Length", buf0);
|
||||||
|
} else if (http_get_header_field(&res->hdr, "Transfer-Encoding") == NULL) {
|
||||||
|
ctx->s_keep_alive = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->use_proxy != 2) {
|
||||||
|
http_remove_header_field(&res->hdr, "Connection", HTTP_REMOVE_ALL);
|
||||||
|
http_remove_header_field(&res->hdr, "Keep-Alive", HTTP_REMOVE_ALL);
|
||||||
|
if (ctx->s_keep_alive && ctx->c_keep_alive) {
|
||||||
|
http_add_header_field(&res->hdr, "Connection", "keep-alive");
|
||||||
|
sprintf(buf0, "timeout=%i, max=%i", CLIENT_TIMEOUT, REQ_PER_CONNECTION);
|
||||||
|
http_add_header_field(&res->hdr, "Keep-Alive", buf0);
|
||||||
|
} else {
|
||||||
|
http_add_header_field(&res->hdr, "Connection", "close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http_send_response(client, res);
|
||||||
|
ctx->res_ts = clock_micros();
|
||||||
|
const char *location = http_get_header_field(&res->hdr, "Location");
|
||||||
|
info("%s%s%03i %s%s%s (%s)%s", http_get_status_color(res->status->code), ctx->use_proxy ? "-> " : "", res->status->code,
|
||||||
|
res->status->msg, location != NULL ? " -> " : "", location != NULL ? location : "",
|
||||||
|
format_duration(ctx->res_ts - ctx->req_s, buf0), CLR_STR);
|
||||||
|
|
||||||
|
// TODO access/error log file
|
||||||
|
|
||||||
|
if (ctx->use_proxy) {
|
||||||
|
// reverse proxy
|
||||||
|
return 3;
|
||||||
|
} else if (!streq(req->method, "HEAD")) {
|
||||||
|
// default response
|
||||||
|
if (ctx->msg_buf != NULL) {
|
||||||
|
ret = sock_send_x(client, ctx->msg_buf, ctx->content_length, 0);
|
||||||
|
if (ret <= 0) {
|
||||||
|
error("Unable to send");
|
||||||
|
}
|
||||||
|
} else if (ctx->file != NULL) {
|
||||||
|
unsigned long len, snd_len = 0;
|
||||||
|
while (snd_len < ctx->content_length) {
|
||||||
|
len = fread(buffer, 1, CHUNK_SIZE, ctx->file);
|
||||||
|
if (snd_len + len > ctx->content_length) {
|
||||||
|
len = ctx->content_length - snd_len;
|
||||||
|
}
|
||||||
|
ret = sock_send_x(client, buffer, len, feof(ctx->file) ? 0 : MSG_MORE);
|
||||||
|
if (ret <= 0) {
|
||||||
|
error("Unable to send");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
snd_len += ret;
|
||||||
|
}
|
||||||
|
} else if (ctx->use_fastcgi) {
|
||||||
|
// FastCGI
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0) ctx->c_keep_alive = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void request_complete(client_ctx_t *ctx) {
|
||||||
|
char buf[32];
|
||||||
|
ctx->req_e = clock_micros();
|
||||||
|
info("Transfer complete: %s", format_duration(ctx->req_e - ctx->req_s, buf));
|
||||||
|
|
||||||
|
if (ctx->file) fclose(ctx->file);
|
||||||
|
free(ctx->msg_buf_ptr);
|
||||||
|
uri_free(&ctx->uri);
|
||||||
|
http_free_req(&ctx->req);
|
||||||
|
http_free_res(&ctx->res);
|
||||||
|
}
|
||||||
|
|
||||||
|
void timeout_request(client_ctx_t *ctx) {
|
||||||
|
init_ctx(ctx);
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->socket.s_addr, ctx->log_prefix);
|
||||||
|
|
||||||
|
ctx->s_keep_alive = 0;
|
||||||
|
ctx->res.status = http_get_status(408);
|
||||||
|
|
||||||
|
respond(ctx);
|
||||||
|
request_complete(ctx);
|
||||||
|
tcp_close(ctx);
|
||||||
|
}
|
109
src/worker/tcp_acceptor.c
Normal file
109
src/worker/tcp_acceptor.c
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief TCP acceptor
|
||||||
|
* @file src/worker/tcp_acceptor.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-28
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "func.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "../lib/utils.h"
|
||||||
|
#include "../lib/geoip.h"
|
||||||
|
#include "../workers.h"
|
||||||
|
#include "../server.h"
|
||||||
|
#include "../lib/error.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
static int tcp_acceptor(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
void tcp_acceptor_func(client_ctx_t *ctx) {
|
||||||
|
if (tcp_acceptor(ctx) == 0) {
|
||||||
|
handle_request(ctx);
|
||||||
|
} else {
|
||||||
|
tcp_close(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tcp_acceptor(client_ctx_t *ctx) {
|
||||||
|
struct sockaddr_in6 server_addr;
|
||||||
|
|
||||||
|
memset(ctx->_c_addr, 0, sizeof(ctx->_c_addr));
|
||||||
|
memset(ctx->_s_addr, 0, sizeof(ctx->_s_addr));
|
||||||
|
inet_ntop(ctx->socket._addr.ipv6.sin6_family, &ctx->socket._addr.ipv6.sin6_addr, ctx->_c_addr, sizeof(ctx->_c_addr));
|
||||||
|
if (strstarts(ctx->_c_addr, "::ffff:")) {
|
||||||
|
ctx->socket.addr = ctx->_c_addr + 7;
|
||||||
|
} else {
|
||||||
|
ctx->socket.addr = ctx->_c_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
socklen_t len = sizeof(server_addr);
|
||||||
|
getsockname(ctx->socket.socket, (struct sockaddr *) &server_addr, &len);
|
||||||
|
inet_ntop(server_addr.sin6_family, (void *) &server_addr.sin6_addr, ctx->_s_addr, sizeof(ctx->_s_addr));
|
||||||
|
if (strstarts(ctx->_s_addr, "::ffff:")) {
|
||||||
|
ctx->socket.s_addr = ctx->_s_addr + 7;
|
||||||
|
} else {
|
||||||
|
ctx->socket.s_addr = ctx->_s_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(ctx->log_prefix, "[%s%4i%s]%s[%*s][%5i]%s", (int) ctx->socket.enc ? HTTPS_STR : HTTP_STR,
|
||||||
|
ntohs(server_addr.sin6_port), CLR_STR, /*color_table[0]*/ "", ADDRSTRLEN, ctx->socket.addr,
|
||||||
|
ntohs(ctx->socket._addr.ipv6.sin6_port), CLR_STR);
|
||||||
|
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->socket.s_addr, ctx->log_prefix);
|
||||||
|
|
||||||
|
sock *client = &ctx->socket;
|
||||||
|
ctx->cnx_s = clock_micros();
|
||||||
|
|
||||||
|
sock_reverse_lookup(&ctx->socket, ctx->host, sizeof(ctx->host));
|
||||||
|
|
||||||
|
ctx->cc[0] = 0;
|
||||||
|
geoip_lookup_country(&client->_addr.sock, ctx->cc);
|
||||||
|
|
||||||
|
info("Connection accepted from %s %s%s%s[%s]", ctx->socket.addr, ctx->host[0] != 0 ? "(" : "",
|
||||||
|
ctx->host[0] != 0 ? ctx->host : "", ctx->host[0] != 0 ? ") " : "",
|
||||||
|
ctx->cc[0] != 0 ? ctx->cc : "N/A");
|
||||||
|
|
||||||
|
if (sock_set_socket_timeout(client, SOCKET_TIMEOUT) != 0 || sock_set_timeout(client, CLIENT_TIMEOUT) != 0) {
|
||||||
|
error("Unable to set timeout for socket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client->enc) {
|
||||||
|
client->ssl = SSL_new(client->ctx);
|
||||||
|
SSL_set_fd(client->ssl, client->socket);
|
||||||
|
SSL_set_accept_state(client->ssl);
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
if ((ret = SSL_accept(client->ssl)) != 1) {
|
||||||
|
sock_error(client, ret);
|
||||||
|
info("Unable to perform handshake");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
client->ts_last = clock_micros();
|
||||||
|
client->ts_last_send = client->ts_last;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->req_num = 0;
|
||||||
|
ctx->s_keep_alive = 1;
|
||||||
|
ctx->c_keep_alive = 1;
|
||||||
|
ctx->chunks_transferred = 0;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tcp_close(client_ctx_t *ctx) {
|
||||||
|
errno = 0;
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->socket.s_addr, ctx->log_prefix);
|
||||||
|
|
||||||
|
sock_close(&ctx->socket);
|
||||||
|
|
||||||
|
ctx->cnx_e = clock_micros();
|
||||||
|
char buf[32];
|
||||||
|
info("Connection closed (%s)", format_duration(ctx->cnx_e - ctx->cnx_s, buf));
|
||||||
|
|
||||||
|
server_free_client(ctx);
|
||||||
|
}
|
113
src/worker/ws_frame_handler.c
Normal file
113
src/worker/ws_frame_handler.c
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief WebSocket frame handler
|
||||||
|
* @file src/worker/ws_frame_handler.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-30
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../defs.h"
|
||||||
|
#include "func.h"
|
||||||
|
#include "../logger.h"
|
||||||
|
#include "../lib/websocket.h"
|
||||||
|
#include "../workers.h"
|
||||||
|
#include "../lib/utils.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static int ws_frame_handler(ws_ctx_t *ctx);
|
||||||
|
|
||||||
|
void ws_frame_handler_func(ws_ctx_t *ctx) {
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
|
||||||
|
|
||||||
|
if (ws_frame_handler(ctx) == 0) {
|
||||||
|
if (ctx->client->ws_close == 3) {
|
||||||
|
ws_close(ctx);
|
||||||
|
} else {
|
||||||
|
ws_handle_frame(ctx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ws_close(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ws_handle_connection(client_ctx_t *ctx) {
|
||||||
|
info("Upgrading to WebSocket connection");
|
||||||
|
|
||||||
|
// copy proxy connection details
|
||||||
|
proxy_ctx_t *proxy = malloc(sizeof(proxy_ctx_t));
|
||||||
|
memcpy(proxy, ctx->proxy, sizeof(proxy_ctx_t));
|
||||||
|
|
||||||
|
// free proxy connection slot
|
||||||
|
ctx->proxy->initialized = 0;
|
||||||
|
proxy_unlock_ctx(ctx->proxy);
|
||||||
|
ctx->proxy = proxy;
|
||||||
|
|
||||||
|
sock_set_timeout(&ctx->socket, WS_TIMEOUT);
|
||||||
|
sock_set_timeout(&proxy->proxy, WS_TIMEOUT);
|
||||||
|
|
||||||
|
ws_ctx_t *a = malloc(sizeof(ws_ctx_t));
|
||||||
|
ws_ctx_t *b = malloc(sizeof(ws_ctx_t));
|
||||||
|
|
||||||
|
a->other = b, b->other = a;
|
||||||
|
a->client = ctx, b->client = ctx;
|
||||||
|
a->socket = &ctx->socket, b->socket = &proxy->proxy;
|
||||||
|
|
||||||
|
ws_handle_frame(a);
|
||||||
|
ws_handle_frame(b);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ws_frame_handler(ws_ctx_t *ctx) {
|
||||||
|
ws_frame frame;
|
||||||
|
char buf[CHUNK_SIZE];
|
||||||
|
|
||||||
|
sock *socket = ctx->socket;
|
||||||
|
sock *other = (ctx->socket == &ctx->client->socket) ? &ctx->client->proxy->proxy : &ctx->client->socket;
|
||||||
|
|
||||||
|
if (ws_recv_frame_header(socket, &frame) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
debug("WebSocket: Peer %s, Opcode=0x%X, Len=%li", (ctx->socket == &ctx->client->socket) ? "client" : "server", frame.opcode, frame.len);
|
||||||
|
|
||||||
|
if (frame.opcode == 0x8) {
|
||||||
|
ctx->client->ws_close |= (ctx->socket == &ctx->client->socket) ? 1 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ws_send_frame_header(other, &frame) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (frame.len > 0) {
|
||||||
|
long ret = sock_splice(other, socket, buf, sizeof(buf), frame.len);
|
||||||
|
if (ret < 0) {
|
||||||
|
error("Unable to forward data in WebSocket");
|
||||||
|
return -1;
|
||||||
|
} else if (ret != frame.len) {
|
||||||
|
error("Unable to forward correct number of bytes in WebSocket");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ws_close(ws_ctx_t *ctx) {
|
||||||
|
ws_ctx_t *other = ctx->other;
|
||||||
|
if (other) {
|
||||||
|
proxy_ctx_t *proxy = ctx->client->proxy;
|
||||||
|
other->other = NULL;
|
||||||
|
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
|
||||||
|
|
||||||
|
proxy->cnx_e = clock_micros();
|
||||||
|
char buf[32];
|
||||||
|
info("Closing WebSocket connection (%s)", format_duration(proxy->cnx_e - proxy->cnx_s, buf));
|
||||||
|
|
||||||
|
sock_close(&proxy->proxy);
|
||||||
|
free(ctx->client->proxy);
|
||||||
|
tcp_close(ctx->client);
|
||||||
|
}
|
||||||
|
free(ctx);
|
||||||
|
errno = 0;
|
||||||
|
}
|
129
src/workers.c
Normal file
129
src/workers.c
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Worker interface
|
||||||
|
* @file src/workers.c
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-29
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "workers.h"
|
||||||
|
#include "lib/mpmc.h"
|
||||||
|
|
||||||
|
#include "worker/func.h"
|
||||||
|
#include "async.h"
|
||||||
|
|
||||||
|
static mpmc_t tcp_acceptor_ctx, request_handler_ctx, local_handler_ctx, fastcgi_handler_ctx, proxy_handler_ctx,
|
||||||
|
proxy_peer_handler_ctx, ws_frame_handler_ctx, chunk_handler_ctx, fastcgi_frame_handler_ctx;
|
||||||
|
|
||||||
|
int workers_init(void) {
|
||||||
|
mpmc_init(&tcp_acceptor_ctx, 8, 64, (void (*)(void *)) tcp_acceptor_func, "tcp");
|
||||||
|
mpmc_init(&request_handler_ctx, 8, 64, (void (*)(void *)) request_handler_func, "req");
|
||||||
|
mpmc_init(&local_handler_ctx, 8, 64, (void (*)(void *)) local_handler_func, "local");
|
||||||
|
mpmc_init(&fastcgi_handler_ctx, 8, 64, (void (*)(void *)) fastcgi_handler_func, "fcgi");
|
||||||
|
mpmc_init(&proxy_handler_ctx, 8, 64, (void (*)(void *)) proxy_handler_func, "proxy");
|
||||||
|
mpmc_init(&proxy_peer_handler_ctx, 1, 8, (void (*)(void *)) proxy_peer_handler_func, "prxy_p");
|
||||||
|
mpmc_init(&ws_frame_handler_ctx, 8, 64, (void (*)(void *)) ws_frame_handler_func, "ws");
|
||||||
|
mpmc_init(&chunk_handler_ctx, 8, 64, (void (*)(void *)) chunk_handler_func, "chunk");
|
||||||
|
mpmc_init(&fastcgi_frame_handler_ctx, 8, 64, (void (*)(void *)) fastcgi_frame_handler_func, "fcgi_f");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void workers_stop(void) {
|
||||||
|
mpmc_stop(&tcp_acceptor_ctx);
|
||||||
|
mpmc_stop(&local_handler_ctx);
|
||||||
|
mpmc_stop(&fastcgi_handler_ctx);
|
||||||
|
mpmc_stop(&proxy_handler_ctx);
|
||||||
|
mpmc_stop(&proxy_peer_handler_ctx);
|
||||||
|
mpmc_stop(&request_handler_ctx);
|
||||||
|
mpmc_stop(&ws_frame_handler_ctx);
|
||||||
|
mpmc_stop(&chunk_handler_ctx);
|
||||||
|
mpmc_stop(&fastcgi_frame_handler_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void workers_destroy(void) {
|
||||||
|
mpmc_destroy(&tcp_acceptor_ctx);
|
||||||
|
mpmc_destroy(&local_handler_ctx);
|
||||||
|
mpmc_destroy(&fastcgi_handler_ctx);
|
||||||
|
mpmc_destroy(&proxy_handler_ctx);
|
||||||
|
mpmc_destroy(&proxy_peer_handler_ctx);
|
||||||
|
mpmc_destroy(&request_handler_ctx);
|
||||||
|
mpmc_destroy(&ws_frame_handler_ctx);
|
||||||
|
mpmc_destroy(&chunk_handler_ctx);
|
||||||
|
mpmc_destroy(&fastcgi_frame_handler_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int tcp_accept(client_ctx_t *ctx) {
|
||||||
|
return mpmc_queue(&tcp_acceptor_ctx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_request_cb(client_ctx_t *ctx) {
|
||||||
|
return mpmc_queue(&request_handler_ctx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int handle_request(client_ctx_t *ctx) {
|
||||||
|
if (ctx->c_keep_alive && ctx->s_keep_alive) {
|
||||||
|
return async(&ctx->socket, ASYNC_WAIT_READ, 0, ctx,
|
||||||
|
(void (*)(void *)) handle_request_cb,
|
||||||
|
(void (*)(void *)) timeout_request,
|
||||||
|
(void (*)(void *)) tcp_close);
|
||||||
|
} else {
|
||||||
|
tcp_close(ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int local_handle(client_ctx_t *ctx) {
|
||||||
|
return mpmc_queue(&local_handler_ctx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fastcgi_handle(client_ctx_t *ctx) {
|
||||||
|
return mpmc_queue(&fastcgi_handler_ctx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fastcgi_handle_frame_cb(fastcgi_ctx_t *ctx) {
|
||||||
|
return mpmc_queue(&fastcgi_frame_handler_ctx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fastcgi_handle_frame(fastcgi_ctx_t *ctx) {
|
||||||
|
return async(&ctx->cnx.socket, ASYNC_WAIT_READ, 0, ctx,
|
||||||
|
(void (*)(void *)) fastcgi_handle_frame_cb,
|
||||||
|
(void (*)(void *)) fastcgi_close,
|
||||||
|
(void (*)(void *)) fastcgi_close);
|
||||||
|
}
|
||||||
|
|
||||||
|
int proxy_handle(client_ctx_t *ctx) {
|
||||||
|
return mpmc_queue(&proxy_handler_ctx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int proxy_peer_handle_cb(proxy_ctx_t *ctx) {
|
||||||
|
return mpmc_queue(&proxy_peer_handler_ctx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int proxy_peer_handle(proxy_ctx_t *ctx) {
|
||||||
|
return async(&ctx->proxy, ASYNC_WAIT_READ, ASYNC_IGNORE_PENDING, ctx,
|
||||||
|
(void (*)(void *)) proxy_peer_handle_cb,
|
||||||
|
(void (*)(void *)) proxy_peer_handle_cb,
|
||||||
|
(void (*)(void *)) proxy_peer_handle_cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ws_handle_frame_cb(ws_ctx_t *ctx) {
|
||||||
|
return mpmc_queue(&ws_frame_handler_ctx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ws_handle_frame(ws_ctx_t *ctx) {
|
||||||
|
return async(ctx->socket, ASYNC_WAIT_READ, 0, ctx,
|
||||||
|
(void (*)(void *)) ws_handle_frame_cb,
|
||||||
|
(void (*)(void *)) ws_close,
|
||||||
|
(void (*)(void *)) ws_close);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_chunk_cb(chunk_ctx_t *ctx) {
|
||||||
|
return mpmc_queue(&chunk_handler_ctx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int handle_chunk(chunk_ctx_t *ctx) {
|
||||||
|
return async(ctx->socket, ASYNC_WAIT_READ, 0, ctx,
|
||||||
|
(void (*)(void *)) handle_chunk_cb,
|
||||||
|
(void (*)(void *)) ctx->err_cb,
|
||||||
|
(void (*)(void *)) ctx->err_cb);
|
||||||
|
}
|
38
src/workers.h
Normal file
38
src/workers.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* sesimos - secure, simple, modern web server
|
||||||
|
* @brief Worker interface (header file)
|
||||||
|
* @file src/workers.h
|
||||||
|
* @author Lorenz Stechauner
|
||||||
|
* @date 2022-12-29
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SESIMOS_WORKERS_H
|
||||||
|
#define SESIMOS_WORKERS_H
|
||||||
|
|
||||||
|
#include "worker/func.h"
|
||||||
|
|
||||||
|
int workers_init(void);
|
||||||
|
|
||||||
|
void workers_stop(void);
|
||||||
|
|
||||||
|
void workers_destroy(void);
|
||||||
|
|
||||||
|
int tcp_accept(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
int handle_request(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
int local_handle(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
int fastcgi_handle(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
int fastcgi_handle_frame(fastcgi_ctx_t *ctx);
|
||||||
|
|
||||||
|
int proxy_handle(client_ctx_t *ctx);
|
||||||
|
|
||||||
|
int proxy_peer_handle(proxy_ctx_t *ctx);
|
||||||
|
|
||||||
|
int ws_handle_frame(ws_ctx_t *ctx);
|
||||||
|
|
||||||
|
int handle_chunk(chunk_ctx_t *ctx);
|
||||||
|
|
||||||
|
#endif //SESIMOS_WORKERS_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;
|
||||||
|
}
|
92
test/test_list.c
Normal file
92
test/test_list.c
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
|
||||||
|
#include <criterion/criterion.h>
|
||||||
|
#include <criterion/parameterized.h>
|
||||||
|
|
||||||
|
#include "../src/lib/list.h"
|
||||||
|
|
||||||
|
Test(list, simple) {
|
||||||
|
int v;
|
||||||
|
int *list = list_create(sizeof(int), 16);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 0);
|
||||||
|
|
||||||
|
v = 1;
|
||||||
|
list = list_append(list, &v);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 1);
|
||||||
|
cr_assert_eq(list[0], 1);
|
||||||
|
|
||||||
|
v = 3;
|
||||||
|
list = list_append(list, &v);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 2);
|
||||||
|
cr_assert_eq(list[0], 1);
|
||||||
|
cr_assert_eq(list[1], 3);
|
||||||
|
|
||||||
|
v = 2;
|
||||||
|
list = list_insert(list, &v, 1);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 3);
|
||||||
|
cr_assert_eq(list[0], 1);
|
||||||
|
cr_assert_eq(list[1], 2);
|
||||||
|
cr_assert_eq(list[2], 3);
|
||||||
|
|
||||||
|
list = list_remove(list, 0);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 2);
|
||||||
|
cr_assert_eq(list[0], 2);
|
||||||
|
cr_assert_eq(list[1], 3);
|
||||||
|
|
||||||
|
list = list_remove(list, 1);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 1);
|
||||||
|
cr_assert_eq(list[0], 2);
|
||||||
|
|
||||||
|
list = list_remove(list, 0);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 0);
|
||||||
|
|
||||||
|
list_free(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
Test(list, resize) {
|
||||||
|
int v;
|
||||||
|
int *list = list_create(sizeof(int), 4);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4096; i++) {
|
||||||
|
v = 9182 - i;
|
||||||
|
list = list_append(list, &v);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), i + 1);
|
||||||
|
for (int j = 0; j <= i; j++) {
|
||||||
|
cr_assert_eq(list[j], 9182 - j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4096; i++) {
|
||||||
|
list = list_remove(list, -1);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 4096 - i - 1);
|
||||||
|
for (int j = 0; j < 4096 - i; j++) {
|
||||||
|
cr_assert_eq(list[j], 9182 - j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4096; i++) {
|
||||||
|
v = 47391 - i;
|
||||||
|
list = list_append(list, &v);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), i + 1);
|
||||||
|
for (int j = 0; j <= i; j++) {
|
||||||
|
cr_assert_eq(list[j], 47391 - j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list = list_clear(list);
|
||||||
|
cr_assert_not_null(list);
|
||||||
|
cr_assert_eq(list_size(list), 0);
|
||||||
|
|
||||||
|
list_free(list);
|
||||||
|
}
|
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:00 min"},
|
||||||
|
{1000000 * 60 * 30L - 30000000, "29:30 min"},
|
||||||
|
{1000000 * 60 * 60L, "60:00 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