268 Commits
v3.0 ... v4.5

Author SHA1 Message Date
cd3bc9aa90 Implement * notation for hosts 2022-01-13 18:07:24 +01:00
6ab65abec9 Fix parsing error 2022-01-13 17:53:00 +01:00
7d6fa4682d Update version to 4.5 2022-01-13 15:39:04 +01:00
6f4cbb6e24 Update readme 2021-12-27 18:34:28 +01:00
174865b71c Fix malloc/alloca 2021-12-27 18:25:28 +01:00
4a002d6c31 Fix config file parsing 2021-12-27 17:17:17 +01:00
2a3f74e825 Increase stat_str size to 8 2021-12-27 17:16:42 +01:00
9eaa644fa1 Add SNI 2021-12-27 16:16:41 +01:00
ce91fecc80 Add image/gif to compressible mime types 2021-12-27 13:08:51 +01:00
15c160a60a Add image/vnd.microsoft.iconbinary 2021-12-06 23:33:40 +01:00
fd5e2302d4 Fix conf seg fault 2021-11-25 23:14:42 +01:00
8c13eecc51 Shorten margin statement in error doc 2021-11-25 10:08:09 +01:00
ae751925fa Fix FastCGI error code 502 -> 500 2021-11-24 23:27:27 +01:00
a1fee3d2ec Fix FastCGI compression 2021-11-21 16:31:26 +01:00
8721f07d00 Add <pre> formatting 2021-11-20 18:16:33 +01:00
81ad3fea3c Fix cache mkdir error handling 2021-11-20 16:46:47 +01:00
877ee12351 Fix msg_content 2021-11-20 14:27:19 +01:00
dfbe3bbb95 Updated warning document icon 2021-11-20 14:16:35 +01:00
6c13922f2f Add redirect document 2021-11-20 14:10:00 +01:00
171bca55fa Fixed reverse proxy timeout 2021-11-12 15:16:09 +01:00
fe0cdc9b1a rev_proxy_void working 2021-11-07 20:46:37 +01:00
930d4db2a2 EINPROGRESS also leads to 504 in rev proxy 2021-11-07 20:15:18 +01:00
b37757fc73 Add reverse proxy error documents 2021-11-07 19:48:09 +01:00
61c645eca8 Fix reverse proxy timeout 2021-10-23 16:31:54 +02:00
2fd71116ec Fix big FastCGI frames 2021-09-29 18:16:15 +02:00
495a3a6aaf Remove webroot from error documents 2021-09-29 17:45:07 +02:00
69dfc562af using return value from fastcgi_send and rev_proxy_send 2021-09-29 17:01:33 +02:00
a2383825ed Replace Script with FastCGI app 2021-09-29 16:58:38 +02:00
55f2318b57 Always use CLR_STR on end of PHP messages 2021-09-17 22:43:16 +02:00
eeb0ce7035 Changed config and cache directory from necronda-server to necronda/server 2021-09-17 22:43:08 +02:00
b5749ae991 Adjust logging width for IPv6 2021-09-17 22:43:05 +02:00
9adb4791df Added basic support for Necronda backend 2021-09-17 22:43:02 +02:00
eba916fad4 Fixed Error string in rev_proxy.c 2021-09-17 22:42:51 +02:00
ac2d17f23a Updated Makefile 2021-09-17 22:33:46 +02:00
433cc7861f Reverse proxy IPv6 and IPv4 2021-09-12 19:25:57 +02:00
52ea670d36 Better reverse proxy error messages 2021-09-12 19:24:26 +02:00
b4b5d77985 Updated PHP-FPM to 7.4 2021-09-12 18:58:05 +02:00
aebc731c4e fixed error status codes 2021-08-03 23:09:57 +02:00
4994b4375b Replace strncpy with snprintf where necessary 2021-05-11 22:10:02 +02:00
43c512dc5a Add gcc opimization 2021-05-10 18:12:08 +02:00
3f5eee236d Fix mime javascript types 2021-05-10 18:05:30 +02:00
0b157bcb74 Add Access-Control-Allow-Origin header for /.well-known/ directory 2021-05-09 12:25:39 +02:00
30b163c6fa Small fixes 2021-05-07 19:57:40 +02:00
cf2c0de697 Added Vary header 2021-05-06 20:55:44 +02:00
7aa47cac61 Fix includes 2021-05-06 20:46:17 +02:00
456deeae20 Bump version to 4.4 2021-05-05 21:58:14 +02:00
b08481818c Updated readme 2021-05-05 21:57:31 +02:00
49ad349775 Fix Content-Encoding header for FastCGI 2021-05-05 21:45:10 +02:00
2b823cabd6 ETag Content-Encoding bugfix 2021-05-05 21:37:36 +02:00
ecd4f16afe Transfer-Encoding implemented 2021-05-05 21:30:44 +02:00
45c5f20345 Preparing for transfer encoding compression 2021-05-05 20:40:23 +02:00
c42f27e961 Fix compression 2021-05-05 19:00:01 +02:00
de8ab406f6 Main executable cleanup 2021-05-05 18:46:24 +02:00
22d50ed4bd compression mode init fix 2021-05-05 18:22:50 +02:00
de44f4a3fe Cache dynamic mem 2021-05-05 18:17:57 +02:00
cc29250d76 FastCGI cleanup 2021-05-05 18:15:20 +02:00
c2f8f4c962 Unified compression interfaces 2021-05-05 18:07:12 +02:00
ff708230bd Fix FastCGI encoding (really really) 2021-05-04 23:14:35 +02:00
52ea541833 Fix FastCGI encoding (really) 2021-05-04 23:07:09 +02:00
f4bd426f3c Fix FastCGI compression 2021-05-04 22:57:44 +02:00
ffbbcc6490 Fix makefile 2021-05-04 22:47:37 +02:00
80986325ce Added brotli compression 2021-05-04 22:32:21 +02:00
10d405e745 Cache PID refactor 2021-05-03 21:04:52 +02:00
28f163f97a Added DEBIAN_OPTS 2021-05-03 20:48:16 +02:00
a8914aa981 Fixed tabs in docs/example.conf 2021-05-03 20:41:19 +02:00
2ada22481d Added docs/ 2021-05-03 20:40:17 +02:00
17b25a3596 Added description 2021-05-03 20:39:01 +02:00
d130474989 Refactor for shared library use 2021-05-03 20:10:23 +02:00
8dea0cd3fc Fixed if indents 2021-05-02 18:12:16 +02:00
0406cad0d8 Added mime_is_compressible 2021-05-02 18:08:57 +02:00
21b7ab585a Allow header only on 405 response 2021-05-02 16:05:02 +02:00
7587e15749 Added support for TRACE method 2021-04-29 22:21:53 +02:00
1b44752f91 Changed icon order in default document 2021-03-19 18:13:00 +01:00
5eeb9ef3c1 Http document alternate icon 2021-03-19 18:10:18 +01:00
dd15b9d906 Http and proxy refactor 2021-03-19 17:55:52 +01:00
12922a0661 Updated version string to 4.3 2021-03-18 20:37:07 +01:00
c0799101b1 Removed TODO escape IPv6 addresses 2021-03-18 20:32:09 +01:00
c1d076db04 Refactor code to avoid warnings 2021-03-18 20:27:45 +01:00
10464f3f30 Added Reverse Proxy Header Support 2021-03-18 19:56:50 +01:00
63781472fa Bugfix for config post processing 2021-03-16 22:07:19 +01:00
81931d287d Cache debug messages improvements 2021-03-16 21:57:33 +01:00
531ddb4880 Caching debug messages 2021-03-16 21:54:49 +01:00
e0d8ab31d5 Improved cache idle performance 2021-03-16 20:22:36 +01:00
3a36d54e9d Added standard favicon /favicon.ico to error document 2021-03-16 19:27:03 +01:00
33d9aa3a5d Date header field fix 2021-03-16 19:15:26 +01:00
f4d30206b0 Fix hidden paths 2021-03-11 22:09:32 +01:00
5b094ba98d Hidden paths -> 403 2021-03-11 22:05:38 +01:00
f60cdc8228 Added TODO for 3xx Redirects 2021-03-11 20:30:03 +01:00
ab1c4d6fd4 Changed 203 -> 404 2021-03-11 20:25:07 +01:00
26d54e9968 Added error documents and 203 response if webroot does not exist 2021-03-11 20:19:14 +01:00
b6c7d8f58e URL UTF-8 Bugfix 2021-01-24 21:20:48 +01:00
53fcceeafb Add FIXME 2021-01-17 18:10:50 +01:00
96567909db FastCGI error bugfix 2021-01-17 18:06:03 +01:00
4b3c067a75 Fix includes 2021-01-17 17:56:16 +01:00
77b80ca67b Bugfix splice 2021-01-13 21:21:24 +01:00
70e76d8783 Error doc update 2 2021-01-10 12:22:48 +01:00
6b1bc54cf3 Error doc update 2021-01-10 12:18:44 +01:00
e1edb48a3c Redirect to https even on reverse proxy 2021-01-09 12:57:24 +01:00
dc5d1bebcc Add TODO for access/error log file 2021-01-09 12:39:55 +01:00
7b562c4b78 Reverse proxy printing debug message always 2021-01-09 11:50:44 +01:00
413ab2aa5b Reverse proxy bugfix 2021-01-08 21:43:39 +01:00
68315b9765 Update version string to 4.2 2021-01-08 20:16:25 +01:00
820a232a96 Sending X-Forwarded-For 2021-01-08 19:59:18 +01:00
b676388018 Bugfix for proxy 2021-01-08 19:45:41 +01:00
debac11f90 Trying proxy more often 2021-01-08 17:52:35 +01:00
9297788cdf Added TODOs 2021-01-07 22:51:02 +01:00
e61d16fb41 Hopefully bugfix 2021-01-07 22:35:49 +01:00
ebf3258092 Bugfix? 2021-01-07 22:26:01 +01:00
59d0c485fd Add version in welcome string 2021-01-07 22:13:15 +01:00
c90df2397f Removed debug message 2021-01-07 22:12:27 +01:00
c4eb6709cf Changed buffer mode for stdout 2021-01-07 22:11:27 +01:00
a73cbac7a1 Bugfix for sending files 2021-01-07 22:03:11 +01:00
95946b1666 Reverse proxy working 2021-01-07 21:58:45 +01:00
785ab31890 Added custom status 2021-01-07 18:28:51 +01:00
5481d314c9 Error document update 2 2021-01-07 18:00:32 +01:00
5986f39802 Error document update 2021-01-07 17:58:08 +01:00
a972340209 Add custom status todos 2021-01-07 17:53:55 +01:00
e6dd8b84f9 Error document style update 2021-01-07 17:53:38 +01:00
c3942b3382 Bugfix 2 2021-01-06 23:38:28 +01:00
fde2b7aabb Bugfix reverse proxy 2021-01-06 23:34:18 +01:00
d0be587a36 Bugfix 2021-01-06 23:29:45 +01:00
351568004f Add reverse proxy 2021-01-06 23:24:47 +01:00
be757add02 Bugfix URI 2021-01-06 19:19:06 +01:00
037c150868 enhanced reverse proxy message 2021-01-06 19:17:25 +01:00
576d0a0dba Dig error as print 2021-01-06 19:15:20 +01:00
ee6f9115a8 client refactor 2021-01-06 19:10:06 +01:00
6bbf79245b Report status on not local mode 2021-01-06 18:45:38 +01:00
080d729f31 Add config file 2021-01-06 15:49:52 +01:00
2f6ba62715 Small http err doc fix 2021-01-05 23:14:36 +01:00
4d4d94fc81 Add config.c and config.h 2021-01-05 23:01:38 +01:00
55028bd9cd HTTP error document update 2021-01-05 23:01:04 +01:00
8b0eb45854 Handling IPv6 addresses as Host field 2021-01-05 19:45:00 +01:00
df49236130 Preventing double slashes 2021-01-04 20:56:05 +01:00
c7ca62a7e9 Add TODO for double slashes 2021-01-03 22:18:30 +01:00
5d01f7e219 Error document style update 2021-01-03 16:21:42 +01:00
ceb0167742 Small Code Refactor 2021-01-01 22:40:01 +01:00
8fa9f2528f Bugfix dir mode 3 2021-01-01 22:36:31 +01:00
2e16fdee96 Bugfix dir mode 2 2021-01-01 22:32:59 +01:00
f9b7e83ac8 Bugfix dir mode 2021-01-01 22:28:53 +01:00
9b9ddbb913 Add get_dir_mode 2021-01-01 22:17:30 +01:00
566ac0ca93 Update of Default error doc 2021-01-01 21:44:34 +01:00
bcdf36527f Dir mode change to info 2020-12-29 23:32:46 +01:00
75ef4110c8 Using right country code 2020-12-29 17:13:47 +01:00
f1064692a8 GeoIP hardcoded ip bugfix 2020-12-29 16:36:49 +01:00
f0ec64b629 GeoIP Info implemented 2020-12-29 16:26:53 +01:00
0db781e823 Bugifx double free client.buf 2020-12-29 11:50:23 +01:00
798c41f1c8 Bugfix for POST and PUT method 2020-12-29 11:48:56 +01:00
bc4a764bd5 Update .gitignore 2020-12-29 11:23:08 +01:00
105e11d31d Add Todo for Reverse Proxy 2020-12-29 11:17:51 +01:00
15530b642a Non-existent webroot bugfix 2020-12-29 11:05:38 +01:00
e856f3f091 Using strcmp more often 2020-12-29 10:52:48 +01:00
b04c787df4 Method parsing bugfix 2020-12-29 10:46:44 +01:00
ee7d1e086b Get hostnames with reverse DNS 2020-12-28 23:32:37 +01:00
cf8862100a Stdout unbuffered 2020-12-28 22:56:03 +01:00
4cc7b1c18a Bugfix for charset in Content-Type 2020-12-28 21:04:39 +01:00
6fa62aeb4d Range Responses 2020-12-28 19:11:56 +01:00
3663a6064b Added TODO for Reverse DNS 2020-12-28 16:40:48 +01:00
ed233f8b5e Bugifx for closed connections 2020-12-28 16:07:39 +01:00
6e8875d528 Bugfix query 2020-12-28 15:57:23 +01:00
640f3abc1f Flush after print 2020-12-28 15:22:02 +01:00
7d8f065ec9 Add compile-debian target 2020-12-28 14:57:09 +01:00
dd44ffee95 Update Makefile 2020-12-28 14:53:03 +01:00
c0962b90a6 Bugfix create /var/necronda-server/ 2020-12-28 14:01:16 +01:00
161952441d Shared mem bugfix 3 2020-12-28 13:55:38 +01:00
09b2118863 Shared mem bugfix 2 2020-12-28 13:53:18 +01:00
b64828b01a Shared mem debug 2020-12-28 13:48:37 +01:00
8a640acd24 Remove shared mem on error 2020-12-28 13:37:30 +01:00
609fd34ab7 Define value only if not defined 2020-12-28 13:32:24 +01:00
7cb4f40d22 Process FastCGI header fields 2020-12-28 13:18:38 +01:00
156c7d6621 normal printing in fastcgi.c 2020-12-28 09:23:00 +01:00
f39e1a6cb6 Code Cleanup 2020-12-27 23:58:59 +01:00
c708633197 FastCGI stdin implementation 2020-12-27 23:57:14 +01:00
b7f574e2ed Last-Modified for php scripts 2020-12-27 22:42:01 +01:00
be4475c336 Bugfix for FastCGI compression 2020-12-27 20:27:25 +01:00
c6fbf25192 Bugfix for FastCGI compression 2020-12-27 20:15:49 +01:00
b6ecb68b1a FastCGI compression 2020-12-27 20:04:15 +01:00
492e6a94cb Remove debugging file messages 2020-12-27 19:39:00 +01:00
1733ad3c1d Always lowering both http field names in search 2020-12-27 19:36:48 +01:00
885ec2226f FastCGI working buggy 2020-12-27 18:33:50 +01:00
1429961e44 Add fastcgi 2020-12-26 21:13:18 +01:00
d88c70c978 Compression of xml files 2020-12-24 23:53:14 +01:00
bfe08623c2 Bugfix for receive in http.c 2020-12-24 23:52:58 +01:00
2ed8451db1 File compression working 2020-12-23 21:19:40 +01:00
495381ab3f Possibility to accept If-Modified-Since field 2020-12-23 18:57:37 +01:00
9d1594dc01 Using SEEK_END and SEEK_SET 2020-12-23 18:33:34 +01:00
463568182d Check for illegal characters in http header 2020-12-23 18:29:38 +01:00
fbd9a2667f Bugfix cache file 2020-12-23 18:21:03 +01:00
d39f69d665 Support for compressed files in client.c 2020-12-22 22:40:08 +01:00
c6f364d6e8 Add ETag calculation 2020-12-22 22:31:58 +01:00
8cccf48826 Add geoip TODOs 2020-12-22 20:32:00 +01:00
7e2c9bea6d Fix --geoip usage 2020-12-22 20:23:01 +01:00
562cfb04e6 Remove parent stds and added --geoip 2020-12-22 20:22:07 +01:00
14bcc47db2 Handle child process exit codes 2020-12-22 20:01:59 +01:00
815fa32d14 Fix for free() 2020-12-22 18:43:11 +01:00
31fa9195a1 Bugfix 2020-12-20 17:01:38 +01:00
caa09c3f05 Bugfix 2020-12-20 16:55:18 +01:00
00abf345b9 Added basic file transfer 2020-12-20 16:47:40 +01:00
f0338209f5 Added magic file type detection 2020-12-19 22:09:03 +01:00
f6ac55adaf Added cache.c/.h 2020-12-19 17:38:56 +01:00
5e9c98d67f Added HEAD method 2020-12-19 17:13:03 +01:00
ba57509461 Bugfix for redirection 2020-12-19 13:17:30 +01:00
4e079e094f Bugfix location redirect 2020-12-19 13:06:51 +01:00
a4901f4c23 Changed port numbers 2020-12-19 13:03:32 +01:00
f5a42b8c28 Redirection on insecure connection 2020-12-19 13:03:03 +01:00
4f33e1d809 Changed abort goto 2020-12-18 23:08:28 +01:00
c7902ea5b5 Added dir mode in client 2020-12-18 22:39:18 +01:00
50f5438654 Proper url encoding for location field 2020-12-18 22:20:32 +01:00
a5c1516468 Added uri parsing 2020-12-18 22:06:41 +01:00
db224c7452 Renamed http not strict 2020-12-16 18:44:43 +01:00
812d5ab384 Renamed http strict and not strict 2020-12-16 18:43:53 +01:00
1be4929c6c Refacotred graceful terminating 2020-12-16 18:38:23 +01:00
80ed6dea3e Added http header strict and not strict mode 2020-12-16 18:35:11 +01:00
14b4f71af7 Refactored html error document 2020-12-15 22:13:57 +01:00
a6789a7f3e Added favicon 2020-12-15 21:55:02 +01:00
7c2e3f2f9b Changed sleep time from 1 sec to 50 ms 2020-12-15 19:59:20 +01:00
93973c6e77 Refactored http default error document 2020-12-15 19:53:21 +01:00
97b04173ee Fixed typo in src/http.h 2020-12-13 21:00:18 +01:00
8002ab3ed7 Moved http.c and http.h from src/net to src 2020-12-13 20:55:27 +01:00
35b924eea5 Bugfix of error document 2020-12-13 20:33:46 +01:00
3dbe356b87 Bugfix of error document 2020-12-13 19:57:15 +01:00
84c3e87a08 Bugfix of error document 2020-12-13 19:29:55 +01:00
51531ff78e Updated http error document 2020-12-13 19:08:15 +01:00
e0ecc35d0c Refactored http header parsing error handling 2020-12-13 19:02:06 +01:00
c2de0ce439 Added http error messages 2020-12-13 18:50:08 +01:00
2bcfa97e1b Added url_encode and url_decode 2020-12-13 16:05:55 +01:00
7c5a57b3d8 Renamed webroot to webroot_base 2020-12-13 15:34:39 +01:00
99328996fa Using const char in http 2020-12-13 15:33:46 +01:00
8249b7ed89 Used strcpy 2020-12-13 15:17:56 +01:00
db94388e69 Added struct uri 2020-12-13 14:12:53 +01:00
a5919807a3 Restructured includes 2020-12-13 14:12:53 +01:00
cc53b43c8a Added uri.h and uri.c 2020-12-13 14:12:53 +01:00
1ea0c95ebb Improved argument parsing 2020-12-13 13:40:53 +01:00
9f3d8cc0c0 Added argument parsing 2020-12-13 13:05:49 +01:00
8b9b32b9af Refactored client.c 2020-12-12 22:09:42 +01:00
81b5a569db Fix in http_receive_request 2020-12-12 22:02:49 +01:00
dcdf91d5bf Refactored client.c 2020-12-12 21:47:15 +01:00
2d6277dc23 Implemented http responses 2020-12-12 21:41:21 +01:00
8335a20e32 Fixed keep alive 2020-12-12 17:53:19 +01:00
8ac29b3815 Fixing format_duration 2020-12-12 17:14:59 +01:00
687d918677 Parsing HTTP header 2020-12-12 17:13:36 +01:00
b4a4f12e9c Changed termination message 2020-12-12 16:06:04 +01:00
38448401d0 Updated format_duration 2020-12-12 15:31:26 +01:00
352742d26c Renamed timeout to client_timeout 2020-12-12 11:26:13 +01:00
e71786716e Updated sigterm handling 2020-12-11 23:51:23 +01:00
bce6f64c6e Refactored format_duration 2020-12-11 23:41:06 +01:00
43bf946cce Added Keep-Alive header 2020-12-11 23:19:36 +01:00
49f1a5f429 Added Client timeout 2020-12-11 23:16:30 +01:00
3ba1848e40 Updated makefile 2020-12-11 23:07:12 +01:00
f7fc4fa801 Http keep alive 2020-12-11 23:03:54 +01:00
e116b940b2 Set timeout for client 2020-12-11 20:34:12 +01:00
37f3e4eae1 client request handler 2020-12-11 20:28:48 +01:00
bbbde82a46 Removed BIOs 2020-12-11 17:08:26 +01:00
77852d9626 Added ready status message 2020-12-11 17:01:53 +01:00
13fb362d12 Implemented BIOs 2020-12-11 16:38:17 +01:00
0bf8c0ccc3 Implemented basic SSL 2020-12-11 16:25:26 +01:00
f98ffc7077 Child process management 2020-12-10 22:45:45 +01:00
e6760cf665 Process management 2020-12-10 22:24:06 +01:00
ae430c340c Updated Makefile 2020-12-10 21:46:30 +01:00
42b8aee4bd Simple echo client 2020-12-10 20:55:33 +01:00
24ce5d8cd6 Implemented Hello World 2020-12-09 22:45:35 +01:00
00382a71e2 Renamed functions in client 2020-12-09 20:04:27 +01:00
feaadf31bf Make run.sh executable 2020-12-09 19:37:28 +01:00
a16c66c7e7 Update run.sh 2020-12-09 19:36:17 +01:00
7500476dc5 Added readme 2020-12-09 19:30:15 +01:00
a417efdece Updated Makefile 2020-12-09 19:23:55 +01:00
d104a43d1b Refactored for version 4 2020-12-09 19:21:57 +01:00
e68e0239c3 Passing Country Code to PHP 2020-12-05 16:48:38 +01:00
f148578154 Adding micros start to php-cgi env 2020-12-05 12:40:49 +01:00
53 changed files with 5203 additions and 3115 deletions

6
.gitignore vendored
View File

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

View File

@ -1,22 +1,26 @@
.DEFAULT_GOAL := install
CC=gcc
CFLAGS=-std=gnu11 -Wall
LIBS=-lssl -lcrypto -lmagic -lz -lmaxminddb -lbrotlienc
DEBIAN_OPTS=-D CACHE_MAGIC_FILE="\"/usr/share/file/magic.mgc\"" -D PHP_FPM_SOCKET="\"/var/run/php/php7.4-fpm.sock\""
packages:
@echo "Installing packages..."
sudo apt-get install g++ libmagic-dev libssl-dev php-cgi
sudo apt install gcc php-fpm libmagic-dev libssl-dev libmaxminddb-dev
@echo "Finished downloading!"
update:
@echo "Updating imported git repos..."
cd CppNet
git pull
cd ..
@echo "Finished updating!"
permit:
sudo setcap 'cap_net_bind_service=+ep' "$(shell pwd)/bin/necronda-server"
compile:
@echo "Compiling..."
@mkdir -p bin
g++ src/necronda-server.cpp -o bin/necronda-server -std=c++17 -fPIC -pthread -lz -lmagic -lssl -ldl -lcrypto
@echo "Finished compiling!"
$(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
install: | packages update compile
@echo "Finished!"
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

47
README.md Normal file
View File

@ -0,0 +1,47 @@
Necronda web server
===================
## Features
* Full IPv4 and IPv6 support
* TLS Server Name Inspection (SNI)
* Serving local files via HTTP and HTTPS
* File compression ([gzip](https://www.gzip.org/), [Brotli](https://www.brotli.org/)) and disk cache for compressed files
* Reverse proxy for other HTTP and HTTPS servers
* FastCGI support (e.g. [PHP-FPM](https://php-fpm.org/))
* Support for [MaxMind's GeoIP Database](https://www.maxmind.com/en/geoip2-services-and-databases)
* Optional DNS reverse lookup for connecting hosts
* Automatic URL rewrite (e.g. `/index.html` -> `/`, `/test.php` -> `/test`)
* Modern looking and responsive error documents
## Configuration
See [docs/example.conf](docs/example.conf) for more details.
### Global directives
* `geoip_dir` (optional) - path to a directory containing GeoIP databases
* `dns_server` (optional) - address of a DNS server
### Configuration
* `[cert <cert-name]` - begins section for a certificate
* `certificate` - path to SSL certificate (or certificate chain)
* `private_key` - path to SSL private key
* `[host <host>]` - begins section for the virtual host `<host>`
* `cert` - the name of the certificate to use
* Local
* `webroot` - path to the root directory
* `dir_mode` - specify the behaviour for directories without an `index.html` or `index.php`
* `forbidden` - the server will respond with `403 Forbidden`
* `info` - try passing *path info* to an upper `.php` file.
* `list` - list contents of directory (**not implemented yet**)
* Reverse proxy
* `hostname` - hostname of server to be reverse proxy of
* `port` - port to be used
* `http` - use HTTP to communicate with server
* `https` - use HTTPS to communicate with server

19
docs/example.conf Normal file
View File

@ -0,0 +1,19 @@
certificate /var/cert/cert.pem
private_key /var/cert/cert.key
#geoip_dir /var/dir
#dns_server 192.168.0.1
[localhost]
webroot /var/www/localhost
dir_mode forbidden
[me.local]
hostname www.example.com
port 80
http
[secure.local]
hostname www.example.com
port 443
https

7
run.sh
View File

@ -1,7 +0,0 @@
#!/bin/bash
echo "-- Building and starting Necronda Server..."
make update && make compile && \
echo -e "-- Successfully finished compiling!\n" && \
sleep 0.0625 && \
echo -e "-- Starting Server...\n" && \
authbind ./bin/necronda-server

View File

@ -1,196 +0,0 @@
#include "URI.h"
#include "necronda-server.h"
#include <utility>
#include <sys/stat.h>
using namespace std;
string getExtension(string path) {
long pos = path.find_last_of('.');
if (pos == string::npos) {
return "";
}
return path.substr(pos + 1, path.length() - pos);
}
string getFilename(string path) {
long pos = path.find_last_of('/');
if (pos == string::npos) {
return "";
}
return path.substr(pos + 1, path.length() - pos);
}
bool isDirectory(string path) {
struct stat statbuf;
return stat(path.c_str(), &statbuf) == 0 && S_ISDIR(statbuf.st_mode) != 0;
}
bool isFile(string path) {
struct stat statbuf;
return stat(path.c_str(), &statbuf) == 0 && S_ISDIR(statbuf.st_mode) == 0;
}
bool fileExists(string path) {
struct stat statbuf;
return stat(path.c_str(), &statbuf) == 0;
}
URI::URI() = default;
URI::URI(string webroot, string reqpath) {
unsigned long pos = reqpath.find('?');
if (pos != string::npos) {
queryinit = true;
query = reqpath.substr(pos + 1, reqpath.length() - pos);
reqpath.erase(pos, reqpath.length() - pos);
} else {
query = "";
queryinit = false;
}
if (webroot.length() >= 1 && webroot[webroot.length() - 1] == '/') {
webroot.erase(webroot.length() - 1);
}
reqpath = url_decode(reqpath);
if (reqpath.find("/../") != string::npos) {
throw (char *) "Invalid path";
}
if (reqpath[0] != '/') {
reqpath = '/' + reqpath;
}
this->webroot = webroot;
this->reqpath = reqpath;
info = "";
relpath = reqpath;
while ((!fileExists(webroot + relpath) || (isDirectory(webroot + relpath) && !fileExists(webroot + relpath + "/index.php")))
&& (!fileExists(webroot + relpath + ".php") || (isDirectory(webroot + relpath + ".php") && !fileExists(webroot + relpath + ".php/index.php")))
&& (!fileExists(webroot + relpath + ".html") || (isDirectory(webroot + relpath + ".html") && !fileExists(webroot + relpath + ".html/index.php")))) {
long slash = relpath.find_last_of('/');
if (slash == string::npos || relpath == "/") {
break;
}
info = relpath.substr(slash) + info;
relpath.erase(slash);
}
if (!info.empty() && isDirectory(webroot + relpath)) {
relpath.append("/");
}
string abs = relpath;
if (fileExists(webroot + abs)) {
string ext = getExtension(abs);
if (ext == "php" || ext == "html") {
abs.erase(abs.length() - ext.length() - 1, abs.length());
}
}
string fname = getFilename(abs);
if (fname == "index") {
abs.erase(abs.length() - fname.length() - 1, abs.length());
}
this->filepath = webroot + relpath;
if (isDirectory(webroot + abs)) {
if (abs[abs.length() - 1] != '/') {
abs += "/";
}
this->relpath = abs;
abs += "index";
if (fileExists(webroot + abs + ".php")) {
this->filepath = webroot + abs + ".php";
} else if (fileExists(webroot + abs + ".html")) {
this->filepath = webroot + abs + ".html";
}
} else {
if (abs[abs.length() - 1] == '/') {
abs.erase(abs.length() - 1, abs.length() - 1);
}
this->relpath = abs;
if (fileExists(webroot + abs + ".php")) {
this->filepath = webroot + abs + ".php";
} else if (fileExists(webroot + abs + ".html")) {
this->filepath = webroot + abs + ".html";
}
}
if (isStatic() && !info.empty()) {
if (relpath[relpath.length() - 1] == '/') {
relpath.erase(relpath.length() - 1);
}
newpath = relpath + info;
filepath = "";
} else if (relpath != reqpath) {
if (!info.empty() && relpath[relpath.length() - 1] == '/') {
info.erase(0,1);
}
newpath = relpath + info;
} else {
newpath = "";
}
}
string URI::getWebRoot() {
return webroot;
}
string URI::getRelativePath() {
return relpath;
}
string URI::getAbsolutePath() {
return webroot + relpath;
}
string URI::getFilePath() {
return filepath;
}
string URI::getRelativeFilePath() {
string str = getFilePath();
long len = getWebRoot().length();
return str.substr(len, str.length() - len);
}
string URI::getNewPath() {
if (isStatic()) {
if (hasQuery()) {
return getRelativePath();
}
}
if (!newpath.empty() && newpath != reqpath) {
return url_encode(newpath) + (queryinit? "?" + query : "");
} else {
return "";
}
}
FILE *URI::openFile() {
return fopen64(getFilePath().c_str(), "rb");
}
string URI::getFilePathInfo() {
return info; //getAbsolutePath().erase(getFilePath().length(), getAbsolutePath().length());
}
string URI::getFileType() {
return getMimeType(getFilePath());
}
bool URI::isStatic() {
return getExtension(filepath) != "php";
}
string URI::getQuery() {
return query;
}
bool URI::hasQuery() {
return queryinit;
}

View File

@ -1,51 +0,0 @@
#include <iostream>
#ifndef NECRONDA_PATH
#define NECRONDA_PATH
using namespace std;
class URI {
private:
string webroot;
string reqpath;
string relpath;
string query;
string info;
string filepath;
string newpath;
bool queryinit{};
public:
URI();
URI(string webroot, string reqpath);
string getWebRoot();
string getRelativePath();
string getAbsolutePath();
string getFilePath();
string getRelativeFilePath();
string getNewPath();
FILE *openFile();
string getFilePathInfo();
string getFileType();
bool isStatic();
string getQuery();
bool hasQuery();
};
#endif

898
src/client.c Normal file
View File

@ -0,0 +1,898 @@
/**
* 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;
}

View File

@ -1,673 +0,0 @@
#include <utility>
#include <utility>
/**
* Necronda Web Server 3.0
* client.cpp - Client and Connection handler
* Lorenz Stechauner, 2018-05-16
*/
#include <string>
#include <iostream>
#include <zlib.h>
#include <cassert>
#include <fstream>
#include <openssl/md5.h>
#include <cstring>
#include <fcntl.h>
#include <sstream>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include "network/Socket.h"
#include "network/http/HttpRequest.h"
#include "network/http/HttpConnection.h"
#include "necronda-server.h"
#include "network/http/HttpStatusCode.h"
#include "URI.h"
#include "procopen.h"
#include "network/Address.h"
typedef struct {
string host;
string cc;
string country;
string prov;
string provname;
string city;
string timezone;
string localdate;
} IpAddressInfo;
void log_to_file(const char *prefix, const string &str, string host) {
//FILE *file = fopen((getWebRoot(std::move(host)) + ".access.log").c_str(), "a");
//fprintf(file, "%s%s\r\n", prefix, str.c_str());
//fflush(file);
//fclose(file);
}
void log_error_to_file(const char *prefix, const string &str, string host) {
log_to_file(prefix, "\x1B[1;31m" + str + "\x1B[0m", std::move(host));
}
/**
* Writes log messages to the console
* @param prefix The connection prefix
* @param str The string to be written
*/
void log(const char *prefix, const string &str) {
printf("%s%s\r\n", prefix, str.c_str());
flush(cout);
}
void log_error(const char *prefix, const string &str) {
log(prefix, "\x1B[1;31m" + str + "\x1B[0m");
}
void php_error_handler(const char *prefix, FILE *stderr) {
string line;
while (!(line = read_line(stderr)).empty()) {
log_error(prefix, line);
}
fclose(stderr);
}
IpAddressInfo get_ip_address_info(Address* addr) {
FILE *name = popen(("/opt/ipinfo/ipinfo.py " + addr->toString()).c_str(), "r");
char hostbuffer[1024];
memset(hostbuffer, 0, 1024);
size_t size = fread(hostbuffer, 1, 1024, name);
istringstream buffer(hostbuffer);
string line;
IpAddressInfo info;
int num = 0;
while (std::getline(buffer, line)) {
switch (num) {
case 0: info.host = line; break;
case 1: info.cc = line; break;
case 2: info.country = line; break;
case 3: info.prov = line; break;
case 4: info.provname = line; break;
case 5: info.city = line; break;
case 6: info.timezone = line; break;
case 7: info.localdate = line; break;
}
num++;
}
return info;
}
string get_os_info(int fd) {
struct tcp_info ti;
socklen_t tisize = sizeof(ti);
getsockopt(fd, IPPROTO_TCP, TCP_INFO, &ti, &tisize);
int ttl;
socklen_t ttlsize = sizeof(ttl);
getsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, &ttlsize);
return "win_size=" + to_string(ti.tcpi_rcv_space) + ", ttl=" + to_string(ttl);
}
string getETag(string filename) {
ifstream etags = ifstream("/var/necronda/ETags");
ifstream a = ifstream();
string line;
int index = 0;
int i = 0;
string timestamp = getTimestamp(filename);
long size = getFileSize(filename);
while (getline(etags, line)) {
i++;
if (line == filename) {
index = i;
break;
}
long p1 = line.find(':');
if (p1 == string::npos) continue;
long p2 = line.find(':', (unsigned) p1 + 1);
if (p2 == string::npos) continue;
long p3 = line.find(':', (unsigned) p2 + 1);
if (p3 == string::npos) continue;
string FILENAME = line.substr(0, (unsigned) p1);
string HASH = line.substr((unsigned) p1 + 1, (unsigned) (p2 - p1));
string TIMESTAMP = line.substr((unsigned) p2 + 1, (unsigned) (p3 - p2));
long SIZE = strtol(line.substr((unsigned) p3 + 1, line.length() - p3).c_str(), nullptr, 10);
if (FILENAME == filename) {
index = i;
if (timestamp != TIMESTAMP || size != SIZE) {
break;
} else {
etags.close();
return HASH;
}
}
}
etags.close();
MD5_CTX mdContext;
MD5_Init(&mdContext);
size_t bytes;
char buffer[4096];
FILE *file = fopen(filename.c_str(), "rb");
if (file == nullptr) {
throw (char *) "Invalid file";
}
while ((bytes = fread(buffer, 1, 4096, file)) != 0) {
MD5_Update(&mdContext, buffer, bytes);
}
fclose(file);
unsigned char md[16];
MD5_Final(md, &mdContext);
char md5buff[32];
for (int i = 0; i < 16; i++) {
sprintf(md5buff + i * 2, "%02x", md[i]);
}
string md5 = string(md5buff);
if (index == 0) {
char buff[256];
sprintf(buff, "%s:%s:%s:%ld\n", filename.c_str(), md5.c_str(), timestamp.c_str(), size);
FILE *f = fopen("/var/necronda/ETags", "a");
if (f == nullptr) {
throw (char *) strerror(errno);
}
fseek(f, 0, SEEK_END);
fwrite(buff, 1, strlen(buff), f);
fflush(f);
fclose(f);
} else {
}
return md5;
}
#include <iostream>
#include <wait.h>
#include <thread>
long getPosition(std::string str, char c, int occurence) {
int tempOccur = 0;
int num = 0;
for (auto it : str) {
num++;
if (it == c) {
if (++tempOccur == occurence) {
return num;
}
}
}
return -1;
}
int websocket_handler(Socket *socket, stds *pipes) {
fd_set readfd;
int maxfd = (socket->getFd() > pipes->stdout->_fileno) ? socket->getFd() : pipes->stdout->_fileno;
FD_ZERO(&readfd);
FD_SET(socket->getFd(), &readfd);
FD_SET(pipes->stdout->_fileno, &readfd);
/*while (true) {
int ret = ::select(maxfd + 1, &readfd, nullptr, nullptr, nullptr);
if (ret < 0) {
throw (char *) strerror(errno);
}
int c = fgetc(pipes->stdout);
if (c == -1) {
long rec = socket->receive(pipes->stdin);
} else {
ungetc(c, pipes->stdout);
socket->send(pipes->stdout);
}
}*/
}
/**
* Handles (keep-alive) HTTP connections
* @param prefix The connection prefix
* @param socket The socket
* @param id The client ID
* @param num The Connection Number in the client
* @return Should the server wait for another header?
*/
bool connection_handler(const char *preprefix, const char *col1, const char *col2, Socket *socket, long id, long num, IpAddressInfo *info) {
bool error = false;
char buffer[1024];
char *prefix = (char *) preprefix;
HttpConnection req;
try {
req = HttpConnection(socket);
} catch (char *msg) {
try {
if (msg == "Malformed header") {
log(prefix, "Unable to parse header: Malformed header");
socket->send("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
return false;
} else if (msg == "timeout") {
log(prefix, "Timeout!");
socket->send("HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n");
return false;
} else {
log(prefix, (string) "Unable to receive from socket: " + msg);
return false;
}
} catch (char *msg2) {
return false;
}
}
try {
bool noRedirect, redir, invalidMethod, etag, compress, wantsWebsocket, websocket = false;
URI path;
pid_t childpid;
FILE *file;
int statuscode;
string hash, type, host;
thread *t;
long pos;
stds pipes;
if (req.isExistingField("Connection") && req.getField("Connection") == "keep-alive") {
req.setField("Connection", "keep-alive");
req.setField("Keep-Alive", "timeout=3600, max=100");
} else {
req.setField("Connection", "close");
error = true;
}
host = "";
if (!req.isExistingField("Host")) {
req.respond(400);
goto respond;
}
host = req.getField("Host");
pos = host.find(':');
if (pos != string::npos) {
host.erase(pos, host.length() - pos);
}
/*
FILE *name = popen(("dig @8.8.8.8 +time=1 -x " + socket->getPeerAddress()->toString() +
" | grep -oP \"^[^;].*\\t\\K([^ ]*)\\w\"").c_str(), "r");
char hostbuffer[1024];
memset(hostbuffer, 0, 1024);
size_t size = fread(hostbuffer, 1, 1024, name);
hostbuffer[size - 1] = 0; // remove \n
if (size <= 1) {
sprintf(hostbuffer, "%s", socket->getPeerAddress()->toString().c_str());
}
*/
sprintf(buffer, "[\x1B[1m%s\x1B[0m][%i]%s[%s][%i]%s ", host.c_str(), socket->getSocketPort(), col1,
info->host.c_str(), socket->getPeerPort(), col2);
prefix = buffer;
log(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m");
log_to_file(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m", host);
noRedirect = req.getPath().find("/.well-known/") == 0 || (req.getPath().find("/files/") == 0);
redir = true;
if (!noRedirect) {
if (getWebRoot(host).empty()) {
req.redirect(303, "https://www.necronda.net" + req.getPath());
} else if (socket->getSocketPort() != 443) {
req.redirect(302, "https://" + host + req.getPath());
} else {
redir = false;
}
} else {
redir = false;
}
path = URI(getWebRoot(host), req.getPath());
childpid = 0;
if (redir) {
goto respond;
} else if (!path.getNewPath().empty() && req.getMethod() != "POST") {
req.redirect(303, path.getNewPath());
goto respond;
}
file = path.openFile();
if (file == nullptr) {
req.setField("Cache-Control", "public, max-age=60");
req.respond(404);
goto respond;
}
type = path.getFileType();
if (type.find("inode/") == 0 || (path.getRelativeFilePath().find("/.") != string::npos && !noRedirect)) {
req.respond(403);
goto respond;
}
req.setField("Content-Type", type);
req.setField("Last-Modified", getHttpDate(path.getFilePath()));
invalidMethod = false;
etag = false;
if (path.isStatic()) {
hash = getETag(path.getFilePath());
req.setField("ETag", hash);
req.setField("Accept-Ranges", "bytes");
if (type.find("text/") == 0) {
req.setField("Cache-Control", "public, max-age=3600");
} else {
req.setField("Cache-Control", "public, max-age=86400");
}
req.setField("Allow", "GET");
if (req.getMethod() != "GET") {
invalidMethod = true;
}
if (req.isExistingField("If-None-Match") && req.getField("If-None-Match") == hash) {
etag = true;
}
} else {
req.setField("Accept-Ranges", "none");
req.setField("Cache-Control", "private, no-cache");
req.setField("Allow", "GET, POST, PUT");
if (req.getMethod() != "GET" && req.getMethod() != "POST" && req.getMethod() != "PUT") {
invalidMethod = true;
}
}
if (invalidMethod) {
req.respond(405);
goto respond;
} else if (etag) {
req.respond(304);
goto respond;
}
statuscode = 0;
if (!path.isStatic()) {
string cmd = (string) "env -i" +
" REDIRECT_STATUS=" + cli_encode("CGI") +
" DOCUMENT_ROOT=" + cli_encode(getWebRoot(host)) +
" " + req.cgiExport() +
(req.isExistingField("Content-Length") ? " CONTENT_LENGTH=" +
cli_encode(req.getField(
"Content-Length"))
: "") +
(req.isExistingField("Content-Type") ? " CONTENT_TYPE=" + cli_encode(
req.getField("Content-Type")) : "") +
((socket->isSecured()) ? " HTTPS=on" : "") +
" PATH_INFO=" + cli_encode(path.getFilePathInfo()) +
" PATH_TRANSLATED=" + cli_encode(path.getAbsolutePath()) +
" QUERY_STRING=" + cli_encode(path.getQuery()) +
" REMOTE_ADDR=" + cli_encode(socket->getPeerAddress()->toString()) +
" REMOTE_HOST=" + cli_encode(info->host) +
" REMOTE_PORT=" + cli_encode(to_string(socket->getPeerPort())) +
" REQUEST_METHOD=" + cli_encode(req.getMethod()) +
" REQUEST_URI=" + cli_encode(req.getPath()) +
" SCRIPT_FILENAME=" + cli_encode(path.getFilePath()) +
" SCRIPT_NAME=" + cli_encode(path.getRelativePath()) +
" SERVER_ADMIN=" + cli_encode("lorenz.stechauner@gmail.com") +
" SERVER_NAME=" + cli_encode(host) +
" SERVER_PORT=" + cli_encode(to_string(socket->getSocketPort())) +
" SERVER_SOFTWARE=" + cli_encode("Necronda 3.0") +
" SERVER_PROTOCOL=" + cli_encode("HTTP/1.1") +
" GATEWAY_INTERFACE=" + cli_encode("CGI/1.1") +
" /usr/bin/php-cgi";
pipes = procopen(cmd.c_str());
childpid = pipes.pid;
long len = req.isExistingField("Content-Length")
? strtol(req.getField("Content-Length").c_str(), nullptr, 10)
: ((req.getMethod() == "POST" || req.getMethod() == "PUT") ? -1 : 0);
socket->receive(pipes.stdin, len);
wantsWebsocket = req.getMethod() == "GET" &&
req.isExistingResponseField("Connection") && req.getField("Connection") == "Upgrade" &&
req.isExistingResponseField("Upgrade") && req.getField("Upgrade") == "websocket";
if (!wantsWebsocket) {
// Close only if no Websocket upgrade is possible
fclose(pipes.stdin);
}
t = new thread(php_error_handler, prefix, pipes.stderr);
string line;
while (!(line = read_line(pipes.stdout)).empty()) {
pos = line.find(':');
string index = line.substr(0, pos);
string data = line.substr(pos + 1, line.length() - pos);
while (index[0] == ' ') index.erase(index.begin() + 0);
while (index[index.length() - 1] == ' ') index.erase(index.end() - 1);
while (data[0] == ' ') data.erase(data.begin() + 0);
while (data[data.length() - 1] == ' ') data.erase(data.end() - 1);
if (index == "Status") {
statuscode = (int) strtol(data.substr(0, 3).c_str(), nullptr, 10);
} else {
if (index == "Location" && statuscode == 0) {
statuscode = 303;
} else if (index == "Content-Type") {
type = data;
}
req.setField(index, data);
}
}
websocket = statuscode == 101 &&
req.isExistingResponseField("Connection") && req.getResponseField("Connection") == "Upgrade" &&
req.isExistingResponseField("Upgrade") && req.getResponseField("Upgrade") == "websocket";
fclose(file);
file = pipes.stdout;
if (websocket) {
log(prefix, "Upgrade to WebSocket!");
req.respond(statuscode);
goto respond;
} else {
int c = fgetc(pipes.stdout);
if (c == -1) {
// No Data -> Error
req.respond((statuscode == 0) ? 500 : statuscode);
goto respond;
} else {
ungetc(c, pipes.stdout);
}
}
}
statuscode = (statuscode == 0) ? 200 : statuscode;
compress = (type.find("text/") == 0 ||
(type.find("application/") == 0 && type.find("+xml") != string::npos) ||
type == "application/json" ||
type == "application/javascript") &&
req.isExistingField("Accept-Encoding") &&
req.getField("Accept-Encoding").find("deflate") != string::npos;
if (compress) {
req.setField("Accept-Ranges", "none");
}
if (compress && req.isExistingField("Range")) {
req.respond(416);
} else if (req.isExistingField("Range")) {
string range = req.getField("Range");
if (range.find("bytes=") != 0 || !path.isStatic()) {
req.respond(416);
} else {
fseek(file, 0L, SEEK_END);
long len = ftell(file);
fseek(file, 0L, SEEK_SET);
long p = range.find('-');
if (p == string::npos) {
req.respond(416);
} else {
string part1 = range.substr(6, (unsigned long) (p - 6));
string part2 = range.substr((unsigned long) (p + 1),
range.length() - p - 1);
long num1 = stol(part1, nullptr, 10);
long num2 = len - 1;
if (!part2.empty()) {
num2 = stol(part2, nullptr, 10);
}
if (num1 < 0 || num1 >= len || num2 < 0 || num2 >= len) {
req.respond(416);
} else {
req.setField("Content-Range",
(string) "bytes " + to_string(num1) + "-" +
to_string(num2) +
"/" + to_string(len));
req.respond(206, file, compress, num1, num2);
}
}
}
} else {
req.respond(statuscode, file, compress);
}
fclose(file);
if (childpid > 0) {
waitpid(childpid, nullptr, 0);
}
respond:
HttpStatusCode status = req.getStatusCode();
int code = status.code;
string color;
string comment;
if ((code >= 200 && code < 300) || code == 304 || code == 101) {
color = "\x1B[1;32m"; // Success (Cached, Switching Protocols): Green
} else if (code >= 100 && code < 200) {
color = "\x1B[1;93m"; // Continue: Yellow
} else if (code >= 300 && code < 400) {
color = "\x1B[1;93m"; // Redirect: Yellow
comment = " -> " +
(req.isExistingResponseField("Location") ? req.getResponseField("Location") : "<invalid>");
} else if (code >= 400 && code < 500) {
color = "\x1B[1;31m"; // Client Error: Red
//comment = " -> " + req.getPath();
} else if (code >= 500 & code < 600) {
color = "\x1B[1;31m"; // Server Error: Red
//comment = " -> " + req.getPath();
}
string msg = color + to_string(status.code) + " " + status.message + comment + " (" + formatTime(req.getDuration()) + ")\x1B[0m";
log(prefix, msg);
if (!host.empty()) {
log_to_file(prefix, msg, host);
}
if (websocket) {
websocket_handler(socket, &pipes);
log(prefix, "\x1B[1mClosing WebSocket (" + formatTime(req.getDuration()) + ")\x1B[0m");
}
} catch (char *msg) {
HttpStatusCode status = req.getStatusCode();
log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req.getDuration()) + ")");
try {
if (strncmp(msg, "timeout", strlen(msg)) == 0) {
log(prefix, "Timeout!");
req.setField("Connection", "close");
req.respond(408);
error = true;
} else if (strncmp(msg, "Invalid path", strlen(msg)) == 0) {
log(prefix, "Timeout!");
req.setField("Connection", "close");
req.respond(400);
} else {
log(prefix, (string) "Unable to receive from socket: " + msg);
error = true;
}
} catch (char *msg2) {
}
}
return !error;
}
/**
* Handles HTTP clients
* @param socket The socket
* @param id The client ID
*/
void client_handler(Socket *socket, long id, bool ssl) {
const char *prefix;
char const *col1;
char const *col2 = "\x1B[0m";
IpAddressInfo info = get_ip_address_info(socket->getPeerAddress());
auto os = get_os_info(socket->getFd());
{
auto group = (int) (id % 6);
if (group == 0) {
col1 = "\x1B[0;31m"; // Red
} else if (group == 1) {
col1 = "\x1B[0;32m"; // Green
} else if (group == 2) {
col1 = "\x1B[0;34m"; // Blue
} else if (group == 3) {
col1 = "\x1B[0;33m"; // Yellow
} else if (group == 4) {
col1 = "\x1B[0;35m"; // Magenta
} else {
col1 = "\x1B[0;36m"; // Cyan
}
string *a = new string("[" + socket->getSocketAddress()->toString() + "][" +
to_string(socket->getSocketPort()) + "]" + col1 +
"[" + info.host + "][" + to_string(socket->getPeerPort()) +
"]" + col2 + " ");
prefix = a->c_str();
}
log(prefix, "Connection established");
log(prefix, string("Host: ") + info.host + " (" + socket->getPeerAddress()->toString() + ")");
log(prefix, string("OS: ") + os);
log(prefix, string("Location: ") + info.cc + "/" + info.country + ", " + info.prov + "/" + info.provname + ", " + info.city);
log(prefix, string("Local Date: ") + info.localdate + " (" + info.timezone + ")");
bool err = false;
try {
socket->setReceiveTimeout(3600000);
socket->setSendTimeout(36000000);
} catch (char *msg) {
log(prefix, (string) "Unable to set timeout on socket: " + msg);
err = true;
}
try {
if (ssl) {
//socket->sslHandshake("/home/lorenz/Documents/Projects/Necronda-Server/necronda-server-3.0/privkey.pem",
// "/home/lorenz/Documents/Projects/Necronda-Server/necronda-server-3.0/fullchain.pem");
socket->sslHandshake("/cert/necronda.net/privkey.pem",
"/cert/necronda.net/fullchain.pem");
}
} catch (char *msg) {
log(prefix, (string) "Unable to perform handshake: " + msg);
err = true;
}
long reqnum = 0;
if (!err) {
while (connection_handler(prefix, col1, col2, socket, id, ++reqnum, &info));
reqnum--;
}
log(prefix,
"Connection terminated (#:" + to_string(reqnum) + ", R: " + formatSize(socket->getBytesReceived()) + ", S: " +
formatSize(socket->getBytesSent()) + ", T: " + formatTime(socket->getDuration()) + ")");
socket->close();
}

397
src/lib/cache.c Normal file
View File

@ -0,0 +1,397 @@
/**
* 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;
}

49
src/lib/cache.h Normal file
View File

@ -0,0 +1,49 @@
/**
* 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

79
src/lib/compress.c Normal file
View File

@ -0,0 +1,79 @@
/**
* Necronda Web Server
* Compression interface
* src/lib/compress.c
* Lorenz Stechauner, 2021-05-05
*/
#include "compress.h"
#include <malloc.h>
#include <errno.h>
int compress_init(compress_ctx *ctx, int mode) {
ctx->gzip = NULL;
ctx->brotli = NULL;
ctx->mode = 0;
int ret;
if (mode & COMPRESS_GZ) {
ctx->mode |= COMPRESS_GZ;
ctx->gzip = malloc(sizeof(z_stream));
ctx->gzip->zalloc = Z_NULL;
ctx->gzip->zfree = Z_NULL;
ctx->gzip->opaque = Z_NULL;
ret = deflateInit2(ctx->gzip, COMPRESS_LEVEL_GZIP, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY);
if (ret != Z_OK) return -1;
}
if (mode & COMPRESS_BR) {
ctx->mode |= COMPRESS_BR;
ctx->brotli = BrotliEncoderCreateInstance(NULL, NULL, NULL);
if (ctx->brotli == NULL) return -1;
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_MODE, BROTLI_MODE_GENERIC);
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_QUALITY, COMPRESS_LEVEL_BROTLI);
}
return 0;
}
int compress_compress(compress_ctx *ctx, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish) {
if ((ctx->mode & COMPRESS_GZ) && (ctx->mode & COMPRESS_BR)) {
errno = EINVAL;
return -1;
}
return compress_compress_mode(ctx, ctx->mode, in, in_len, out, out_len, finish);
}
int compress_compress_mode(compress_ctx *ctx, int mode, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish) {
if ((mode & COMPRESS_GZ) && (mode & COMPRESS_BR)) {
errno = EINVAL;
return -1;
} else if (mode & COMPRESS_GZ) {
ctx->gzip->next_in = (unsigned char*) in;
ctx->gzip->avail_in = *in_len;
ctx->gzip->next_out = (unsigned char*) out;
ctx->gzip->avail_out = *out_len;
int ret = deflate(ctx->gzip, finish ? Z_FINISH : Z_NO_FLUSH);
*in_len = ctx->gzip->avail_in;
*out_len = ctx->gzip->avail_out;
return ret;
} else if (mode & COMPRESS_BR) {
int ret = BrotliEncoderCompressStream(ctx->brotli, finish ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
in_len, (const unsigned char**) &in, out_len, (unsigned char **) &out, NULL);
return (ret == BROTLI_TRUE) ? 0 : -1;
} else {
errno = EINVAL;
return -2;
}
}
int compress_free(compress_ctx *ctx) {
if (ctx->gzip != NULL) {
deflateEnd(ctx->gzip);
free(ctx->gzip);
ctx->gzip = NULL;
}
if (ctx->brotli != NULL) {
BrotliEncoderDestroyInstance(ctx->brotli);
ctx->brotli = NULL;
}
ctx->mode = 0;
return 0;
}

37
src/lib/compress.h Normal file
View File

@ -0,0 +1,37 @@
/**
* Necronda Web Server
* Compression interface (header file)
* src/lib/compress.h
* Lorenz Stechauner, 2021-05-05
*/
#ifndef NECRONDA_SERVER_COMPRESS_H
#define NECRONDA_SERVER_COMPRESS_H
#include <zlib.h>
#include <brotli/encode.h>
#define COMPRESS_LEVEL_GZIP 9
#define COMPRESS_LEVEL_BROTLI BROTLI_MAX_QUALITY
#define COMPRESS_GZ 1
#define COMPRESS_BR 2
#define COMPRESS 3
typedef struct {
int mode;
z_stream *gzip;
BrotliEncoderState *brotli;
} compress_ctx;
int compress_init(compress_ctx *ctx, int mode);
int compress_compress(compress_ctx *ctx, const char *in, unsigned long *in_len, char *out, unsigned long *out_len,
int finish);
int compress_compress_mode(compress_ctx *ctx, int mode, const char *in, unsigned long *in_len, char *out,
unsigned long *out_len, int finish);
int compress_free(compress_ctx *ctx);
#endif //NECRONDA_SERVER_COMPRESS_H

274
src/lib/config.c Normal file
View File

@ -0,0 +1,274 @@
/**
* Necronda Web Server
* Configuration file loader
* src/lib/config.c
* Lorenz Stechauner, 2021-01-05
*/
#include "config.h"
#include "utils.h"
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
t_config *config;
char geoip_dir[256], dns_server[256];
int config_init() {
int shm_id = shmget(CONFIG_SHM_KEY, sizeof(t_config), IPC_CREAT | IPC_EXCL | 0640);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
return -1;
}
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
if (shm == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
return -2;
}
config = shm;
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
return -3;
}
config = shm_rw;
memset(config, 0, 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;
}
int config_load(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
fprintf(stderr, ERR_STR "Unable to open config file: %s" CLR_STR "\n", strerror(errno));
return -1;
}
fseek(file, 0, SEEK_END);
unsigned long len = ftell(file);
fseek(file, 0, SEEK_SET);
char *conf = alloca(len + 1);
fread(conf, 1, len, file);
conf[len] = 0;
fclose(file);
t_config *tmp_config = malloc(sizeof(t_config));
memset(tmp_config, 0, sizeof(t_config));
int i = 0;
int j = 0;
int line = 0;
int mode = 0;
char section = 0;
char *ptr = NULL;
char *source, *target;
while ((ptr = strsep(&conf, "\r\n")) != NULL) {
line++;
char *comment = strchr(ptr, '#');
if (comment != NULL) comment[0] = 0;
len = strlen(ptr);
char *end_ptr = ptr + len - 1;
while (end_ptr[0] == ' ' || end_ptr[0] == '\t') {
end_ptr[0] = 0;
end_ptr--;
}
len = strlen(ptr);
if (len == 0) continue;
if (ptr[0] == '[') {
if (ptr[len - 1] != ']') goto err;
ptr++;
int l = 0;
if (strncmp(ptr, "host", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
ptr += 4;
while (ptr[0] == ' ' || ptr[0] == '\t' || ptr[0] == ']') ptr++;
while (ptr[l] != ' ' && ptr[l] != '\t' && ptr[l] != ']') l++;
if (l == 0) goto err;
snprintf(tmp_config->hosts[i].name, sizeof(tmp_config->hosts[i].name), "%.*s", l, ptr);
i++;
section = 'h';
} else if (strncmp(ptr, "cert", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
ptr += 4;
while (ptr[0] == ' ' || ptr[0] == '\t' || ptr[0] == ']') ptr++;
while (ptr[l] != ' ' && ptr[l] != '\t' && ptr[l] != ']') l++;
if (l == 0) goto err;
snprintf(tmp_config->certs[j].name, sizeof(tmp_config->certs[j].name), "%.*s", l, ptr);
j++;
section = 'c';
} else {
goto err;
}
continue;
} else if (section == 0) {
if (len > 10 && strncmp(ptr, "geoip_dir", 9) == 0 && (ptr[9] == ' ' || ptr[9] == '\t')) {
source = ptr + 9;
target = geoip_dir;
} else if (len > 11 && strncmp(ptr, "dns_server", 10) == 0 && (ptr[10] == ' ' || ptr[10] == '\t')) {
source = ptr + 10;
target = dns_server;
} else {
goto err;
}
} else if (section == 'c') {
cert_config *cc = &tmp_config->certs[j - 1];
if (len > 12 && strncmp(ptr, "certificate", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
source = ptr + 11;
target = cc->full_chain;
} else if (len > 12 && strncmp(ptr, "private_key", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
source = ptr + 11;
target = cc->priv_key;
} else {
goto err;
}
} else if (section == 'h') {
host_config *hc = &tmp_config->hosts[i - 1];
if (len > 8 && strncmp(ptr, "webroot", 7) == 0 && (ptr[7] == ' ' || ptr[7] == '\t')) {
source = ptr + 7;
target = hc->local.webroot;
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
goto err;
} else {
hc->type = CONFIG_TYPE_LOCAL;
}
} else if (len > 5 && strncmp(ptr, "cert", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
source = ptr + 4;
target = hc->cert_name;
} else if (len > 9 && strncmp(ptr, "dir_mode", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) {
source = ptr + 8;
target = NULL;
mode = 1;
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
goto err;
} else {
hc->type = CONFIG_TYPE_LOCAL;
}
} else if (len > 9 && strncmp(ptr, "hostname", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) {
source = ptr + 8;
target = hc->rev_proxy.hostname;
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
}
} else if (len > 5 && strncmp(ptr, "port", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
source = ptr + 4;
target = NULL;
mode = 2;
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
}
} else if (strcmp(ptr, "http") == 0) {
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
hc->rev_proxy.enc = 0;
}
continue;
} else if (strcmp(ptr, "https") == 0) {
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
hc->rev_proxy.enc = 1;
}
continue;
} else {
goto err;
}
} else {
goto err;
}
while (source[0] == ' ' || source[0] == '\t') source++;
if (strlen(source) == 0) {
err:
free(tmp_config);
fprintf(stderr, ERR_STR "Unable to parse config file (line %i)" CLR_STR "\n", line);
return -2;
}
if (target != NULL) {
strcpy(target, source);
} else if (mode == 1) {
if (strcmp(source, "forbidden") == 0) {
tmp_config->hosts[i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN;
} else if (strcmp(source, "info") == 0) {
tmp_config->hosts[i - 1].local.dir_mode = URI_DIR_MODE_INFO;
} else if (strcmp(source, "list") == 0) {
tmp_config->hosts[i - 1].local.dir_mode = URI_DIR_MODE_LIST;
} else {
goto err;
}
} else if (mode == 2) {
tmp_config->hosts[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10);
}
}
for (int k = 0; k < i; k++) {
host_config *hc = &tmp_config->hosts[k];
if (hc->type == CONFIG_TYPE_LOCAL) {
char *webroot = tmp_config->hosts[k].local.webroot;
if (webroot[strlen(webroot) - 1] == '/') {
webroot[strlen(webroot) - 1] = 0;
}
}
if (hc->cert_name[0] == 0) goto err2;
int found = 0;
for (int m = 0; m < j; m++) {
if (strcmp(tmp_config->certs[m].name, hc->cert_name) == 0) {
hc->cert = m;
found = 1;
break;
}
}
if (!found) {
err2:
free(tmp_config);
fprintf(stderr, ERR_STR "Unable to parse config file" CLR_STR "\n");
return -2;
}
}
int shm_id = shmget(CONFIG_SHM_KEY, 0, 0);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to get 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;
}

64
src/lib/config.h Normal file
View File

@ -0,0 +1,64 @@
/**
* Necronda Web Server
* Configuration file loader (header file)
* src/lib/config.h
* Lorenz Stechauner, 2021-01-05
*/
#ifndef NECRONDA_SERVER_CONFIG_H
#define NECRONDA_SERVER_CONFIG_H
#include "uri.h"
#define CONFIG_SHM_KEY 255642
#define CONFIG_MAX_HOST_CONFIG 64
#define CONFIG_MAX_CERT_CONFIG 64
#define CONFIG_TYPE_UNSET 0
#define CONFIG_TYPE_LOCAL 1
#define CONFIG_TYPE_REVERSE_PROXY 2
#ifndef DEFAULT_CONFIG_FILE
# define DEFAULT_CONFIG_FILE "/etc/necronda/server.conf"
#endif
typedef struct {
int type;
char name[256];
char cert_name[256];
int cert;
union {
struct {
char hostname[256];
unsigned short port;
unsigned char enc:1;
} rev_proxy;
struct {
char webroot[256];
unsigned char dir_mode:2;
} local;
};
} host_config;
typedef struct {
char name[256];
char full_chain[256];
char priv_key[256];
} cert_config;
typedef struct {
host_config hosts[CONFIG_MAX_HOST_CONFIG];
cert_config certs[CONFIG_MAX_CERT_CONFIG];
} t_config;
extern t_config *config;
extern char geoip_dir[256], dns_server[256];
int config_init();
int config_load(const char *filename);
int config_unload();
#endif //NECRONDA_SERVER_CONFIG_H

622
src/lib/fastcgi.c Normal file
View File

@ -0,0 +1,622 @@
/**
* Necronda Web Server
* FastCGI interface implementation
* src/lib/fastcgi.c
* Lorenz Stechauner, 2020-12-26
*/
#include "fastcgi.h"
#include "utils.h"
#include "compress.h"
#include "../necronda-server.h"
#include <sys/un.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
char *fastcgi_add_param(char *buf, const char *key, const char *value) {
char *ptr = buf;
unsigned long key_len = strlen(key);
unsigned long val_len = strlen(value);
if (key_len <= 127) {
ptr[0] = (char) (key_len & 0x7F);
ptr++;
} else {
ptr[0] = (char) (0x80 | (key_len >> 24));
ptr[1] = (char) ((key_len >> 16) & 0xFF);
ptr[2] = (char) ((key_len >> 8) & 0xFF);
ptr[3] = (char) (key_len & 0xFF);
ptr += 4;
}
if (val_len <= 127) {
ptr[0] = (char) (val_len & 0x7F);
ptr++;
} else {
ptr[0] = (char) (0x80 | (val_len >> 24));
ptr[1] = (char) ((val_len >> 16) & 0xFF);
ptr[2] = (char) ((val_len >> 8) & 0xFF);
ptr[3] = (char) (val_len & 0xFF);
ptr += 4;
}
memcpy(ptr, key, key_len);
ptr += key_len;
memcpy(ptr, value, val_len);
ptr += val_len;
return ptr;
}
int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned int req_num, const sock *client,
const http_req *req, const http_uri *uri) {
unsigned short req_id = (client_num & 0xFFF) << 4;
if (client_num == 0) {
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 = {
.version = FCGI_VERSION_1,
.requestIdB1 = req_id >> 8,
.requestIdB0 = req_id & 0xFF,
.paddingLength = 0,
.reserved = 0
};
header.type = FCGI_BEGIN_REQUEST;
header.contentLengthB1 = 0;
header.contentLengthB0 = sizeof(FCGI_BeginRequestBody);
FCGI_BeginRequestRecord begin = {
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];
char buf0[256];
char *param_ptr = param_buf + sizeof(header);
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, "GATEWAY_INTERFACE", "CGI/1.1");
param_ptr = fastcgi_add_param(param_ptr, "SERVER_SOFTWARE", SERVER_STR);
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PROTOCOL", "HTTP/1.1");
param_ptr = fastcgi_add_param(param_ptr, "SERVER_NAME", http_get_header_field(&req->hdr, "Host"));
if (client->enc) {
param_ptr = fastcgi_add_param(param_ptr, "HTTPS", "on");
}
struct sockaddr_storage addr_storage;
struct sockaddr_in6 *addr;
socklen_t len = sizeof(addr_storage);
getsockname(client->socket, (struct sockaddr *) &addr_storage, &len);
addr = (struct sockaddr_in6 *) &addr_storage;
sprintf(buf0, "%i", addr->sin6_port);
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PORT", buf0);
len = sizeof(addr_storage);
getpeername(client->socket, (struct sockaddr *) &addr_storage, &len);
addr = (struct sockaddr_in6 *) &addr_storage;
sprintf(buf0, "%i", addr->sin6_port);
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_HOST", client_host_str != NULL ? client_host_str : client_addr_str);
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_IDENT", "");
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_USER", "");
param_ptr = fastcgi_add_param(param_ptr, "REQUEST_METHOD", req->method);
param_ptr = fastcgi_add_param(param_ptr, "REQUEST_URI", req->uri);
param_ptr = fastcgi_add_param(param_ptr, "SCRIPT_NAME", uri->filename + strlen(uri->webroot));
param_ptr = fastcgi_add_param(param_ptr, "SCRIPT_FILENAME", uri->filename);
//param_ptr = fastcgi_add_param(param_ptr, "PATH_TRANSLATED", uri->filename);
param_ptr = fastcgi_add_param(param_ptr, "QUERY_STRING", uri->query != NULL ? uri->query : "");
if (uri->pathinfo != NULL && strlen(uri->pathinfo) > 0) {
sprintf(buf0, "/%s", uri->pathinfo);
} else {
buf0[0] = 0;
}
param_ptr = fastcgi_add_param(param_ptr, "PATH_INFO", buf0);
//param_ptr = fastcgi_add_param(param_ptr, "AUTH_TYPE", "");
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_LENGTH", content_length != NULL ? content_length : "");
char *content_type = http_get_header_field(&req->hdr, "Content-Type");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_TYPE", content_type != NULL ? content_type : "");
if (client_geoip != NULL) {
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", client_geoip);
}
for (int i = 0; i < req->hdr.field_num; i++) {
char *ptr = buf0;
ptr += sprintf(ptr, "HTTP_");
for (int j = 0; j < strlen(req->hdr.fields[i][0]); j++, ptr++) {
char ch = req->hdr.fields[i][0][j];
if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
ch = ch;
} else if (ch >= 'a' && ch <= 'z') {
ch &= 0x5F;
} else {
ch = '_';
}
ptr[0] = ch;
ptr[1] = 0;
}
param_ptr = fastcgi_add_param(param_ptr, buf0, req->hdr.fields[i][1]);
}
unsigned short param_len = param_ptr - param_buf - sizeof(header);
header.type = FCGI_PARAMS;
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;
header.contentLengthB1 = 0;
header.contentLengthB0 = 0;
if (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
print(ERR_STR "Unable to send to FastCGI socket: %s" CLR_STR, strerror(errno));
return -2;
}
return 0;
}
int fastcgi_close_stdin(fastcgi_conn *conn) {
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 (send(conn->socket, &header, sizeof(header), 0) != sizeof(header)) {
print(ERR_STR "Unable to send to FastCGI socket: %s" CLR_STR, strerror(errno));
return -2;
}
return 0;
}
int fastcgi_php_error(const fastcgi_conn *conn, const char *msg, int msg_len, char *err_msg) {
char *msg_str = malloc(msg_len + 1);
char *ptr0 = msg_str;
strncpy(msg_str, msg, msg_len);
char *ptr1 = NULL;
int len;
int err = 0;
// FIXME *msg is part of a stream, handle fragmented lines
while (1) {
int msg_type = 0;
int msg_pre_len = 0;
ptr1 = strstr(ptr0, "PHP message: ");
if (ptr1 == NULL) {
len = (int) (msg_len - (ptr0 - msg_str));
if (ptr0 == msg_str) msg_type = 2;
} else {
len = (int) (ptr1 - ptr0);
}
if (len == 0) {
goto next;
}
if (len >= 14 && strncmp(ptr0, "PHP Warning: ", 14) == 0) {
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;
char *ptr3;
int len2;
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);
return err;
}
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
FCGI_Header header;
char *content;
unsigned short content_len, req_id;
long ret;
int err = 0;
while (1) {
ret = recv(conn->socket, &header, sizeof(header), 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));
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) {
continue;
}
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 1;
} else if (header.type == FCGI_STDERR) {
// TODO implement Necronda backend error handling
if (conn->mode == FASTCGI_PHP) {
err = err || fastcgi_php_error(conn, content, content_len, err_msg);
}
} else if (header.type == FCGI_STDOUT) {
break;
} else {
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
}
free(content);
}
if (err) {
res->status = http_get_status(500);
return 2;
}
conn->out_buf = content;
conn->out_len = content_len;
conn->out_off = (unsigned short) (strstr(content, "\r\n\r\n") - content + 4);
char *buf = content;
unsigned short header_len = conn->out_off;
if (header_len <= 0) {
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
return 1;
}
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
return 2;
}
}
char *ptr = buf;
while (header_len != (ptr - buf)) {
char *pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
return 1;
}
ret = http_parse_header_field(&res->hdr, ptr, pos0);
if (ret != 0) return (int) ret;
if (pos0[2] == '\r' && pos0[3] == '\n') {
return 0;
}
ptr = pos0 + 2;
}
return 0;
}
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags) {
FCGI_Header header;
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) {
FCGI_Header header;
long ret;
char buf0[256];
char *content, *ptr = buf;
unsigned short req_id, content_len;
if (conn->out_buf != NULL && conn->out_len > conn->out_off) {
ptr += snprintf(ptr, len, "%.*s", conn->out_len - conn->out_off, conn->out_buf + conn->out_off);
}
while (1) {
ret = recv(conn->socket, &header, sizeof(header), 0);
if (ret < 0) {
print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
return -1;
} else if (ret != sizeof(header)) {
print(ERR_STR "Unable to receive from FastCGI socket: received len (%li) != header len (%li)" CLR_STR,
ret, sizeof(header));
return -1;
}
req_id = (header.requestIdB1 << 8) | header.requestIdB0;
content_len = (header.contentLengthB1 << 8) | header.contentLengthB0;
content = malloc(content_len + header.paddingLength);
long rcv_len = 0;
while (rcv_len < content_len + header.paddingLength) {
ret = recv(conn->socket, content + rcv_len, content_len + header.paddingLength - rcv_len, 0);
if (ret < 0) {
print(ERR_STR "Unable to receive from FastCGI socket: %s" CLR_STR, strerror(errno));
free(content);
return -1;
}
rcv_len += ret;
}
if (header.type == FCGI_END_REQUEST) {
FCGI_EndRequestBody *body = (FCGI_EndRequestBody *) content;
int app_status = (body->appStatusB3 << 24) | (body->appStatusB2 << 16) | (body->appStatusB1 << 8) |
body->appStatusB0;
if (body->protocolStatus != FCGI_REQUEST_COMPLETE) {
print(ERR_STR "FastCGI protocol error: %i" CLR_STR, body->protocolStatus);
}
if (app_status != 0) {
print(ERR_STR "FastCGI app terminated with exit code %i" CLR_STR, app_status);
}
close(conn->socket);
conn->socket = 0;
free(content);
return 0;
} else if (header.type == FCGI_STDERR) {
// TODO implement Necronda backend error handling
if (conn->mode == FASTCGI_PHP) {
fastcgi_php_error(conn, content, content_len, buf0);
}
} else if (header.type == FCGI_STDOUT) {
ptr += snprintf(ptr, len - (ptr - buf), "%.*s", content_len, content);
} else {
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
}
free(content);
}
}
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len) {
unsigned long rcv_len = 0;
char *buf[16384];
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;
}
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;
}

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

@ -0,0 +1,57 @@
/**
* Necronda Web Server
* FastCGI interface implementation (header file)
* src/lib/fastcgi.h
* Lorenz Stechauner, 2020-12-26
*/
#ifndef NECRONDA_SERVER_FASTCGI_H
#define NECRONDA_SERVER_FASTCGI_H
#include "include/fastcgi.h"
#include "http.h"
#include "uri.h"
#define FASTCGI_CHUNKED 1
#define FASTCGI_COMPRESS_GZ 2
#define FASTCGI_COMPRESS_BR 4
#define FASTCGI_COMPRESS 6
#define FASTCGI_COMPRESS_HOLD 8
#define FASTCGI_PHP 1
#define FASTCGI_NECRONDA 2
#ifndef PHP_FPM_SOCKET
# define PHP_FPM_SOCKET "/var/run/php-fpm/php-fpm.sock"
#endif
#define NECRONDA_BACKEND_SOCKET "/var/run/necronda/necronda-backend.sock"
typedef struct {
int mode;
int socket;
unsigned short req_id;
char *out_buf;
const char *webroot;
unsigned short out_len;
unsigned short out_off;
} fastcgi_conn;
char *fastcgi_add_param(char *buf, const char *key, const char *value);
int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned int req_num, const sock *client,
const http_req *req, const http_uri *uri);
int fastcgi_close_stdin(fastcgi_conn *conn);
int fastcgi_php_error(const fastcgi_conn *conn, const char *msg, int msg_len, char *err_msg);
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg);
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags);
int fastcgi_dump(fastcgi_conn *conn, char *buf, long len);
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len);
#endif //NECRONDA_SERVER_FASTCGI_H

69
src/lib/geoip.c Normal file
View File

@ -0,0 +1,69 @@
/**
* Necronda Web Server
* MaxMind GeoIP Database interface
* src/lib/geoip.c
* Lorenz Stechauner, 2021-05-04
*/
#include "geoip.h"
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len) {
switch (list->entry_data.type) {
case MMDB_DATA_TYPE_MAP:
*str_off += sprintf(str + *str_off, "{");
break;
case MMDB_DATA_TYPE_ARRAY:
*str_off += sprintf(str + *str_off, "[");
break;
case MMDB_DATA_TYPE_UTF8_STRING:
*str_off += sprintf(str + *str_off, "\"%.*s\"", list->entry_data.data_size, list->entry_data.utf8_string);
break;
case MMDB_DATA_TYPE_UINT16:
*str_off += sprintf(str + *str_off, "%u", list->entry_data.uint16);
break;
case MMDB_DATA_TYPE_UINT32:
*str_off += sprintf(str + *str_off, "%u", list->entry_data.uint32);
break;
case MMDB_DATA_TYPE_UINT64:
*str_off += sprintf(str + *str_off, "%lu", list->entry_data.uint64);
break;
case MMDB_DATA_TYPE_UINT128:
*str_off += sprintf(str + *str_off, "%llu", (unsigned long long) list->entry_data.uint128);
break;
case MMDB_DATA_TYPE_INT32:
*str_off += sprintf(str + *str_off, "%i", list->entry_data.uint32);
break;
case MMDB_DATA_TYPE_BOOLEAN:
*str_off += sprintf(str + *str_off, "%s", list->entry_data.boolean ? "true" : "false");
break;
case MMDB_DATA_TYPE_FLOAT:
*str_off += sprintf(str + *str_off, "%f", list->entry_data.float_value);
break;
case MMDB_DATA_TYPE_DOUBLE:
*str_off += sprintf(str + *str_off, "%f", list->entry_data.double_value);
break;
}
if (list->entry_data.type != MMDB_DATA_TYPE_MAP && list->entry_data.type != MMDB_DATA_TYPE_ARRAY) {
return list->next;
}
MMDB_entry_data_list_s *next = list->next;
int stat = 0;
for (int i = 0; i < list->entry_data.data_size; i++) {
next = mmdb_json(next, str, str_off, str_len);
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
stat = !stat;
if (stat) {
i--;
*str_off += sprintf(str + *str_off, ":");
continue;
}
}
if (i != list->entry_data.data_size - 1) *str_off += sprintf(str + *str_off, ",");
}
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
*str_off += sprintf(str + *str_off, "}");
} else {
*str_off += sprintf(str + *str_off, "]");
}
return next;
}

18
src/lib/geoip.h Normal file
View File

@ -0,0 +1,18 @@
/**
* Necronda Web Server
* MaxMind GeoIP Database interface (header file)
* src/lib/geoip.h
* Lorenz Stechauner, 2021-05-04
*/
#ifndef NECRONDA_SERVER_GEOIP_H
#define NECRONDA_SERVER_GEOIP_H
#include <maxminddb.h>
#define GEOIP_MAX_SIZE 8192
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len);
#endif //NECRONDA_SERVER_GEOIP_H

328
src/lib/http.c Normal file
View File

@ -0,0 +1,328 @@
/**
* Necronda Web Server
* HTTP implementation
* src/lib/http.c
* Lorenz Stechauner, 2020-12-09
*/
#include "http.h"
#include "utils.h"
#include "compress.h"
#include <string.h>
void http_to_camel_case(char *str, int mode) {
char last = '-';
char ch;
for (int i = 0; i < strlen(str); i++) {
ch = str[i];
if (mode == HTTP_CAMEL && last == '-' && ch >= 'a' && ch <= 'z') {
str[i] = (char) ((int) ch & 0x5F);
} else if (mode == HTTP_LOWER && ch >= 'A' && ch <= 'Z') {
str[i] = (char) ((int) ch | 0x20);
}
last = str[i];
}
}
void http_free_hdr(http_hdr *hdr) {
for (int i = 0; i < hdr->field_num; i++) {
free(hdr->fields[i][0]);
free(hdr->fields[i][1]);
}
hdr->field_num = 0;
}
void http_free_req(http_req *req) {
if (req->uri != NULL) free(req->uri);
req->uri = NULL;
http_free_hdr(&req->hdr);
}
void http_free_res(http_res *res) {
http_free_hdr(&res->hdr);
}
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) {
char *pos1 = memchr(buf, ':', end_ptr - buf);
char *pos2;
if (pos1 == NULL) {
print(ERR_STR "Unable to parse header" CLR_STR);
return 3;
}
long len = pos1 - buf;
hdr->fields[(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;
}
int http_receive_request(sock *client, http_req *req) {
long rcv_len, len;
char *ptr, *pos0, *pos1, *pos2;
char buf[CLIENT_MAX_HEADER_SIZE];
memset(buf, 0, sizeof(buf));
memset(req->method, 0, sizeof(req->method));
memset(req->version, 0, sizeof(req->version));
req->uri = NULL;
req->hdr.field_num = 0;
while (1) {
rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE, 0);
if (rcv_len <= 0) {
print("Unable to receive http header: %s", sock_strerror(client));
return -1;
}
unsigned long header_len = strstr(buf, "\r\n\r\n") - buf + 4;
if (header_len <= 0) {
print(ERR_STR "Unable to parse http header: End of header not found" CLR_STR);
return 5;
}
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
print(ERR_STR "Unable to parse http header: Header contains illegal characters" CLR_STR);
return 4;
}
}
ptr = buf;
while (header_len > (ptr - buf + 2)) {
pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
print(ERR_STR "Unable to parse http header: Invalid header format" CLR_STR);
return 1;
}
if (req->version[0] == 0) {
pos1 = memchr(ptr, ' ', rcv_len - (ptr - buf)) + 1;
if (pos1 == NULL) goto err_hdr_fmt;
if (pos1 - ptr - 1 >= sizeof(req->method)) {
print(ERR_STR "Unable to parse http header: Method name too long" CLR_STR);
return 2;
}
for (int i = 0; i < (pos1 - ptr - 1); i++) {
if (ptr[i] < 'A' || ptr[i] > 'Z') {
print(ERR_STR "Unable to parse http header: Invalid method" CLR_STR);
return 2;
}
}
snprintf(req->method, sizeof(req->method), "%.*s", (int) (pos1 - ptr - 1), ptr);
pos2 = memchr(pos1, ' ', rcv_len - (pos1 - buf)) + 1;
if (pos2 == NULL) {
err_hdr_fmt:
print(ERR_STR "Unable to parse http header: Invalid header format" CLR_STR);
return 1;
}
if (memcmp(pos2, "HTTP/", 5) != 0 || memcmp(pos2 + 8, "\r\n", 2) != 0) {
print(ERR_STR "Unable to parse http header: Invalid version" CLR_STR);
return 3;
}
len = pos2 - pos1 - 1;
req->uri = malloc(len + 1);
sprintf(req->uri, "%.*s", (int) len, pos1);
sprintf(req->version, "%.3s", pos2 + 5);
} else {
int ret = http_parse_header_field(&req->hdr, ptr, pos0);
if (ret != 0) return ret;
}
ptr = pos0 + 2;
}
if (pos0[2] == '\r' && pos0[3] == '\n') {
break;
}
}
client->buf_len = rcv_len - (pos0 - buf + 4);
if (client->buf_len > 0) {
client->buf = malloc(client->buf_len);
client->buf_off = 0;
memcpy(client->buf, pos0 + 4, client->buf_len);
}
return 0;
}
char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
char field_name_1[256], field_name_2[256];
strcpy(field_name_1, field_name);
http_to_camel_case(field_name_1, HTTP_LOWER);
for (int i = 0; i < hdr->field_num; i++) {
strcpy(field_name_2, hdr->fields[i][0]);
http_to_camel_case(field_name_2, HTTP_LOWER);
if (strcmp(field_name_1, field_name_2) == 0) {
return hdr->fields[i][1];
}
}
return NULL;
}
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
size_t len_name = strlen(field_name);
size_t len_value = strlen(field_value);
char *_field_name = malloc(len_name + 1);
char *_field_value = malloc(len_value + 1);
strcpy(_field_name, field_name);
strcpy(_field_value, field_value);
http_to_camel_case(_field_name, HTTP_PRESERVE);
hdr->fields[(int) hdr->field_num][0] = _field_name;
hdr->fields[(int) hdr->field_num][1] = _field_value;
hdr->field_num++;
}
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
char field_name_1[256], field_name_2[256];
strcpy(field_name_1, field_name);
http_to_camel_case(field_name_1, HTTP_LOWER);
int i = 0;
int diff = 1;
if (mode == HTTP_REMOVE_LAST) {
i = hdr->field_num - 1;
diff = -1;
}
for (; i < hdr->field_num && i >= 0; i += diff) {
strcpy(field_name_2, hdr->fields[i][0]);
http_to_camel_case(field_name_2, HTTP_LOWER);
if (strcmp(field_name_1, field_name_2) == 0) {
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) {
i -= diff;
} else {
return;
}
}
}
}
int http_send_response(sock *client, http_res *res) {
char buf[CLIENT_MAX_HEADER_SIZE];
long off = sprintf(buf, "HTTP/%s %03i %s\r\n", res->version, res->status->code, res->status->msg);
for (int i = 0; i < res->hdr.field_num; i++) {
off += sprintf(buf + off, "%s: %s\r\n", res->hdr.fields[i][0], res->hdr.fields[i][1]);
}
off += sprintf(buf + off, "\r\n");
if (sock_send(client, buf, off, 0) < 0) {
return -1;
}
return 0;
}
int http_send_request(sock *server, http_req *req) {
char buf[CLIENT_MAX_HEADER_SIZE];
long off = sprintf(buf, "%s %s HTTP/%s\r\n", req->method, req->uri, req->version);
for (int i = 0; i < req->hdr.field_num; i++) {
off += sprintf(buf + off, "%s: %s\r\n", req->hdr.fields[i][0], req->hdr.fields[i][1]);
}
off += sprintf(buf + off, "\r\n");
long ret = sock_send(server, buf, off, 0);
if (ret <= 0) {
return -1;
}
return 0;
}
const http_status *http_get_status(unsigned short status_code) {
for (int i = 0; i < http_statuses_size / sizeof(http_status); i++) {
if (http_statuses[i].code == status_code) {
return &http_statuses[i];
}
}
return NULL;
}
const http_status_msg *http_get_error_msg(const http_status *status) {
unsigned short code = status->code;
for (int i = 0; i < http_status_messages_size / sizeof(http_status_msg); i++) {
if (http_status_messages[i].code == code) {
return &http_status_messages[i];
}
}
return NULL;
}
const char *http_get_status_color(const http_status *status) {
unsigned short code = status->code;
if (code >= 100 && code < 200) {
return HTTP_1XX_STR;
} else if ((code >= 200 && code < 300) || code == 304) {
return HTTP_2XX_STR;
} else if (code >= 300 && code < 400) {
return HTTP_3XX_STR;
} else if (code >= 400 && code < 500) {
return HTTP_4XX_STR;
} else if (code >= 500 && code < 600) {
return HTTP_5XX_STR;
}
return "";
}
char *http_format_date(time_t time, char *buf, size_t size) {
struct tm *timeinfo = gmtime(&time);
strftime(buf, size, "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
return buf;
}
char *http_get_date(char *buf, size_t size) {
time_t rawtime;
time(&rawtime);
return http_format_date(rawtime, buf, size);
}
const http_doc_info *http_get_status_info(const http_status *status) {
unsigned short code = status->code;
static http_doc_info info[] = {
{"info", HTTP_COLOR_INFO, http_info_icon, http_info_document},
{"success", HTTP_COLOR_SUCCESS, http_success_icon, http_success_document},
{"warning", HTTP_COLOR_WARNING, http_warning_icon, http_warning_document},
{"error", HTTP_COLOR_ERROR, http_error_icon, http_error_document}
};
if (code >= 100 && code < 200) {
return &info[0];
} else if ((code >= 200 && code < 300) || code == 304) {
return &info[1];
} else if (code >= 300 && code < 400) {
return &info[2];
} else if (code >= 400 && code < 600) {
return &info[3];
}
return NULL;
}
int http_get_compression(const http_req *req, const http_res *res) {
char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
char *content_type = http_get_header_field(&res->hdr, "Content-Type");
char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
if (mime_is_compressible(content_type) && content_encoding == NULL && accept_encoding != NULL) {
if (strstr(accept_encoding, "br") != NULL) {
return COMPRESS_BR;
} else if (strstr(accept_encoding, "gzip") != NULL) {
return COMPRESS_GZ;
}
}
return 0;
}

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

@ -0,0 +1,139 @@
/**
* Necronda Web Server
* HTTP implementation (header file)
* src/lib/http.h
* Lorenz Stechauner, 2020-12-09
*/
#ifndef NECRONDA_SERVER_HTTP_H
#define NECRONDA_SERVER_HTTP_H
#include "sock.h"
#define HTTP_PRESERVE 0
#define HTTP_LOWER 1
#define HTTP_CAMEL 2
#define HTTP_REMOVE_ONE 0
#define HTTP_REMOVE_ALL 1
#define HTTP_REMOVE_LAST 2
#define HTTP_1XX_STR "\x1B[1;32m"
#define HTTP_2XX_STR "\x1B[1;32m"
#define HTTP_3XX_STR "\x1B[1;33m"
#define HTTP_4XX_STR "\x1B[1;31m"
#define HTTP_5XX_STR "\x1B[1;31m"
#define HTTP_COLOR_SUCCESS "#008000"
#define HTTP_COLOR_INFO "#606060"
#define HTTP_COLOR_WARNING "#E0C000"
#define HTTP_COLOR_ERROR "#C00000"
#define CLIENT_MAX_HEADER_SIZE 8192
#ifndef SERVER_STR
# define SERVER_STR "Necronda"
#endif
#ifndef SERVER_STR_HTML
# define SERVER_STR_HTML "Necronda&nbsp;web&nbsp;server"
#endif
typedef struct {
unsigned short code;
char type[16];
char msg[64];
} http_status;
typedef struct {
unsigned short code;
const char *msg;
} http_status_msg;
typedef struct {
char mode[8];
char color[8];
const char *icon;
const char *doc;
} http_doc_info;
typedef struct {
char field_num;
char *fields[64][2];
} http_hdr;
typedef struct {
char method[16];
char *uri;
char version[4];
http_hdr hdr;
} http_req;
typedef struct {
const http_status *status;
char version[4];
http_hdr hdr;
} http_res;
typedef enum {
NONE, INTERNAL, CLIENT_REQ, SERVER_REQ, SERVER, SERVER_RES, CLIENT_RES
} http_error_origin;
typedef struct {
unsigned short status;
http_error_origin origin;
} http_status_ctx;
extern const http_status http_statuses[];
extern const http_status_msg http_status_messages[];
extern const int http_statuses_size;
extern const int http_status_messages_size;
extern const char http_default_document[];
extern const char http_rev_proxy_document[];
extern const char http_error_document[];
extern const char http_error_icon[];
extern const char http_warning_document[];
extern const char http_warning_icon[];
extern const char http_success_document[];
extern const char http_success_icon[];
extern const char http_info_document[];
extern const char http_info_icon[];
void http_to_camel_case(char *str, int mode);
void http_free_hdr(http_hdr *hdr);
void http_free_req(http_req *req);
void http_free_res(http_res *res);
int http_receive_request(sock *client, http_req *req);
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) ;
char *http_get_header_field(const http_hdr *hdr, const char *field_name);
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode);
int http_send_response(sock *client, http_res *res);
int http_send_request(sock *server, http_req *req);
const http_status *http_get_status(unsigned short status_code);
const http_status_msg *http_get_error_msg(const http_status *status);
const char *http_get_status_color(const http_status *status);
char *http_format_date(time_t time, char *buf, size_t size);
char *http_get_date(char *buf, size_t size);
const http_doc_info *http_get_status_info(const http_status *status);
int http_get_compression(const http_req *req, const http_res *res);
#endif //NECRONDA_SERVER_HTTP_H

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

@ -0,0 +1,303 @@
/**
* Necronda Web Server
* HTTP static implementation
* src/lib/http_static.c
* Lorenz Stechauner, 2021-05-03
*/
#include "../necronda.h"
#include "http.h"
const http_status http_statuses[] = {
{100, "Informational", "Continue"},
{101, "Informational", "Switching Protocols"},
{200, "Success", "OK"},
{201, "Success", "Created"},
{202, "Success", "Accepted"},
{203, "Success", "Non-Authoritative Information"},
{204, "Success", "No Content"},
{205, "Success", "Reset Content"},
{206, "Success", "Partial Content"},
{300, "Redirection", "Multiple Choices"},
{301, "Redirection", "Moved Permanently"},
{302, "Redirection", "Found"},
{303, "Redirection", "See Other"},
{304, "Success", "Not Modified"},
{305, "Redirection", "Use Proxy"},
{307, "Redirection", "Temporary Redirect"},
{308, "Redirection", "Permanent Redirect"},
{400, "Client Error", "Bad Request"},
{401, "Client Error", "Unauthorized"},
{402, "Client Error", "Payment Required"},
{403, "Client Error", "Forbidden"},
{404, "Client Error", "Not Found"},
{405, "Client Error", "Method Not Allowed"},
{406, "Client Error", "Not Acceptable"},
{407, "Client Error", "Proxy Authentication Required"},
{408, "Client Error", "Request Timeout"},
{409, "Client Error", "Conflict"},
{410, "Client Error", "Gone"},
{411, "Client Error", "Length Required"},
{412, "Client Error", "Precondition Failed"},
{413, "Client Error", "Request Entity Too Large"},
{414, "Client Error", "Request-URI Too Long"},
{415, "Client Error", "Unsupported Media Type"},
{416, "Client Error", "Range Not Satisfiable"},
{417, "Client Error", "Expectation Failed"},
{500, "Server Error", "Internal Server Error"},
{501, "Server Error", "Not Implemented"},
{502, "Server Error", "Bad Gateway"},
{503, "Server Error", "Service Unavailable"},
{504, "Server Error", "Gateway Timeout"},
{505, "Server Error", "HTTP Version Not Supported"},
};
const http_status_msg http_status_messages[] = {
{100, "The client SHOULD continue with its request."},
{101, "The server understands and is willing to comply with the clients request, via the Upgrade message header field, for a change in the application protocol being used on this connection."},
{200, "The request has succeeded."},
{201, "The request has been fulfilled and resulted in a new resource being created."},
{202, "The request has been accepted for processing, but the processing has not been completed."},
{203, "The returned meta information in the entity-header is not the definitive set as available from the origin server, but is gathered from a local or a third-party copy."},
{204, "The server has fulfilled the request but does not need to return an entity-body, and might want to return updated meta information."},
{205, "The server has fulfilled the request and the user agent SHOULD reset the document view which caused the request to be sent."},
{206, "The server has fulfilled the partial GET request for the resource."},
{300, "The requested resource corresponds to any one of a set of representations, each with its own specific location, and agent-driven negotiation information is being provided so that the user (or user agent) can select a preferred representation and redirect its request to that location."},
{301, "The requested resource has been assigned a new permanent URI and any future references to this resource SHOULD use one of the returned URIs."},
{302, "The requested resource resides temporarily under a different URI."},
{303, "The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource."},
{304, "The request has been fulfilled and the requested resource has not been modified."},
{305, "The requested resource MUST be accessed through the proxy given by the Location field."},
{307, "The requested resource resides temporarily under a different URI."},
{308, "The requested resource has been assigned a new permanent URI and any future references to this resource ought to use one of the enclosed URIs."},
{400, "The request could not be understood by the server due to malformed syntax."},
{401, "The request requires user authentication."},
{403, "The server understood the request, but is refusing to fulfill it."},
{404, "The server has not found anything matching the Request-URI."},
{405, "The method specified in the Request-Line is not allowed for the resource identified by the Request-URI."},
{406, "The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request."},
{407, "The request requires user authentication on the proxy."},
{408, "The client did not produce a request within the time that the server was prepared to wait."},
{409, "The request could not be completed due to a conflict with the current state of the resource."},
{410, "The requested resource is no longer available at the server and no forwarding address is known."},
{411, "The server refuses to accept the request without a defined Content-Length."},
{412, "The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server."},
{413, "The server is refusing to process a request because the request entity is larger than the server is willing or able to process."},
{414, "The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret."},
{415, "The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method."},
{416, "None of the ranges in the requests Range header field overlap the current extent of the selected resource or that the set of ranges requested has been rejected due to invalid ranges or an excessive request of small or overlapping ranges."},
{417, "The expectation given in an Expect request-header field could not be met by this server, or, if the server is a proxy, the server has unambiguous evidence that the request could not be met by the next-hop server."},
{500, "The server encountered an unexpected condition which prevented it from fulfilling the request."},
{501, "The server does not support the functionality required to fulfill the request."},
{502, "The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request."},
{503, "The server is currently unable to handle the request due to a temporary overloading or maintenance of the server."},
{504, "The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI or some other auxiliary server it needed to access in attempting to complete the request."},
{505, "The server does not support, or refuses to support, the HTTP protocol version that was used in the request message."}
};
const char http_default_document[] =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
"\t<title>%1$i %2$s - %7$s</title>\n"
"\t<meta charset=\"UTF-8\"/>\n"
"\t<meta name=\"theme-color\" content=\"%6$s\"/>\n"
"\t<meta name=\"color-scheme\" content=\"light dark\"/>\n"
"\t<meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\"/>\n"
"\t<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n"
"\t<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/favicon.ico\"/>\n"
"%5$s"
"\t<style>\n"
"\t\thtml{font-family:\"Arial\",sans-serif;--error:" HTTP_COLOR_ERROR ";--warning:" HTTP_COLOR_WARNING ";--success:" HTTP_COLOR_SUCCESS ";--info:" HTTP_COLOR_INFO ";--soft:#808080;--color:var(--%4$s);}\n"
"\t\tbody{background-color:#F0F0F0;margin:0;}\n"
"\t\tmain{max-width:650px;margin:2em auto;}\n"
"\t\tsection{margin:2em 1em;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em;}\n"
"\t\th1,h2,h3,h4,h5,h6{text-align:center;color:var(--color);font-weight:normal;}\n"
"\t\th1{font-size:3em;margin:0.125em 0;}\n"
"\t\th2{font-size:1.5em;margin:0.25em 0 1em 0;}\n"
"\t\tp{text-align:center;font-size:0.875em;}\n"
"\t\tdiv.footer{color:var(--soft);font-size:0.75em;text-align:center;margin:2em 0 0.5em 0;}\n"
"\t\tdiv.footer a{color:var(--soft);}\n"
"\t\tul,ol{width:fit-content;margin:auto;}\n"
"\t\tpre{width:fit-content;margin:2em auto 0 auto;}\n"
"\n"
"\t\tsection.error-ctx{display:flex;padding:0;border:none;}\n"
"\t\tdiv.box{flex:100%% 1 1;border:1px solid var(--info);color:var(--info);position:relative;padding:1em;box-sizing:border-box;text-align:center;}\n"
"\t\tdiv.box.error{border-color:var(--error);color:var(--error);}\n"
"\t\tdiv.box.success{border-color:var(--success);color:var(--success);}\n"
"\t\tdiv.arrow{position:absolute;height:20px;width:30px;z-index:10;background-repeat:no-repeat;background-size:contain;}\n"
"\t\tdiv.arrow.response{left:-17.5px;bottom:calc(33.3333%% - 10px);}\n"
"\t\tdiv.arrow.request{right:-17.5px;top:calc(33.3333%% - 10px);}\n"
"\t\tdiv.border{flex:1px 0 0;background-color:var(--info);}\n"
"\t\tdiv.border.error{background-color:var(--error);}\n"
"\t\tdiv.border.success{background-color:var(--success);}\n"
"\t\tdiv.content>span{display:block;color:var(--soft);font-size:0.75em;}\n"
"\t\tdiv.content>img{height:3.75rem;margin:0.75rem auto;display:block;}\n"
"\t\th3{font-size:2.25em;margin:0.75rem 0 0 0;color:unset;height:2.5rem;}\n"
"\t\th4{font-size:1em;margin:0 0 0.75rem 0;color:unset;height:1.25rem;}\n"
"\n"
"\t\tdiv.arrow.request.success{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjRkZG"
"RkZGIiBzdHJva2U9IiMwMDgwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}\n"
"\t\tdiv.arrow.request.error{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjRkZG"
"RkZGIiBzdHJva2U9IiNDMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}\n"
"\t\tdiv.arrow.response.success{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiNGRkZG"
"RkYiIHN0cm9rZT0iIzAwODAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}\n"
"\t\tdiv.arrow.response.error{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiNGRkZG"
"RkYiIHN0cm9rZT0iI0MwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}\n"
"\n"
"\t\t@media(prefers-color-scheme:dark){\n"
"\t\t\thtml{color:#FFFFFF;--soft:#404040;}\n"
"\t\t\tbody{background-color:#101010;}\n"
"\t\t\tsection{background-color:#181818;}\n"
"\n"
"\t\t\tdiv.arrow.request.success{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjMTgx"
"ODE4IiBzdHJva2U9IiMwMDgwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}\n"
"\t\t\tdiv.arrow.request.error{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjMTgx"
"ODE4IiBzdHJva2U9IiNDMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}\n"
"\t\t\tdiv.arrow.response.success{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiMxODE4"
"MTgiIHN0cm9rZT0iIzAwODAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}\n"
"\t\t\tdiv.arrow.response.error{background-image:url('data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiMxODE4"
"MTgiIHN0cm9rZT0iI0MwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}\n"
"\t\t}\n"
"\t\t@media(min-width:650px){\n"
"\t\t\tdiv.box:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px;border-right:none;}\n"
"\t\t\tdiv.box:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px;border-left:none;}\n"
"\t\t\tdiv.box:not(:last-child):not(:first-child){border-left:none;border-right:none;}\n"
"\t\t}\n"
"\t\t@media(max-width:650px){\n"
"\t\t\tsection.error-ctx{flex-direction:column;height:unset;}\n"
"\t\t\tdiv.box:first-child{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom:none;padding-top:1em;}\n"
"\t\t\tdiv.box:last-child{border-bottom-right-radius:4px;border-bottom-left-radius:4px;border-top:none;padding-bottom:1em;}\n"
"\t\t\tdiv.box:not(:last-child):not(:first-child){border-top:none;border-bottom:none;}\n"
"\t\t\tdiv.arrow.response{transform:rotate(90deg);top:-10px;left:calc(33.3333%% - 22.5px);right:unset;}\n"
"\t\t\tdiv.arrow.request{transform:rotate(90deg);bottom:-10px;right:calc(33.3333%% - 22.5px);top:unset;}\n"
"\t\t}\n"
"\t</style>\n"
"</head>\n"
"<body>\n"
"\t<main>\n"
"\t\t<section>\n"
"%3$s"
"%9$s"
"\t\t\t<div class=\"footer\"><a href=\"https://%7$s/\">%7$s</a> - " SERVER_STR_HTML "</div>\n"
"\t\t</section>\n"
"%8$s"
"\t</main>\n"
"</body>\n"
"</html>\n";
const char http_rev_proxy_document[] =
"\t\t<section class=\"error-ctx\">\n"
"\t\t\t<div class=\"box%1$s\">\n"
"\t\t\t\t<div class=\"content\">\n"
"\t\t\t\t\t<span>Client</span>\n"
"\t\t\t\t\t<img src=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHBhdGggZD0iTTIsMzIgYTMwLDMwLDAsMSwwLDYwLDAgYTMwLDMwLDAsMSwwLC02MCww"
"IEw2MiwzMiBNNiwxNiBMNTgsMTYgTTYsNDggTDU4LDQ4IE0zMiwyIEwzMiw2MiBhMTUsMzAsMCwx"
"LDAsMCwtNjAgYTE1LDMwLDAsMSwwLDAsNjAgWiIgc3Ryb2tlPSIjMDA4MDAwIiBzdHJva2Utd2lk"
"dGg9IjIiIGZpbGw9IiMwMDAwMDAwMCIvPjwvc3ZnPgo=\"/>\n"
"\t\t\t\t\t<span>Your Browser</span>\n"
"\t\t\t\t</div>\n"
"\t\t\t\t<div class=\"arrow request%2$s\"></div>\n"
"\t\t\t</div>\n"
"\t\t\t<div class=\"border%8$s\"></div>\n"
"\t\t\t<div class=\"box%3$s\">\n"
"\t\t\t\t<div class=\"content\">\n"
"\t\t\t\t\t<span>Reverse Proxy</span>\n"
"\t\t\t\t\t<h3>%10$03i</h3>\n"
"\t\t\t\t\t<h4>%11$s</h4>\n"
"\t\t\t\t\t<span>" SERVER_NAME "</span>\n"
"\t\t\t\t</div>\n"
"\t\t\t\t<div class=\"arrow request%4$s\"></div>\n"
"\t\t\t\t<div class=\"arrow response%5$s\"></div>\n"
"\t\t\t</div>\n"
"\t\t\t<div class=\"border%9$s\"></div>\n"
"\t\t\t<div class=\"box%6$s\">\n"
"\t\t\t\t<div class=\"content\">\n"
"\t\t\t\t\t<span>Server</span>\n"
"\t\t\t\t\t<h3>%12$s</h3>\n"
"\t\t\t\t\t<h4>%13$s</h4>\n"
"\t\t\t\t\t<span>%14$s</span>\n"
"\t\t\t\t</div>\n"
"\t\t\t\t<div class=\"arrow response%7$s\"></div>\n"
"\t\t\t</div>\n"
"\t\t</section>\n";
const char http_error_document[] =
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;(</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char http_error_icon[] =
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNDMDAwMDAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjooPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
const char http_warning_document[] =
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;)</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char http_warning_icon[] =
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiNFMEMwMDAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
const char http_success_document[] =
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;)</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char http_success_icon[] =
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiMwMDgwMDAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
const char http_info_document[] =
"\t\t\t<h1>%1$i</h1>\n"
"\t\t\t<h2>%2$s :&#xFEFF;)</h2>\n"
"\t\t\t<p>%3$s</p>\n"
"\t\t\t<p>%4$s</p>\n";
const char http_info_icon[] =
"\t<link rel=\"alternate icon\" type=\"image/svg+xml\" sizes=\"any\" href=\"data:image/svg+xml;base64,"
"PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAw"
"L3N2ZyI+PHRleHQgeD0iNCIgeT0iMTIiIGZpbGw9IiM2MDYwNjAiIHN0eWxlPSJmb250LWZhbWls"
"eTonQXJpYWwnLHNhbnMtc2VyaWYiPjopPC90ZXh0Pjwvc3ZnPgo=\"/>\n";
const int http_statuses_size = sizeof(http_statuses);
const int http_status_messages_size = sizeof(http_status_messages);

122
src/lib/include/fastcgi.h Normal file
View File

@ -0,0 +1,122 @@
/**
* Necronda Web Server
* FastCGI header file
* src/lib/include/fastcgi.h
* Lorenz Stechauner, 2021-05-03
*/
#ifndef NECRONDA_SERVER_EXTERN_FASTCGI_H
#define NECRONDA_SERVER_EXTERN_FASTCGI_H
/*
* Listening socket file number
*/
#define FCGI_LISTENSOCK_FILENO 0
typedef struct {
unsigned char version;
unsigned char type;
unsigned char requestIdB1;
unsigned char requestIdB0;
unsigned char contentLengthB1;
unsigned char contentLengthB0;
unsigned char paddingLength;
unsigned char reserved;
} FCGI_Header;
/*
* Number of bytes in a FCGI_Header. Future versions of the protocol
* will not reduce this number.
*/
#define FCGI_HEADER_LEN 8
/*
* Value for version component of FCGI_Header
*/
#define FCGI_VERSION_1 1
/*
* Values for type component of FCGI_Header
*/
#define FCGI_BEGIN_REQUEST 1
#define FCGI_ABORT_REQUEST 2
#define FCGI_END_REQUEST 3
#define FCGI_PARAMS 4
#define FCGI_STDIN 5
#define FCGI_STDOUT 6
#define FCGI_STDERR 7
#define FCGI_DATA 8
#define FCGI_GET_VALUES 9
#define FCGI_GET_VALUES_RESULT 10
#define FCGI_UNKNOWN_TYPE 11
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
/*
* Value for requestId component of FCGI_Header
*/
#define FCGI_NULL_REQUEST_ID 0
typedef struct {
unsigned char roleB1;
unsigned char roleB0;
unsigned char flags;
unsigned char reserved[5];
} FCGI_BeginRequestBody;
typedef struct {
FCGI_Header header;
FCGI_BeginRequestBody body;
} FCGI_BeginRequestRecord;
/*
* Mask for flags component of FCGI_BeginRequestBody
*/
#define FCGI_KEEP_CONN 1
/*
* Values for role component of FCGI_BeginRequestBody
*/
#define FCGI_RESPONDER 1
#define FCGI_AUTHORIZER 2
#define FCGI_FILTER 3
typedef struct {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned char protocolStatus;
unsigned char reserved[3];
} FCGI_EndRequestBody;
typedef struct {
FCGI_Header header;
FCGI_EndRequestBody body;
} FCGI_EndRequestRecord;
/*
* Values for protocolStatus component of FCGI_EndRequestBody
*/
#define FCGI_REQUEST_COMPLETE 0
#define FCGI_CANT_MPX_CONN 1
#define FCGI_OVERLOADED 2
#define FCGI_UNKNOWN_ROLE 3
/*
* Variable names for FCGI_GET_VALUES / FCGI_GET_VALUES_RESULT records
*/
#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
#define FCGI_MAX_REQS "FCGI_MAX_REQS"
#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
typedef struct {
unsigned char type;
unsigned char reserved[7];
} FCGI_UnknownTypeBody;
typedef struct {
FCGI_Header header;
FCGI_UnknownTypeBody body;
} FCGI_UnknownTypeRecord;
#endif //NECRONDA_SERVER_EXTERN_FASTCGI_H

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

@ -0,0 +1,541 @@
/**
* Necronda Web Server
* Reverse proxy
* src/lib/rev_proxy.c
* Lorenz Stechauner, 2021-01-07
*/
#include "rev_proxy.h"
#include "utils.h"
#include "compress.h"
#include "../necronda-server.h"
#include <openssl/ssl.h>
#include <string.h>
#include <errno.h>
#include <openssl/err.h>
#include <arpa/inet.h>
#include <sys/time.h>
sock rev_proxy;
char *rev_proxy_host = NULL;
struct timeval server_timeout = {.tv_sec = SERVER_TIMEOUT, .tv_usec = 0};
int rev_proxy_preload() {
rev_proxy.buf = NULL;
rev_proxy.buf_len = 0;
rev_proxy.buf_off = 0;
rev_proxy.ctx = SSL_CTX_new(TLS_client_method());
return 0;
}
int rev_proxy_request_header(http_req *req, int enc) {
char buf1[256];
char buf2[256];
int p_len;
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "Connection", "keep-alive");
char *via = http_get_header_field(&req->hdr, "Via");
sprintf(buf1, "HTTP/%s %s", req->version, SERVER_NAME);
if (via == NULL) {
http_add_header_field(&req->hdr, "Via", buf1);
} else {
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
if (p_len < 0 || p_len >= sizeof(buf2)) {
print(ERR_STR "Header field 'Via' too long" CLR_STR);
return -1;
}
http_remove_header_field(&req->hdr, "Via", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "Via", buf2);
}
char *host = http_get_header_field(&req->hdr, "Host");
char *forwarded = http_get_header_field(&req->hdr, "Forwarded");
int client_ipv6 = strchr(client_addr_str, ':') != NULL;
int server_ipv6 = strchr(server_addr_str, ':') != NULL;
p_len = snprintf(buf1, sizeof(buf1), "by=%s%s%s;for=%s%s%s;host=%s;proto=%s",
server_ipv6 ? "\"[" : "", server_addr_str, server_ipv6 ? "]\"" : "",
client_ipv6 ? "\"[" : "", client_addr_str, client_ipv6 ? "]\"" : "",
host, enc ? "https" : "http");
if (p_len < 0 || p_len >= sizeof(buf1)) {
print(ERR_STR "Appended part of header field 'Forwarded' too long" CLR_STR);
return -1;
}
if (forwarded == NULL) {
http_add_header_field(&req->hdr, "Forwarded", buf1);
} else {
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", forwarded, buf1);
if (p_len < 0 || p_len >= sizeof(buf2)) {
print(ERR_STR "Header field 'Forwarded' too long" CLR_STR);
return -1;
}
http_remove_header_field(&req->hdr, "Forwarded", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "Forwarded", buf2);
}
char *xff = http_get_header_field(&req->hdr, "X-Forwarded-For");
if (xff == NULL) {
http_add_header_field(&req->hdr, "X-Forwarded-For", client_addr_str);
} else {
sprintf(buf1, "%s, %s", xff, client_addr_str);
http_remove_header_field(&req->hdr, "X-Forwarded-For", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "X-Forwarded-For", buf1);
}
char *xfh = http_get_header_field(&req->hdr, "X-Forwarded-Host");
if (xfh == NULL) {
if (forwarded == NULL) {
http_add_header_field(&req->hdr, "X-Forwarded-Host", host);
} else {
char *ptr = strchr(forwarded, ',');
unsigned long len;
if (ptr != NULL) len = ptr - forwarded;
else len = strlen(forwarded);
ptr = strstr(forwarded, "host=");
if ((ptr - forwarded) < len) {
char *end = strchr(ptr, ';');
if (end == NULL) len -= (ptr - forwarded);
else len = (end - ptr);
len -= 5;
sprintf(buf1, "%.*s", (int) len, ptr + 5);
http_add_header_field(&req->hdr, "X-Forwarded-Host", buf1);
}
}
}
char *xfp = http_get_header_field(&req->hdr, "X-Forwarded-Proto");
if (xfp == NULL) {
if (forwarded == NULL) {
http_add_header_field(&req->hdr, "X-Forwarded-Proto", enc ? "https" : "http");
} else {
char *ptr = strchr(forwarded, ',');
unsigned long len;
if (ptr != NULL) len = ptr - forwarded;
else len = strlen(forwarded);
ptr = strstr(forwarded, "proto=");
if ((ptr - forwarded) < len) {
char *end = strchr(ptr, ';');
if (end == NULL) len -= (ptr - forwarded);
else len = (end - ptr);
len -= 6;
sprintf(buf1, "%.*s", (int) len, ptr + 6);
http_add_header_field(&req->hdr, "X-Forwarded-Proto", buf1);
}
}
}
return 0;
}
int rev_proxy_response_header(http_req *req, http_res *res) {
char buf1[256];
char buf2[256];
int p_len;
char *via = http_get_header_field(&res->hdr, "Via");
p_len = snprintf(buf1, sizeof(buf1), "HTTP/%s %s", req->version, SERVER_NAME);
if (p_len < 0 || p_len >= sizeof(buf1)) {
print(ERR_STR "Appended part of header field 'Via' too long" CLR_STR);
return -1;
}
if (via == NULL) {
http_add_header_field(&res->hdr, "Via", buf1);
} else {
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
if (p_len < 0 || p_len >= sizeof(buf2)) {
print(ERR_STR "Header field 'Via' too long" CLR_STR);
return -1;
}
http_remove_header_field(&res->hdr, "Via", HTTP_REMOVE_ALL);
http_add_header_field(&res->hdr, "Via", buf2);
}
return 0;
}
int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_config *conf, sock *client,
http_status *custom_status, char *err_msg) {
char buffer[CHUNK_SIZE];
long ret;
int tries = 0;
int retry = 0;
if (rev_proxy.socket != 0 && strcmp(rev_proxy_host, conf->name) == 0 && sock_check(&rev_proxy) == 0) {
goto rev_proxy;
}
retry:
if (rev_proxy.socket != 0) {
print(BLUE_STR "Closing proxy connection" CLR_STR);
sock_close(&rev_proxy);
}
retry = 0;
tries++;
rev_proxy.socket = socket(AF_INET6, SOCK_STREAM, 0);
if (rev_proxy.socket < 0) {
print(ERR_STR "Unable to create socket: %s" CLR_STR, strerror(errno));
res->status = http_get_status(500);
ctx->origin = INTERNAL;
return -1;
}
server_timeout.tv_sec = SERVER_TIMEOUT_INIT;
server_timeout.tv_usec = 0;
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
goto rev_proxy_timeout_err;
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
goto rev_proxy_timeout_err;
struct hostent *host_ent = gethostbyname2(conf->rev_proxy.hostname, AF_INET6);
if (host_ent == NULL) {
host_ent = gethostbyname2(conf->rev_proxy.hostname, AF_INET);
if (host_ent == NULL) {
res->status = http_get_status(503);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to connect to server: Name or service not known" CLR_STR);
sprintf(err_msg, "Unable to connect to server: Name or service not known.");
goto proxy_err;
}
}
struct sockaddr_in6 address = {.sin6_family = AF_INET6, .sin6_port = htons(conf->rev_proxy.port)};
if (host_ent->h_addrtype == AF_INET6) {
memcpy(&address.sin6_addr, host_ent->h_addr_list[0], host_ent->h_length);
} else if (host_ent->h_addrtype == AF_INET) {
unsigned char addr[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0, 0, 0, 0};
memcpy(addr + 12, host_ent->h_addr_list[0], host_ent->h_length);
memcpy(&address.sin6_addr, addr, 16);
}
inet_ntop(address.sin6_family, (void *) &address.sin6_addr, buffer, sizeof(buffer));
print(BLUE_STR "Connecting to " BLD_STR "[%s]:%i" CLR_STR BLUE_STR "..." CLR_STR, buffer, conf->rev_proxy.port);
if (connect(rev_proxy.socket, (struct sockaddr *) &address, sizeof(address)) < 0) {
if (errno == ETIMEDOUT || errno == EINPROGRESS) {
res->status = http_get_status(504);
ctx->origin = SERVER_REQ;
} else if (errno == ECONNREFUSED) {
res->status = http_get_status(503);
ctx->origin = SERVER_REQ;
} else {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
}
print(ERR_STR "Unable to connect to [%s]:%i: %s" CLR_STR, buffer, conf->rev_proxy.port, strerror(errno));
sprintf(err_msg, "Unable to connect to server: %s.", strerror(errno));
goto proxy_err;
}
server_timeout.tv_sec = SERVER_TIMEOUT;
server_timeout.tv_usec = 0;
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_RCVTIMEO, &server_timeout, sizeof(server_timeout)) < 0)
goto rev_proxy_timeout_err;
if (setsockopt(rev_proxy.socket, SOL_SOCKET, SO_SNDTIMEO, &server_timeout, sizeof(server_timeout)) < 0) {
rev_proxy_timeout_err:
res->status = http_get_status(500);
ctx->origin = INTERNAL;
print(ERR_STR "Unable to set timeout for reverse proxy socket: %s" CLR_STR, strerror(errno));
sprintf(err_msg, "Unable to set timeout for reverse proxy socket: %s", strerror(errno));
goto proxy_err;
}
if (conf->rev_proxy.enc) {
rev_proxy.ssl = SSL_new(rev_proxy.ctx);
SSL_set_fd(rev_proxy.ssl, rev_proxy.socket);
SSL_set_connect_state(rev_proxy.ssl);
ret = SSL_do_handshake(rev_proxy.ssl);
rev_proxy._last_ret = ret;
rev_proxy._errno = errno;
rev_proxy._ssl_error = ERR_get_error();
rev_proxy.enc = 1;
if (ret < 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to perform handshake: %s.", sock_strerror(&rev_proxy));
goto proxy_err;
}
}
rev_proxy_host = conf->name;
print(BLUE_STR "Established new connection with " BLD_STR "[%s]:%i" CLR_STR, buffer, conf->rev_proxy.port);
rev_proxy:
ret = rev_proxy_request_header(req, (int) client->enc);
if (ret != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
return -1;
}
ret = http_send_request(&rev_proxy, req);
if (ret < 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to send request to server (1): %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
retry = tries < 4;
goto proxy_err;
}
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
if (content_length != NULL) {
unsigned long content_len = strtoul(content_length, NULL, 10);
if (client->buf_len - client->buf_off > 0) {
unsigned long len = client->buf_len - client->buf_off;
if (len > content_len) {
len = content_len;
}
ret = sock_send(&rev_proxy, client->buf, len, 0);
if (ret <= 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to send request to server (2): %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
retry = tries < 4;
goto proxy_err;
}
content_len -= len;
}
if (content_len > 0) {
ret = sock_splice(&rev_proxy, client, buffer, sizeof(buffer), content_len);
if (ret <= 0) {
if (ret == -1) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
print(ERR_STR "Unable to send request to server (3): %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to send request to server: %s.", sock_strerror(&rev_proxy));
goto proxy_err;
} else if (ret == -2) {
res->status = http_get_status(400);
ctx->origin = CLIENT_REQ;
print(ERR_STR "Unable to receive request from client: %s" CLR_STR, sock_strerror(client));
sprintf(err_msg, "Unable to receive request from client: %s.", sock_strerror(client));
return -1;
}
res->status = http_get_status(500);
ctx->origin = INTERNAL;
print(ERR_STR "Unknown Error" CLR_STR);
return -1;
}
}
}
ret = sock_recv(&rev_proxy, buffer, sizeof(buffer), MSG_PEEK);
if (ret <= 0) {
int enc_err = sock_enc_error(&rev_proxy);
if (errno == EAGAIN || errno == EINPROGRESS || enc_err == SSL_ERROR_WANT_READ ||
enc_err == SSL_ERROR_WANT_WRITE)
{
res->status = http_get_status(504);
ctx->origin = SERVER_RES;
} else {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
}
print(ERR_STR "Unable to receive response from server: %s" CLR_STR, sock_strerror(&rev_proxy));
sprintf(err_msg, "Unable to receive response from server: %s.", sock_strerror(&rev_proxy));
goto proxy_err;
}
char *buf = buffer;
unsigned short header_len = (unsigned short) (strstr(buffer, "\r\n\r\n") - buffer + 4);
if (header_len <= 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: End of header not found" CLR_STR);
sprintf(err_msg, "Unable to parser header: End of header not found.");
goto proxy_err;
}
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: Header contains illegal characters" CLR_STR);
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
goto proxy_err;
}
}
char *ptr = buf;
while (header_len != (ptr - buf)) {
char *pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
sprintf(err_msg, "Unable to parse header: Invalid header format.");
goto proxy_err;
}
if (ptr == buf) {
if (strncmp(ptr, "HTTP/", 5) != 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: Invalid header format" CLR_STR);
sprintf(err_msg, "Unable to parse header: Invalid header format.");
goto proxy_err;
}
int status_code = (int) strtol(ptr + 9, NULL, 10);
res->status = http_get_status(status_code);
if (res->status == NULL && status_code >= 100 && status_code <= 999) {
custom_status->code = status_code;
strcpy(custom_status->type, "");
snprintf(custom_status->msg, sizeof(custom_status->msg), "%.*s",
(int) (strchr(ptr, '\r') - ptr - 13), ptr + 13);
res->status = custom_status;
} else if (res->status == NULL) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header: Invalid or unknown status code" CLR_STR);
sprintf(err_msg, "Unable to parse header: Invalid or unknown status code.");
goto proxy_err;
}
} else {
ret = http_parse_header_field(&res->hdr, ptr, pos0);
if (ret != 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
print(ERR_STR "Unable to parse header" CLR_STR);
sprintf(err_msg, "Unable to parse header.");
goto proxy_err;
}
}
if (pos0[2] == '\r' && pos0[3] == '\n') {
break;
}
ptr = pos0 + 2;
}
sock_recv(&rev_proxy, buffer, header_len, 0);
ret = rev_proxy_response_header(req, res);
if (ret != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
return -1;
}
return 0;
proxy_err:
if (retry) goto retry;
return -1;
}
int rev_proxy_send(sock *client, unsigned long len_to_send, int flags) {
// TODO handle websockets
long ret;
char buffer[CHUNK_SIZE];
char comp_out[CHUNK_SIZE];
char buf[256];
long len, snd_len;
int finish_comp = 0;
char *ptr;
compress_ctx comp_ctx;
if (flags & REV_PROXY_COMPRESS_BR) {
flags &= ~REV_PROXY_COMPRESS_GZ;
if (compress_init(&comp_ctx, COMPRESS_BR) != 0) {
print(ERR_STR "Unable to init brotli: %s" CLR_STR, strerror(errno));
flags &= ~REV_PROXY_COMPRESS_BR;
}
} else if (flags & REV_PROXY_COMPRESS_GZ) {
flags &= ~REV_PROXY_COMPRESS_BR;
if (compress_init(&comp_ctx, COMPRESS_GZ) != 0) {
print(ERR_STR "Unable to init gzip: %s" CLR_STR, strerror(errno));
flags &= ~REV_PROXY_COMPRESS_GZ;
}
}
do {
snd_len = 0;
if (flags & REV_PROXY_CHUNKED) {
char *pos;
ret = sock_recv(&rev_proxy, buffer, 16, MSG_PEEK);
if (ret <= 0) goto err0;
len_to_send = strtol(buffer, NULL, 16);
pos = strstr(buffer, "\r\n");
len = pos - buffer + 2;
sock_recv(&rev_proxy, buffer, len, 0);
if (ret <= 0) {
err0:
print("Unable to receive from server: %s", sock_strerror(&rev_proxy));
break;
}
if (len_to_send == 0 && (flags & REV_PROXY_COMPRESS)) {
finish_comp = 1;
len = 0;
goto out;
finish:
compress_free(&comp_ctx);
}
}
while (snd_len < len_to_send) {
unsigned long avail_in, avail_out;
ret = sock_recv(&rev_proxy, buffer, CHUNK_SIZE < (len_to_send - snd_len) ? CHUNK_SIZE : len_to_send - snd_len, 0);
if (ret <= 0) {
print("Unable to receive from server: %s", sock_strerror(&rev_proxy));
break;
}
len = ret;
ptr = buffer;
out:
avail_in = len;
void *next_in = ptr;
do {
long buf_len = len;
if (flags & REV_PROXY_COMPRESS) {
avail_out = sizeof(comp_out);
compress_compress(&comp_ctx, next_in + len - avail_in, &avail_in, comp_out, &avail_out,
finish_comp);
ptr = comp_out;
buf_len = (int) (sizeof(comp_out) - avail_out);
snd_len += (long) (len - avail_in);
}
if (buf_len != 0) {
len = sprintf(buf, "%lX\r\n", buf_len);
ret = 1;
if (flags & REV_PROXY_CHUNKED) ret = sock_send(client, buf, len, 0);
if (ret <= 0) goto err;
ret = sock_send(client, ptr, buf_len, 0);
if (ret <= 0) goto err;
if (!(flags & REV_PROXY_COMPRESS)) snd_len += ret;
if (flags & REV_PROXY_CHUNKED) ret = sock_send(client, "\r\n", 2, 0);
if (ret <= 0) {
err:
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
break;
}
}
} while ((flags & REV_PROXY_COMPRESS) && (avail_in != 0 || avail_out != sizeof(comp_out)));
if (ret <= 0) break;
if (finish_comp) goto finish;
}
if (ret <= 0) break;
if (flags & REV_PROXY_CHUNKED) sock_recv(&rev_proxy, buffer, 2, 0);
} while ((flags & REV_PROXY_CHUNKED) && len_to_send > 0);
if (ret <= 0) return (int) -1;
if (flags & REV_PROXY_CHUNKED) {
ret = sock_send(client, "0\r\n\r\n", 5, 0);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
return -1;
}
}
return 0;
}
int rev_proxy_dump(char *buf, long len) {
sock_recv(&rev_proxy, buf, len, 0);
sock_close(&rev_proxy);
return 0;
}

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

@ -0,0 +1,38 @@
/**
* Necronda Web Server
* Reverse proxy (header file)
* src/lib/rev_proxy.h
* Lorenz Stechauner, 2021-01-07
*/
#ifndef NECRONDA_SERVER_REV_PROXY_H
#define NECRONDA_SERVER_REV_PROXY_H
#define REV_PROXY_CHUNKED 1
#define REV_PROXY_COMPRESS_GZ 2
#define REV_PROXY_COMPRESS_BR 4
#define REV_PROXY_COMPRESS 6
#ifndef SERVER_NAME
# define SERVER_NAME "revproxy"
#endif
#include "http.h"
#include "config.h"
extern sock rev_proxy;
int rev_proxy_preload();
int rev_proxy_request_header(http_req *req, int enc);
int rev_proxy_response_header(http_req *req, http_res *res);
int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_config *conf, sock *client,
http_status *custom_status, char *err_msg);
int rev_proxy_send(sock *client, unsigned long len_to_send, int flags);
int rev_proxy_dump(char *buf, long len);
#endif //NECRONDA_SERVER_REV_PROXY_H

118
src/lib/sock.c Normal file
View File

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

40
src/lib/sock.h Normal file
View File

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

218
src/lib/uri.c Normal file
View File

@ -0,0 +1,218 @@
/**
* Necronda Web Server
* URI and path handlers
* src/lib/uri.c
* Lorenz Stechauner, 2020-12-13
*/
#include "uri.h"
#include "utils.h"
#include <stdlib.h>
#include <string.h>
int path_is_directory(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode) != 0;
}
int path_is_file(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode) == 0;
}
int path_exists(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0;
}
int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mode) {
char buf0[1024];
char buf1[1024];
char buf2[1024];
char buf3[1024];
char buf4[1024];
int p_len;
uri->webroot = NULL;
uri->req_path = NULL;
uri->path = NULL;
uri->pathinfo = NULL;
uri->query = NULL;
uri->filename = NULL;
uri->uri = NULL;
uri->meta = NULL;
uri->is_static = 1;
uri->is_dir = 0;
if (uri_str[0] != '/') {
return 1;
}
uri->webroot = malloc(strlen(webroot) + 1);
strcpy(uri->webroot, webroot);
char *query = strchr(uri_str, '?');
if (query == NULL) {
uri->query = NULL;
} else {
query[0] = 0;
query++;
long size = (long) strlen(query) + 1;
uri->query = malloc(size);
strcpy(uri->query, query);
}
long size = (long) strlen(uri_str) + 1;
uri->req_path = malloc(size);
url_decode(uri_str, uri->req_path, &size);
if (query != NULL) {
query[-1] = '?';
}
if (strstr(uri->req_path, "/../") != NULL || strstr(uri->req_path, "/./") != NULL) {
return 2;
}
size = (long) strlen(uri->req_path) + 1;
uri->path = malloc(size);
uri->pathinfo = malloc(size);
char last = 0;
for (int i = 0, j = 0; i < size - 1; i++) {
char ch = uri->req_path[i];
if (last != '/' || ch != '/') {
uri->path[j++] = ch;
uri->path[j] = 0;
}
last = ch;
}
if (dir_mode == URI_DIR_MODE_NO_VALIDATION) {
return 0;
}
if (uri->path[strlen(uri->path) - 1] == '/') {
uri->path[strlen(uri->path) - 1] = 0;
strcpy(uri->pathinfo, "/");
} else {
strcpy(uri->pathinfo, "");
}
if (!path_exists(uri->webroot)) {
return 3;
}
while (1) {
sprintf(buf0, "%s%s", uri->webroot, uri->path);
p_len = snprintf(buf1, sizeof(buf1), "%s.ncr", buf0);
if (p_len < 0 || p_len >= sizeof(buf1)) return -1;
p_len = snprintf(buf2, sizeof(buf2), "%s.php", buf0);
if (p_len < 0 || p_len >= sizeof(buf2)) return -1;
p_len = snprintf(buf3, sizeof(buf3), "%s.html", buf0);
if (p_len < 0 || p_len >= sizeof(buf3)) return -1;
if (strlen(uri->path) <= 1 ||
path_exists(buf0) ||
path_is_file(buf1) ||
path_is_file(buf2) ||
path_is_file(buf3)) {
break;
}
char *ptr;
parent_dir:
ptr = strrchr(uri->path, '/');
size = (long) strlen(ptr);
sprintf(buf4, "%.*s%s", (int) size, ptr, uri->pathinfo);
strcpy(uri->pathinfo, buf4);
ptr[0] = 0;
}
if (uri->pathinfo[0] != 0) {
sprintf(buf4, "%s", uri->pathinfo + 1);
strcpy(uri->pathinfo, buf4);
}
if (path_is_file(buf0)) {
uri->filename = malloc(strlen(buf0) + 1);
strcpy(uri->filename, buf0);
long len = (long) strlen(uri->path);
if (strcmp(uri->path + len - 4, ".ncr") == 0 || strcmp(uri->path + len - 4, ".php") == 0) {
uri->path[len - 4] = 0;
uri->is_static = 0;
} else if (strcmp(uri->path + len - 5, ".html") == 0) {
uri->path[len - 5] = 0;
}
} else if (path_is_file(buf1)) {
uri->is_static = 0;
uri->filename = malloc(strlen(buf1) + 1);
strcpy(uri->filename, buf1);
} else if (path_is_file(buf2)) {
uri->is_static = 0;
uri->filename = malloc(strlen(buf2) + 1);
strcpy(uri->filename, buf2);
} else if (path_is_file(buf3)) {
uri->filename = malloc(strlen(buf3) + 1);
strcpy(uri->filename, buf3);
} else {
uri->is_dir = 1;
strcpy(uri->path + strlen(uri->path), "/");
sprintf(buf1, "%s%sindex.ncr", uri->webroot, uri->path);
sprintf(buf2, "%s%sindex.php", uri->webroot, uri->path);
sprintf(buf3, "%s%sindex.html", uri->webroot, uri->path);
if (path_is_file(buf1)) {
uri->filename = malloc(strlen(buf1) + 1);
strcpy(uri->filename, buf1);
uri->is_static = 0;
} else if (path_is_file(buf2)) {
uri->filename = malloc(strlen(buf2) + 1);
strcpy(uri->filename, buf2);
uri->is_static = 0;
} else if (path_is_file(buf3)) {
uri->filename = malloc(strlen(buf3) + 1);
strcpy(uri->filename, buf3);
} else {
if (dir_mode == URI_DIR_MODE_FORBIDDEN) {
uri->is_static = 1;
} else if (dir_mode == URI_DIR_MODE_LIST) {
uri->is_static = 0;
} else if (dir_mode == URI_DIR_MODE_INFO) {
if (strlen(uri->path) > 1) {
uri->path[strlen(uri->path) - 1] = 0;
sprintf(buf0, "/%s", uri->pathinfo);
strcpy(uri->pathinfo, buf0);
goto parent_dir;
}
}
}
}
if (strcmp(uri->path + strlen(uri->path) - 5, "index") == 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,
(strlen(uri->pathinfo) == 0 || uri->path[strlen(uri->path) - 1] == '/') ? "" : "/", uri->pathinfo,
uri->query != NULL ? "?" : "", uri->query != NULL ? uri->query : "");
uri->uri = malloc(strlen(buf0) + 1);
strcpy(uri->uri, buf0);
return 0;
}
void uri_free(http_uri *uri) {
if (uri->webroot != NULL) free(uri->webroot);
if (uri->req_path != NULL) free(uri->req_path);
if (uri->path != NULL) free(uri->path);
if (uri->pathinfo != NULL) free(uri->pathinfo);
if (uri->query != NULL) free(uri->query);
if (uri->filename != NULL) free(uri->filename);
if (uri->uri != NULL) free(uri->uri);
uri->webroot = NULL;
uri->req_path = NULL;
uri->path = NULL;
uri->pathinfo = NULL;
uri->query = NULL;
uri->filename = NULL;
uri->uri = NULL;
}

47
src/lib/uri.h Normal file
View File

@ -0,0 +1,47 @@
/**
* Necronda Web Server
* URI and path handlers (header file)
* src/lib/uri.h
* Lorenz Stechauner, 2020-12-13
*/
#ifndef NECRONDA_SERVER_URI_H
#define NECRONDA_SERVER_URI_H
#include <sys/stat.h>
#define URI_DIR_MODE_NO_VALIDATION 0
#define URI_DIR_MODE_FORBIDDEN 1
#define URI_DIR_MODE_LIST 2
#define URI_DIR_MODE_INFO 3
typedef struct {
char etag[64];
char type[24];
char charset[16];
char filename_comp_gz[256];
char filename_comp_br[256];
struct stat stat;
} meta_data;
typedef struct {
char *webroot; // "/srv/www/www.test.org"
char *req_path; // "/account/login"
char *path; // "/account/"
char *pathinfo; // "login"
char *query; // "username=test"
char *filename; // "/account/index.php"
char *uri; // "/account/login?username=test"
meta_data *meta;
unsigned char is_static:1;
unsigned char is_dir:1;
} http_uri;
int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mode);
int uri_init_cache(http_uri *uri);
void uri_free(http_uri *uri);
#endif //NECRONDA_SERVER_URI_H

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

@ -0,0 +1,145 @@
/**
* Necronda Web Server
* Utilities
* src/lib/utils.c
* Lorenz Stechauner, 2020-12-03
*/
#include "utils.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *log_prefix;
char *format_duration(unsigned long micros, char *buf) {
if (micros < 10000) {
sprintf(buf, "%.1f ms", (double) micros / 1000);
} else if (micros < 1000000) {
sprintf(buf, "%li ms", micros / 1000);
} else if (micros < 60000000) {
sprintf(buf, "%.1f s", (double) micros / 1000000);
} else if (micros < 6000000000) {
sprintf(buf, "%.1f min", (double) micros / 1000000 / 60);
} else {
sprintf(buf, "%li min", micros / 1000000 / 60);
}
return buf;
}
int url_encode_component(const char *str, char *enc, long *size) {
char *ptr = enc;
char ch;
memset(enc, 0, *size);
for (int i = 0; i < strlen(str); i++, ptr++) {
if ((ptr - enc) >= *size) {
return -1;
}
ch = str[i];
if (ch == ':' || ch == '/' || ch == '?' || ch == '#' || ch == '[' || ch == ']' || ch == '@' || ch == '!' ||
ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '+' || ch == ',' ||
ch == ';' || ch == '=' || ch < ' ' || ch > '~') {
if ((ptr - enc + 2) >= *size) {
return -1;
}
sprintf(ptr, "%%%02X", ch);
ptr += 2;
} else if (ch == ' ') {
ptr[0] = '+';
} else {
ptr[0] = ch;
}
}
*size = ptr - enc;
return 0;
}
int url_encode(const char *str, char *enc, long *size) {
char *ptr = enc;
unsigned char ch;
memset(enc, 0, *size);
for (int i = 0; i < strlen(str); i++, ptr++) {
if ((ptr - enc) >= *size) {
return -1;
}
ch = str[i];
if (ch > 0x7F || ch == ' ') {
if ((ptr - enc + 2) >= *size) {
return -1;
}
sprintf(ptr, "%%%02X", ch);
ptr += 2;
} else {
ptr[0] = (char) ch;
}
}
*size = ptr - enc;
return 0;
}
int url_decode(const char *str, char *dec, long *size) {
char *ptr = dec;
char ch, buf[3];
memset(dec, 0, *size);
for (int i = 0; i < strlen(str); i++, ptr++) {
if ((ptr - dec) >= *size) {
return -1;
}
ch = str[i];
if (ch == '+') {
ch = ' ';
} else if (ch == '%') {
memcpy(buf, str + i + 1, 2);
buf[2] = 0;
ch = (char) strtol(buf, NULL, 16);
i += 2;
} else if (ch == '?') {
strcpy(ptr, str + i);
break;
}
ptr[0] = ch;
}
*size = ptr - dec;
return 0;
}
int mime_is_compressible(const char *type) {
if (type == NULL) return 0;
char type_parsed[64];
snprintf(type_parsed, sizeof(type_parsed), "%s", type);
char *pos = strchr(type_parsed, ';');
if (pos != NULL) pos[0] = 0;
return
strncmp(type_parsed, "text/", 5) == 0 ||
strncmp(type_parsed, "message/", 7) == 0 ||
strstr(type_parsed, "+xml") != NULL ||
strstr(type_parsed, "+json") != NULL ||
strcmp(type_parsed, "application/javascript") == 0 ||
strcmp(type_parsed, "application/json") == 0 ||
strcmp(type_parsed, "application/xml") == 0 ||
strcmp(type_parsed, "application/x-www-form-urlencoded") == 0 ||
strcmp(type_parsed, "application/x-tex") == 0 ||
strcmp(type_parsed, "application/x-httpd-php") == 0 ||
strcmp(type_parsed, "application/x-latex") == 0 ||
strcmp(type_parsed, "application/vnd.ms-fontobject") == 0 ||
strcmp(type_parsed, "application/x-font-ttf") == 0 ||
strcmp(type_parsed, "application/x-javascript") == 0 ||
strcmp(type_parsed, "application/x-web-app-manifest+json") == 0 ||
strcmp(type_parsed, "font/eot") == 0 ||
strcmp(type_parsed, "font/opentype") == 0 ||
strcmp(type_parsed, "image/bmp") == 0 ||
strcmp(type_parsed, "image/gif") == 0 ||
strcmp(type_parsed, "image/vnd.microsoft.icon") == 0 ||
strcmp(type_parsed, "image/vnd.microsoft.iconbinary") == 0 ||
strcmp(type_parsed, "image/x-icon") == 0;
}
int strcpy_rem_webroot(char *dst, const char *src, long len, const char *webroot) {
strncpy(dst, src, len);
if (webroot == NULL) return 0;
char *pos;
while ((pos = strstr(dst, webroot)) != NULL) {
strcpy(pos, pos + strlen(webroot));
}
return 0;
}

45
src/lib/utils.h Normal file
View File

@ -0,0 +1,45 @@
/**
* Necronda Web Server
* Utilities (header file)
* src/lib/utils.h
* Lorenz Stechauner, 2020-12-03
*/
#ifndef NECRONDA_SERVER_UTILS_H
#define NECRONDA_SERVER_UTILS_H
#include <stdio.h>
#define ERR_STR "\x1B[1;31m"
#define CLR_STR "\x1B[0m"
#define BLD_STR "\x1B[1m"
#define WRN_STR "\x1B[1;33m"
#define BLUE_STR "\x1B[34m"
#define HTTP_STR "\x1B[1;31m"
#define HTTPS_STR "\x1B[1;32m"
extern char *log_prefix;
#define out_1(fmt) fprintf(stdout, "%s" fmt "\n", log_prefix)
#define out_2(fmt, args...) fprintf(stdout, "%s" fmt "\n", log_prefix, args)
#define out_x(x, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, FUNC, ...) FUNC
#define print(...) out_x(, ##__VA_ARGS__, out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), out_2(__VA_ARGS__), \
out_2(__VA_ARGS__), out_1(__VA_ARGS__))
char *format_duration(unsigned long micros, char *buf);
int url_encode_component(const char *str, char *enc, long *size);
int url_encode(const char *str, char *enc, long *size);
int url_decode(const char *str, char *dec, long *size);
int mime_is_compressible(const char *type);
int strcpy_rem_webroot(char *dst, const char *str, long len, const char *webroot);
#endif //NECRONDA_SERVER_UTILS_H

419
src/necronda-server.c Normal file
View File

@ -0,0 +1,419 @@
/**
* 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;
}

View File

@ -1,284 +0,0 @@
/**
* Necronda Web Server 3.0
* necronda-server.cpp - Main Executable
* Lorenz Stechauner, 2018-05-09
*/
#include "necronda-server.h"
#include <magic.h>
#include <iostream>
#include <thread>
#include <sys/time.h>
#include <sys/stat.h>
#include <signal.h>
#include <csignal>
using namespace std;
const char* webroot = "/srv/necronda/";
string getMimeType(string path) {
unsigned long pos = path.find_last_of('.');
string ext;
if (pos != string::npos) {
ext = path.substr(pos + 1, path.length() - pos);
}
magic_t magic = magic_open(MAGIC_MIME_TYPE);
magic_load(magic, "/usr/share/misc/magic.mgc");
string type = magic_file(magic, path.c_str());
magic_setflags(magic, MAGIC_MIME_ENCODING);
string charset = magic_file(magic, path.c_str());
if (type == "text/plain") {
if (ext == "css") {
type = "text/css";
} else if (ext == "js") {
type = "text/javascript";
}
}
magic_close(magic);
return type + "; charset=" + charset;
}
/**
* Sun, 06 Nov 1994 08:49:37 GMT
* @return
*/
std::string getTimestamp(string path) {
struct stat attrib;
stat(path.c_str(), &attrib);
return getTimestamp(attrib.st_ctime);
}
std::string getTimestamp(time_t time) {
char buffer[64];
struct tm *timeinfo = gmtime(&time);
strftime(buffer, sizeof(buffer), "%Y%m%d%H%M%S", timeinfo);
return string(buffer);
}
long getFileSize(string filename) {
struct stat stat_buf;
int rc = stat(filename.c_str(), &stat_buf);
return rc == 0 ? stat_buf.st_size : -1;
}
/**
* Returns a formatted time string
* @param micros Delta time to be formatted
* @return A formatted time string
*/
std::string formatTime(long micros) {
char buffer[64];
if (micros < 1000) {
sprintf(buffer, "%.3f ms", micros / 1000.0);
} else if (micros < 10000) {
sprintf(buffer, "%.2f ms", micros / 1000.0);
} else if (micros < 100000) {
sprintf(buffer, "%.1f ms", micros / 1000.0);
} else if (micros < 1000000) {
sprintf(buffer, "%.0f ms", micros / 1000.0);
} else {
sprintf(buffer, "%.1f s", micros / 1000000.0);
}
return std::string(buffer);
}
std::string formatSize(unsigned long bytes) {
char buffer[64];
if (bytes > 0x10000000000) {
sprintf(buffer, "%.1f TiB", (double) bytes / 0x10000000000);
} else if (bytes > 0x40000000) {
sprintf(buffer, "%.1f GiB", (double) bytes / 0x40000000);
} else if (bytes > 0x100000) {
sprintf(buffer, "%.1f MiB", (double) bytes / 0x100000);
} else if (bytes > 0x400) {
sprintf(buffer, "%.1f KiB", (double) bytes / 0x400);
} else {
sprintf(buffer, "%ld B", bytes);
}
return std::string(buffer);
}
string url_decode(string url) {
long pos = 0;
while ((pos = url.find('+', pos + 1)) != string::npos) {
url.replace(pos, 1, 1, ' ');
}
pos = 0;
while ((pos = url.find('%', pos + 1)) != string::npos) {
const char *num = url.substr(pos + 1, 2).c_str();
auto c = (char) strtol(num, nullptr, 16);
url.erase(pos, 3);
url.insert(pos, 1, c);
}
return url;
}
string url_encode(string url) {
char buff[4];
for (long pos = 0; pos < url.length(); pos++) {
auto c = (unsigned char) url[pos];
if (c < ' ' || c > '~' || c == ' ' || c == '#' || c == '?' || c == '&' || c == '=' || c == '\\' || c == '%') {
sprintf(buff, "%%%02X", c);
url.replace(pos, 1, buff);
}
}
return url;
}
string html_decode(string text) {
return text;
}
string html_encode(string text) {
return text;
}
string cli_encode(string text) {
char buff[5];
for (long pos = 0; pos < text.length(); pos++) {
auto c = (unsigned char) text[pos];
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == ',' || c == '.' || c == '_' || c == '+' || c == ':' || c == '@' || c == '%' || c == '/' || c == '-')) {
sprintf(buff, "\\%.1s", &c);
text.replace(pos, 1, buff);
pos++;
}
}
return text;
}
string read_line(FILE* file) {
char *line;
size_t len = 0;
ssize_t read;
if ((read = getline(&line, &len, file)) < 0 || line == nullptr) {
return "";
}
string l = string(line);
if (l[l.length()-1] == '\n') {
l.erase(l.length()-1);
}
if (l[l.length()-1] == '\r') {
l.erase(l.length()-1);
}
return l;
}
#include "procopen.cpp"
#include "network/Address.cpp"
#include "network/Socket.cpp"
#include "URI.cpp"
#include "network/http/Http.cpp"
#include "network/http/HttpStatusCode.cpp"
#include "network/http/HttpHeader.cpp"
#include "network/http/HttpRequest.cpp"
#include "network/http/HttpResponse.cpp"
#include "network/http/HttpConnection.cpp"
string getWebRoot(string host) {
string root = webroot + host;
if (fileExists(root)) {
return root;
} else {
return "";
}
}
#include "client.cpp"
long clientnum = 0;
int main() {
cout << "Necronda Server 3.0" << endl << "by Lorenz Stechauner" << endl << endl;
signal(SIGPIPE, SIG_IGN);
SSL_load_error_strings();
SSL_library_init();
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
int ret = system("mkdir -p /var/necronda /etc/necronda /tmp/necronda; touch /var/necronda/ETags");
if (ret != 0) {
cout << "Unable to create server files" << endl;
exit(1);
}
list<unsigned short> ports = {80, 443};
list<Socket> servers = {};
auto it = ports.begin();
for (int i = 0; i < ports.size(); i++) {
unsigned short port = *it;
advance(it, 1);
Socket server = Socket();
servers.push_back(server);
try {
server.setReuseAddress(true);
server.setReceiveTimeout(0);
server.setSendTimeout(0);
} catch (char *msg) {
cout << "Unable to set socket option: " << msg << endl;
exit(2);
}
try {
server.bind(port);
} catch (char *msg) {
cout << "Unable to bind socket to port " << port << ": " << msg << endl;
exit(3);
}
try {
server.listen(256);
} catch (char *msg) {
cout << "Unable to listen on socket: " << msg << endl;
exit(4);
}
}
cout << "Ready for connections" << endl;
while (true) {
try {
Socket::select(servers, {});
for (Socket server : servers) {
try {
Socket *socket = server.accept();
clientnum++;
thread *t = new thread(client_handler, socket, clientnum, server.getSocketPort() == 443);
} catch (char *msg) {
// Nothing
}
}
} catch (char *msg) {
cout << "Select: " << msg << endl;
break;
}
}
return 0;
}

View File

@ -1,41 +1,34 @@
//
// Created by lorenz on 5/17/18.
//
/**
* Necronda Web Server
* Main executable (header file)
* src/necronda-server.h
* Lorenz Stechauner, 2020-12-03
*/
#include <string>
#ifndef NECRONDA_SERVER_NECRONDA_SERVER_H
#define NECRONDA_SERVER_NECRONDA_SERVER_H
#ifndef NECRONDA_SERVER
#define NECRONDA_SERVER
#include <sys/time.h>
#include <maxminddb.h>
using namespace std;
#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
unsigned long getMicros();
#define CHUNK_SIZE 8192
string formatTime(long micros);
extern int sockets[NUM_SOCKETS];
extern pid_t children[MAX_CHILDREN];
extern MMDB_s mmdbs[MAX_MMDB];
string formatSize(unsigned long bytes);
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;
string getWebRoot(string host);
string getMimeType(string path);
string getTimestamp(string path);
string getTimestamp(time_t time);
long getFileSize(string filename);
string url_decode(string url);
string url_encode(string url);
string html_decode(string text);
string html_encode(string text);
string cli_encode(string text);
string read_line(FILE *file);
#endif
#endif //NECRONDA_SERVER_NECRONDA_SERVER_H

23
src/necronda.h Normal file
View File

@ -0,0 +1,23 @@
/**
* Necronda Web Server
* Definitions
* src/necronda.h
* Lorenz Stechauner, 2021-05-04
*/
#ifndef NECRONDA_SERVER_NECRONDA_H
#define NECRONDA_SERVER_NECRONDA_H
#define NECRONDA_VERSION "4.5"
#define SERVER_STR "Necronda/" NECRONDA_VERSION
#define SERVER_STR_HTML "Necronda&nbsp;web&nbsp;server&nbsp;" NECRONDA_VERSION
#ifndef DEFAULT_HOST
# define DEFAULT_HOST "www.necronda.net"
#endif
#ifndef SERVER_NAME
# define SERVER_NAME DEFAULT_HOST
#endif
#endif //NECRONDA_SERVER_NECRONDA_H

View File

@ -1,68 +0,0 @@
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstring>
#include "Address.h"
using namespace std;
Address::Address() {
}
Address::Address(string addr) {
// TODO
}
Address::Address(struct sockaddr_in *addr) {
address = ntohl(addr->sin_addr.s_addr);
}
struct sockaddr_in Address::toStruct(unsigned short port)const {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(address);
addr.sin_port = htons(port);
return addr;
}
string Address::toString() const {
struct sockaddr_in addr = toStruct(0);
struct in_addr ipAddr = addr.sin_addr;
return inet_ntoa(ipAddr);
}
bool Address::isLocal() {
string a = toString();
return a.find("127.0.0.") == 0;
}
ostream& operator<<(ostream &str, const Address &addr) {
return str << addr.toString();
}
string operator+(string &str, const Address &addr) {
return str + addr.toString();
}
string operator+(string &str, const Address *addr) {
return str + addr->toString();
}
string operator+(const Address &addr, string &str) {
return addr.toString() + str;
}
string operator+(const Address *addr, string &str) {
return addr->toString() + str;
}

View File

@ -1,31 +0,0 @@
/**
* Necronda Web Server 3.0
* HttpHeader.h - HttpHeader Class definition
* Lorenz Stechauner, 2018-05-09
*/
#ifndef NECRONDA_ADDRESS
#define NECRONDA_ADDRESS
using namespace std;
class Address {
private:
unsigned int address;
public:
Address();
explicit Address(string address);
explicit Address(struct sockaddr_in *address);
struct sockaddr_in toStruct(unsigned short port) const;
string toString() const;
bool isLocal();
};
#endif

View File

@ -1,661 +0,0 @@
/**
* Necronda Web Server 3.0
* Socket.cpp - Socket Class methods
* Lorenz Stechauner, 2018-05-09
*/
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstring>
#include <utility>
#include <unistd.h>
#include <sstream>
#include <ctime>
#include <poll.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <list>
#include <sys/stat.h>
#include "Address.h"
#include "Socket.h"
#include "http/Http.h"
using namespace std;
static void multi_ssl_init() {
SSL_load_error_strings();
SSL_library_init();
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
}
char *multi_ssl_get_error(SSL *ssl, int ret) {
if (ret > 0) {
return nullptr;
}
unsigned long ret2 = ERR_get_error();
const char *err2 = strerror(errno);
const char *err1 = ERR_reason_error_string(ret2);
switch (SSL_get_error(ssl, ret)) {
case SSL_ERROR_NONE:
return (char *) "none";
case SSL_ERROR_ZERO_RETURN:
return (char *) "closed";
case SSL_ERROR_WANT_READ:
return (char *) "want_read";
case SSL_ERROR_WANT_WRITE:
return (char *) "want_write";
case SSL_ERROR_WANT_CONNECT:
return (char *) "want_connect";
case SSL_ERROR_WANT_ACCEPT:
return (char *) "want_accept";
case SSL_ERROR_WANT_X509_LOOKUP:
return (char *) "want_x509_lookup";
case SSL_ERROR_SYSCALL:
return (char *) ((ret2 == 0) ? (ret == 0) ? "protocol violation" : err2 : err1);
case SSL_ERROR_SSL:
return (char *) err1;
default:
return (char *) "unknown error";
}
}
char *strerror_socket(int nr) {
if (nr == EAGAIN || nr == EWOULDBLOCK) {
return (char *) "timeout";
} else if (nr == ECONNRESET) {
return (char *) "closed";
} else {
return strerror(nr);
}
}
Socket::Socket(int fd) {
this->fd = fd;
microsStart = getMicros();
microsLast = microsStart;
bytesSent = 0;
bytesReceived = 0;
enc = false;
ssl = nullptr;
ctx = nullptr;
clients = false;
servers = false;
}
Socket::Socket() {
fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (fd == 0) {
throw strerror(errno);
}
enc = false;
microsStart = getMicros();
microsLast = microsStart;
bytesSent = 0;
bytesReceived = 0;
ssl = nullptr;
ctx = nullptr;
clients = false;
servers = false;
}
int Socket::getFd() {
return fd;
}
void Socket::setSocketOption(int option, bool value = true) {
int val = value ? 1 : 0;
if (::setsockopt(fd, SOL_SOCKET, option, &val, sizeof(val)) != 0) {
throw strerror(errno);
}
}
void Socket::bind(Address *address, unsigned short port) {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY; // address.
addr.sin_port = htons(port);
if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
throw strerror(errno);
}
}
void Socket::bind(unsigned short port) {
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
throw strerror(errno);
}
}
void Socket::listen(int num) {
if (::listen(fd, num) != 0) {
throw strerror(errno);
}
}
void Socket::connect(Address, unsigned short) {
}
Socket* Socket::accept() {
int newfd = ::accept(fd, nullptr, nullptr);
if (newfd < 0) {
throw strerror(errno);
}
Socket *socket = new Socket(newfd);
socket->servers = true;
return socket;
}
void Socket::close() {
if (isSecured()) {
//SSL_shutdown(ssl);
SSL_free(ssl);
SSL_CTX_free(ctx);
}
if (::close(fd) != 0) {
throw strerror(errno);
}
}
Address *Socket::getPeerAddress() const {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
getpeername(fd, (struct sockaddr *) &addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *) &addr;
return new Address(s);
}
unsigned short Socket::getPeerPort() const {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
getpeername(fd, (struct sockaddr *) &addr, &len);
return ntohs(((struct sockaddr_in *) &addr)->sin_port);
}
Address *Socket::getSocketAddress() const {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
getsockname(fd, (struct sockaddr *) &addr, &len);
struct sockaddr_in *s = (struct sockaddr_in *) &addr;
return new Address(s);
}
unsigned short Socket::getSocketPort() const {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
getsockname(fd, (struct sockaddr *) &addr, &len);
return ntohs(((struct sockaddr_in *) &addr)->sin_port);
}
void Socket::setReuseAddress(bool value) {
setSocketOption(SO_REUSEADDR, value);
}
void Socket::setReusePort(bool value) {
setSocketOption(SO_REUSEPORT, value);
}
string Socket::toString() const {
return "{[Socket]" + getSocketAddress()->toString() + ":" + to_string(getSocketPort()) + "<->" +
getPeerAddress()->toString() + ":" + to_string(getPeerPort()) + "}";
}
long Socket::send(string *str) {
return send(str->c_str(), str->length());
}
long Socket::send(string str) {
return send(str.c_str(), str.length());
}
long Socket::send(const char *str, long length) {
return send((void*) str, length);
}
long Socket::send(const char *str) {
return send(str, strlen(str));
}
long Socket::send(FILE *file) {
char buffer[CPPNET_CHUNK];
long all_len = 0;
long len = 0;
do {
len = fread(buffer, 1, CPPNET_CHUNK, file);
send(buffer, len);
all_len += len;
} while (len > 0 && len == CPPNET_CHUNK);
return all_len;
}
Socket::~Socket() {
}
long Socket::receive(void *buffer, int size) {
long len;
if (isSecured()) {
len = SSL_read(ssl, buffer, size);
if (len < 0) {
throw multi_ssl_get_error(ssl, (int) len);
}
} else {
len = recv(fd, buffer, (size_t) size, 0);
if (len < 0) {
throw strerror_socket(errno);
}
}
bytesReceived += len;
return len;
}
long Socket::peek(void *buffer, int size) {
long len;
if (isSecured()) {
len = SSL_peek(ssl, buffer, size);
if (len < 0) {
throw multi_ssl_get_error(ssl, (int) len);
}
} else {
len = recv(fd, buffer, (size_t) size, MSG_PEEK);
if (len < 0) {
throw strerror_socket(errno);
}
}
return len;
}
long Socket::send(void *buffer, int size) {
long len;
if (isSecured()) {
if (size != 0) {
len = SSL_write(ssl, buffer, size);
if (len <= 0) {
throw multi_ssl_get_error(ssl, (int) len);
}
} else {
len = 0;
}
} else {
len = ::send(fd, buffer, (size_t) size, 0);
if (len < 0) {
throw strerror_socket(errno);
}
}
bytesSent += len;
return len;
}
string Socket::receive() {
string *str = new string();
char buffer[CPPNET_CHUNK];
long len = 0;
do {
len = receive((void*) buffer, CPPNET_CHUNK);
str->append(buffer, (unsigned) len);
} while (len > 0 && len == CPPNET_CHUNK);
return *str;
}
string Socket::receive(long length) {
string *str = new string();
char buffer[CPPNET_CHUNK];
long len = 0;
long reclen = 0;
do {
len = receive((void*) buffer, CPPNET_CHUNK);
reclen += len;
str->append(buffer, (unsigned) len);
} while (reclen < length);
return *str;
}
string Socket::receive(string until) {
string *str = new string();
struct pollfd ufds[1];
ufds[0].fd = fd;
ufds[0].events = POLLIN | POLLOUT;
char buffer[CPPNET_CHUNK];
long len = 0;
do {
len = peek((void*) buffer, CPPNET_CHUNK);
if (len != 0) {
string s = string(buffer, (size_t) len);
size_t found = s.find(until);
long l = (found != string::npos) ? found + 1 : len;
long l2 = (found != string::npos) ? found : len;
str->append(buffer, (unsigned) l2);
receive((void *) buffer, (int) l);
if (found != string::npos) {
break;
}
}
if (poll(ufds, 1, 0) < 0) {
throw strerror_socket(errno);
} else if ((ufds[0].revents & POLLIN) == 0) {
if ((ufds[0].revents & POLLOUT) != 0) {
throw (char *) "error";
} else {
throw (char *) "want_write";
}
} else if ((ufds[0].revents & POLLERR) != 0) {
throw (char *) "error";
} else if (ufds[0].revents & (POLLRDHUP | POLLHUP | POLLNVAL) != 0) {
throw (char *) "closed";
}
} while (true);
return *str;
}
string Socket::receive(const char *until) {
return receive(until, (int) (strlen(until)));
}
string Socket::receive(const char *until, unsigned long strlen) {
return receive(string(until, strlen));
}
long Socket::receive(FILE *file) {
char buffer[CPPNET_CHUNK];
long len;
long rec = 0;
do {
len = receive((void*) buffer, CPPNET_CHUNK);
fwrite(buffer, 1, CPPNET_CHUNK, file);
rec += len;
} while (len > 0 && len == CPPNET_CHUNK);
return len;
}
long Socket::receive(FILE *file, long size) {
char buffer[CPPNET_CHUNK];
long len = 0;
long rec = 0;
while (size > rec) {
len = receive((void*) buffer, (CPPNET_CHUNK > (size - rec) && size >= 0)?(size - rec):CPPNET_CHUNK);
fwrite(buffer, 1, len, file);
rec += len;
}
return rec;
}
string Socket::receiveLine() {
string str = receive("\n");
if (str.length() > 0 && str.at(str.length() - 1) == '\r') {
str = str.substr(0, str.length() - 1);
}
return str;
}
long Socket::getDuration() {
return getMicros() - microsStart;
}
void Socket::setReceiveTimeout(unsigned long ms) {
struct timeval timeout;
if (ms == 0) {
timeout.tv_sec = 0;
timeout.tv_usec = 1;
} else {
timeout.tv_sec = ms / 1000;
timeout.tv_usec = (ms % 1000) * 1000;
}
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout)) < 0) {
throw strerror(errno);
}
}
void Socket::setSendTimeout(unsigned long ms) {
struct timeval timeout;
if (ms == 0) {
timeout.tv_sec = 0;
timeout.tv_usec = 1;
} else {
timeout.tv_sec = ms / 1000;
timeout.tv_usec = (ms % 1000) * 1000;
}
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout, sizeof(timeout)) < 0) {
throw strerror(errno);
}
}
bool Socket::isServerSide() {
return servers;
}
bool Socket::isSecured() {
return enc;
}
bool Socket::isClientSide() {
return clients;
}
void Socket::sslHandshake(map<string, KeyPair> sni) {
/*if (isSecured()) {
throw (char *) "Socket already secured";
}
const SSL_METHOD *method;
if (isServerSide()) {
method = TLSv1_2_server_method();
} else if (isClientSide()) {
method = TLSv1_2_client_method();
} else {
method = TLSv1_2_method();
}
SSL_CTX *ctx = SSL_CTX_new(method);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
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);
const char *certfile = keypair.fullchain.c_str();
const char *keyfile = keypair.privkey.c_str();
if (isServerSide()) {
if (SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM) != 1) {
throw (char *) ERR_reason_error_string(ERR_get_error());
}
if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) != 1) {
throw (char *) ERR_reason_error_string(ERR_get_error());
}
}
SSL_CTX_set_tlsext_servername_callback
this->ctx = ctx;
this->ssl = SSL_new(ctx);
SSL_set_fd(ssl, fd);
enc = true;
while (true) {
int ret = 0;
if (isServerSide()) {
ret = SSL_accept(ssl);
} else if (isClientSide()) {
ret = SSL_connect(ssl);
} else {
ret = SSL_do_handshake(ssl);
}
if (ret <= 0 && ((isServerSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_READ) ||
(isClientSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_WRITE))) {
throw multi_ssl_get_error(ssl, ret);
} else if (ret == 1) {
break;
}
}*/
}
void Socket::sslHandshake() {
sslHandshake(KeyPair{"", ""});
}
void Socket::sslHandshake(KeyPair keypair) {
if (isSecured()) {
throw (char *) "Socket already secured";
}
const SSL_METHOD *method;
if (isServerSide()) {
method = TLSv1_2_server_method();
} else if (isClientSide()) {
method = TLSv1_2_client_method();
} else {
method = TLSv1_2_method();
}
SSL_CTX *ctx = SSL_CTX_new(method);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr);
SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION); // TLS1_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);
const char *certfile = keypair.fullchain.c_str();
const char *keyfile = keypair.privkey.c_str();
if (isServerSide()) {
if (SSL_CTX_use_certificate_chain_file(ctx, certfile) != 1) {
throw (char *) ERR_reason_error_string(ERR_get_error());
}
if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) != 1) {
throw (char *) ERR_reason_error_string(ERR_get_error());
}
}
this->ctx = ctx;
this->ssl = SSL_new(ctx);
SSL_set_fd(ssl, fd);
enc = true;
while (true) {
int ret = 0;
if (isServerSide()) {
ret = SSL_accept(ssl);
} else if (isClientSide()) {
ret = SSL_connect(ssl);
} else {
ret = SSL_do_handshake(ssl);
}
if (ret <= 0 && ((isServerSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_READ) ||
(isClientSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_WRITE))) {
throw multi_ssl_get_error(ssl, ret);
} else if (ret == 1) {
break;
}
}
}
void Socket::sslHandshake(string privkey, string fullchain) {
sslHandshake(KeyPair{std::move(privkey), std::move(fullchain)});
}
long Socket::select(list<Socket> read, list<Socket> write, long millis) {
fd_set readfd, writefd;
int maxfd = 0;
FD_ZERO(&readfd);
FD_ZERO(&writefd);
for (Socket s : read) {
if (s.fd > maxfd) {
maxfd = s.fd;
}
FD_SET(s.fd, &readfd);
}
for (Socket s : write) {
if (s.fd > maxfd) {
maxfd = s.fd;
}
FD_SET(s.fd, &writefd);
}
struct timeval *tv = new struct timeval;
if (millis < 0) {
tv = nullptr;
} else if (millis == 0) {
tv->tv_sec = 0;
tv->tv_usec = 1;
} else {
tv->tv_sec = millis / 1000;
tv->tv_usec = (millis % 1000) * 1000;
}
int ret = ::select(maxfd + 1, &readfd, &writefd, nullptr, tv);
if (ret < 0) {
throw (char *) strerror(errno);
}
return ret;
}
long Socket::select(list<Socket> read, list<Socket> write) {
Socket::select(std::move(read), std::move(write), -1);
}
unsigned long Socket::getBytesSent() {
return bytesSent;
}
unsigned long Socket::getBytesReceived() {
return bytesReceived;
}
ostream &operator<<(ostream &str, const Socket &socket) {
return str << socket.toString();
}
ostream &operator<<(ostream &str, const Socket *socket) {
return str << socket->toString();
}
string operator+(string &str, const Socket &socket) {
return str + socket.toString();
}
string operator+(const Socket &socket, string &str) {
return socket.toString() + str;
}

View File

@ -1,174 +0,0 @@
/**
* Necronda Web Server 3.0
* Socket.h - Socket Class definition
* Lorenz Stechauner, 2018-05-09
*/
#ifndef NECRONDA_SOCKET
#define NECRONDA_SOCKET
#include <map>
#define CPPNET_CHUNK 16384
typedef struct {
string privkey;
string fullchain;
} KeyPair;
using namespace std;
class Socket {
private:
int fd;
SSL *ssl;
SSL_CTX *ctx;
bool enc;
bool servers;
bool clients;
unsigned long bytesSent;
unsigned long bytesReceived;
long microsStart;
long microsLast;
void setSocketOption(int, bool);
long send(void *buffer, int size);
long receive(void *buffer, int size);
long peek(void *buffer, int size);
public:
Socket();
explicit Socket(int filedescriptor);
~Socket();
void bind(Address *address, unsigned short port);
void bind(unsigned short port);
void listen(int count = 1);
void connect(Address address, unsigned short port);
Socket* accept();
void sslHandshake();
void sslHandshake(map<string, KeyPair> sni);
void sslHandshake(KeyPair keypair);
void sslHandshake(string privkey, string fullchain);
long send(string *str);
long send(string str);
long send(const char *str);
long send(const char *str, long length);
long send(FILE *file);
string receive();
string receive(long length);
string receive(string until);
string receive(const char *until, unsigned long strlen);
string receive(const char *until);
long receive(FILE *file);
string receiveLine();
void shutdown();
void close();
int getFd();
long getDuration();
Address *getSocketAddress() const;
unsigned short getSocketPort() const;
Address *getPeerAddress() const;
unsigned short getPeerPort() const;
string toString() const;
bool isServerSide();
bool isClientSide();
bool isSecured();
void setReuseAddress(bool value = true);
void setReusePort(bool value = true);
void setSendBufferSize(int value);
void setReceiveBufferSize(int value);
void setMinReceiveBytes(int value);
void setMinSendBytes(int value);
void setSendTimeout(unsigned long ms);
void setReceiveTimeout(unsigned long ms);
bool getReuseAddress();
bool getReusePort();
int getSendBufferSize();
int getReceiveBufferSize();
int getMinReceiveBytes();
int getMinSendBytes();
long getSendTimeout();
long getReceiveTimeout();
unsigned long getBytesSent();
unsigned long getBytesReceived();
static long select(list<Socket> read, list<Socket> write, long millis);
static long select(list<Socket> read, list<Socket> write);
long receive(FILE *file, long size);
};
Socket operator<<(Socket sock, const char *str);
Socket operator<<(Socket sock, string str);
ostream &operator<<(ostream &str, const Socket &socket);
ostream &operator<<(ostream &str, const Socket *socket);
string operator+(string &str, const Socket &socket);
string operator+(const Socket &socket, string &str);
#endif

View File

@ -1,32 +0,0 @@
//
// Created by lorenz on 7/10/18.
//
#include <sys/stat.h>
#include <sys/time.h>
#include "Http.h"
unsigned long getMicros() {
struct timeval tv;
gettimeofday(&tv, nullptr);
return (unsigned long) (1000000 * tv.tv_sec + tv.tv_usec);
}
string getHttpDate() {
time_t rawtime;
time(&rawtime);
return getHttpDate(rawtime);
}
string getHttpDate(string filename) {
struct stat attrib;
stat(filename.c_str(), &attrib);
return getHttpDate(attrib.st_ctime);
}
string getHttpDate(time_t time) {
char buffer[64];
struct tm *timeinfo = gmtime(&time);
strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
return string(buffer);
}

View File

@ -1,22 +0,0 @@
//
// Created by lorenz on 7/10/18.
//
#ifndef CPPNET_HTTP_H
#define CPPNET_HTTP_H
#include <ctime>
#include <string>
using namespace std;
unsigned long getMicros();
string getHttpDate(time_t time);
string getHttpDate();
string getHttpDate(string filename);
#endif //CPPNET_HTTP_H

View File

@ -1,194 +0,0 @@
#include <zlib.h>
#include <cassert>
#include <iostream>
#include <utility>
#include "HttpConnection.h"
#include "../Socket.h"
#include "HttpStatusCode.h"
#include "Http.h"
HttpConnection::HttpConnection() = default;
HttpConnection::HttpConnection(Socket *socket) {
this->socket = socket;
this->request = new HttpRequest(socket);
this->response = new HttpResponse();
microsStart = getMicros();
response->setVersion("1.1");
response->setField("Server", "Necronda/3.0");
}
void HttpConnection::respond(int statuscode) {
if (statuscode >= 400 && statuscode < 600) {
respond(statuscode,
"<!DOCTYPE html><html><head><title>" + to_string(statuscode) + " " +
::getStatusCode(statuscode).message +
"</title></head><body><center><h1>" + to_string(statuscode) + " " +
::getStatusCode(statuscode).message +
"</h1>" +
((request->isExistingField("Host")) ?
(request->isExistingField("Referer") &&
request->getField("Referer").find(request->getField("Host")) != string::npos) ?
"<p>Go back to the last page you visited: <a href=\"" + request->getField("Referer") + "\">" +
request->getField("Referer") + "</a></p>" :
"<p>Go back to the home page of <a href=\"//" +
request->getField("Host") + "/\">" +
request->getField("Host") +
"</a></p>" : "") + "</center></body></html>\r\n"
);
} else {
respond(statuscode, "");
}
}
void HttpConnection::respond(int statuscode, string payload) {
response->setStatusCode(statuscode);
response->setField("Date", getHttpDate());
response->setField("Content-Length", to_string(payload.length()));
response->sendHeader(socket);
socket->send(std::move(payload));
}
void HttpConnection::respond(int statuscode, FILE *file, bool compress, long start, long end) {
response->setStatusCode(statuscode);
response->setField("Transfer-Encoding", "chunked");
response->setField("Date", getHttpDate());
long shouldTransfer;
long transfered = 0;
fseek(file, 0, SEEK_END);
long len = ftell(file);
if (start != -1 && end != -1) {
fseek(file, start, SEEK_SET);
response->setField("Content-Length", to_string(end - start + 1));
shouldTransfer = end - start + 1;
compress = false;
} else {
fseek(file, 0, SEEK_SET);
shouldTransfer = len;
if (len >= 0 && !compress) {
response->setField("Content-Length", to_string(len));
}
}
if (compress) {
response->setField("Content-Encoding", "deflate");
}
response->sendHeader(socket);
if (compress) {
int level = 1;
int ret, flush;
unsigned have;
z_stream strm;
unsigned char in[CPPNET_CHUNK];
unsigned char out[CPPNET_CHUNK];
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
ret = deflateInit(&strm, level);
if (ret != Z_OK) {
throw (char *) "Unable to open file";
}
do {
strm.avail_in = (uInt) fread(in, 1, CPPNET_CHUNK, file);
if (ferror(file)) {
(void) deflateEnd(&strm);
throw (char *) strerror(errno);
}
flush = feof(file) ? Z_FINISH : Z_NO_FLUSH;
strm.next_in = in;
do {
strm.avail_out = CPPNET_CHUNK;
strm.next_out = out;
ret = deflate(&strm, flush);
assert(ret != Z_STREAM_ERROR);
have = CPPNET_CHUNK - strm.avail_out;
if (have != 0) {
char buffer[64];
sprintf(buffer, "%X\r\n", have);
socket->send(buffer);
socket->send((const char *) out, have);
socket->send("\r\n");
}
} while (strm.avail_out == 0);
assert(strm.avail_in == 0);
} while (flush != Z_FINISH);
assert(ret == Z_STREAM_END);
socket->send("0\r\n\r\n");
deflateEnd(&strm);
} else {
char buffer[CPPNET_CHUNK];
char buff[64];
while (true) {
unsigned long size = fread(buffer, 1, (size_t) ((CPPNET_CHUNK > (shouldTransfer - transfered) && shouldTransfer > 0) ? (shouldTransfer - transfered) : CPPNET_CHUNK), file);
transfered += size;
sprintf(buff, "%lX\r\n", size);
socket->send(buff);
socket->send((const char *) buffer, size);
socket->send("\r\n");
if (size == 0) {
break;
}
}
}
}
string HttpConnection::getField(string index) {
return request->getField(std::move(index));
}
string HttpConnection::getPath() {
return request->getPath();
}
void HttpConnection::setField(string index, string data) {
response->setField(std::move(index), std::move(data));
}
bool HttpConnection::isExistingField(string index) {
return request->isExistingField(std::move(index));
}
string HttpConnection::getMethod() {
return request->getMethod();
}
long HttpConnection::getDuration() {
return getMicros() - microsStart;
}
HttpStatusCode HttpConnection::getStatusCode() {
return response->getStatusCode();
}
void HttpConnection::redirect(int statuscode, string location) {
setField("Location", std::move(location));
respond(statuscode, "");
}
string HttpConnection::getResponseField(string index) {
return response->getField(std::move(index));
}
bool HttpConnection::isExistingResponseField(string index) {
return response->isExistingField(std::move(index));
}
string HttpConnection::cgiExport() {
return request->cgiExport();
}
void HttpConnection::removeField(string index) {
response->removeField(std::move(index));
}

View File

@ -1,55 +0,0 @@
#ifndef NECRONDA_HTTP_CONNECTION
#define NECRONDA_HTTP_CONNECTION
#include "../Socket.h"
#include "HttpResponse.h"
#include "HttpRequest.h"
class HttpConnection {
private:
Socket *socket{};
HttpRequest *request{};
HttpResponse *response{};
long microsStart{};
public:
explicit HttpConnection();
explicit HttpConnection(Socket *socket);
void respond(int statuscode);
void respond(int statuscode, string payload);
void respond(int statuscode, FILE *file, bool compress = false, long start = -1, long end = -1);
void redirect(int statuscode, string location);
bool isExistingField(string index);
bool isExistingResponseField(string index);
string getField(string index);
string getResponseField(string index);
string getPath();
string getMethod();
void setField(string index, string data);
long getDuration();
HttpStatusCode getStatusCode();
string cgiExport();
void removeField(string index);
};
#endif

View File

@ -1,122 +0,0 @@
/**
* Necronda Web Server 3.0
* HttpHeader.cpp - HttpHeader Class methods
* Lorenz Stechauner, 2018-05-09
*/
#include <map>
#include <iostream>
#include "../Socket.h"
#include "HttpHeader.h"
using namespace std;
string to_cgi(string text) {
for (auto & c: text) c = (char) toupper(c);
long pos = 0;
while ((pos = text.find('-', pos + 1)) != string::npos) {
text.replace(pos, 1, 1, '_');
}
return text;
}
/**
* Default Constructor
*/
HttpHeader::HttpHeader() {
fields = fields;
}
HttpHeader::HttpHeader(Socket *socket) : HttpHeader::HttpHeader() {
parse(socket);
}
void HttpHeader::parse(Socket *socket) {
while (true) {
string line = socket->receiveLine();
if (line.length() == 0) {
break;
} else {
unsigned long pos = line.find(':');
if (pos == string::npos) {
throw (char *) "Malformed header";
}
string index = line.substr(0, pos);
string data = line.substr(pos + 1, line.length() - pos);
while (index[0] == ' ') index.erase(index.begin() + 0);
while (index[index.length() - 1] == ' ') index.erase(index.end() - 1);
while (data[0] == ' ') data.erase(data.begin() + 0);
while (data[data.length() - 1] == ' ') data.erase(data.end() - 1);
setField(index, data);
}
}
}
/**
* Default Destructor
*/
HttpHeader::~HttpHeader() {
fields.clear();
}
/**
* Sets a field in the HTTP header
* e.g. Content-Length: 42
* @param index The field index
* @param data The field data
*/
void HttpHeader::setField(string index, string data) {
removeField(index);
fields.insert(make_pair(index, data));
}
void HttpHeader::removeField(string index) {
fields.erase(index);
}
/**
* Gets a field from the HTTP header
* e.g. Content-Length: 42
* @param index The field index
* @return The field data
*/
string HttpHeader::getField(string index) {
auto i = fields.find(index);
if (i != fields.end()) {
return fields.at(index);
} else {
return "";
}
}
bool HttpHeader::isExistingField(string index) {
auto i = fields.find(index);
return i != fields.end();
}
string HttpHeader::toString() {
string header = "";
for (auto it = fields.begin(); it != fields.end(); it++ ) {
header += it->first + ": " + it->second + "\r\n";
}
return header;
}
string HttpHeader::cgiExport() {
string header = "";
for (auto it = fields.begin(); it != fields.end(); it++ ) {
header += "HTTP_" + to_cgi(it->first) + "=" + cli_encode(it->second) + " ";
}
return header;
}

View File

@ -1,53 +0,0 @@
/**
* Necronda Web Server 3.0
* HttpHeader.h - HttpHeader Class definition
* Lorenz Stechauner, 2018-05-09
*/
#ifndef NECRONDA_HTTP_HEADER
#define NECRONDA_HTTP_HEADER
#include <cstring>
using namespace std;
struct comp {
bool operator()(const std::string& lhs, const std::string& rhs) const {
return strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
}
};
/**
* Stores Key-Value Pairs for a HTTP header
* e.g.
* Content-Length: 64
* Host: example.org
*/
class HttpHeader {
private:
map<string, string, comp> fields;
public:
HttpHeader();
explicit HttpHeader(Socket *socket);
~HttpHeader();
void setField(string index, string data);
string getField(string index);
void removeField(string index);
bool isExistingField(string index);
void parse(Socket *socket);
string toString();
string cgiExport();
};
#endif

View File

@ -1,107 +0,0 @@
#include <string>
#include <utility>
#include <iostream>
#include "../Socket.h"
#include "HttpHeader.h"
#include "HttpRequest.h"
HttpRequest::HttpRequest() {
this->header = HttpHeader();
}
HttpRequest::HttpRequest(Socket *socket) : HttpRequest::HttpRequest() {
parseHeader(socket);
}
HttpRequest::HttpRequest(string method, string path, string version) : HttpRequest::HttpRequest() {
this->method = std::move(method);
this->path = std::move(path);
this->version = std::move(version);
}
void HttpRequest::parseHeader(Socket *socket) {
string line = socket->receiveLine();
unsigned long pos1 = line.find(' ');
unsigned long pos2;
bool invalid = false;
if (pos1 != string::npos) {
pos2 = line.find(' ', pos1 + 1);
if (pos2 != string::npos) {
method = line.substr(0, pos1);
for (auto &c: method) c = (char) toupper(c);
path = line.substr(pos1 + 1, pos2 - pos1 - 1);
version = line.substr(pos2 + 6, 3);
} else {
invalid = true;
}
} else {
pos2 = string::npos;
invalid = true;
}
if (!invalid && (line.substr(pos2 + 1, 5) != "HTTP/" || version[1] != '.' || path[0] != '/' || !(version[0] >= '0' && version[0] <= '9') || !(version[2] >= '0' && version[2] <= '9'))) {
invalid = true;
}
if (invalid) {
method = "";
path = "";
version = "";
throw (char *) "Malformed header";
}
header.parse(socket);
}
string HttpRequest::getMethod() {
return method;
}
string HttpRequest::getPath() {
return path;
}
string HttpRequest::getVersion() {
return version;
}
void HttpRequest::setMethod(string method) {
this->method = std::move(method);
}
void HttpRequest::setPath(string path) {
this->path = std::move(path);
}
void HttpRequest::setVersion(string version) {
this->version = std::move(version);
}
string HttpRequest::getField(string index) {
return header.getField(std::move(index));
}
void HttpRequest::setField(string index, string data) {
header.setField(std::move(index), std::move(data));
}
bool HttpRequest::isExistingField(string index) {
return header.isExistingField(std::move(index));
}
string HttpRequest::cgiExport() {
return header.cgiExport();
}

View File

@ -1,52 +0,0 @@
/**
* Necronda Web Server 3.0
* HttpHeader.h - HttpHeader Class definition
* Lorenz Stechauner, 2018-05-09
*/
#ifndef NECRONDA_HTTP_REQUEST
#define NECRONDA_HTTP_REQUEST
using namespace std;
class HttpRequest {
private:
HttpHeader header;
string method;
string path;
string version;
public:
HttpRequest();
explicit HttpRequest(Socket *socket);
HttpRequest(string method, string path, string version = "1.1");
void parseHeader(Socket *socket);
void sendHeader(Socket *socket);
string getField(string index);
void setField(string index, string data);
bool isExistingField(string index);
string getMethod();
string getPath();
string getVersion();
void setMethod(string method);
void setPath(string path);
void setVersion(string version);
string cgiExport();
};
#endif

View File

@ -1,70 +0,0 @@
//
// Created by lorenz on 5/17/18.
//
#include "HttpResponse.h"
#include <utility>
#include <iostream>
#include "HttpStatusCode.h"
HttpResponse::HttpResponse() {
this->header = HttpHeader();
}
HttpResponse::HttpResponse(Socket *socket) : HttpResponse::HttpResponse() {
this->parseHeader(socket);
}
HttpResponse::HttpResponse(int statuscode, string version) : HttpResponse::HttpResponse(::getStatusCode(statuscode), std::move(version)) {
}
HttpResponse::HttpResponse(HttpStatusCode statuscode, string version) : HttpResponse::HttpResponse() {
this->statuscode = statuscode;
this->version = std::move(version);
}
void HttpResponse::sendHeader(Socket *socket) {
socket->send("HTTP/" + version + " " + to_string(statuscode.code) + " " + statuscode.message + "\r\n" +
header.toString() + "\r\n");
}
string HttpResponse::getField(string index) {
return header.getField(std::move(index));
}
void HttpResponse::setField(string index, string data) {
header.setField(std::move(index), std::move(data));
}
bool HttpResponse::isExistingField(string index) {
return header.isExistingField(std::move(index));
}
HttpStatusCode HttpResponse::getStatusCode() {
return statuscode;
}
string HttpResponse::getVersion() {
return version;
}
void HttpResponse::setStatusCode(HttpStatusCode statuscode) {
this->statuscode = statuscode;
}
void HttpResponse::setStatusCode(int statuscode) {
this->statuscode = ::getStatusCode(statuscode);
}
void HttpResponse::setVersion(string version) {
this->version = std::move(version);
}
void HttpResponse::parseHeader(Socket *socket) {
}
void HttpResponse::removeField(string index) {
header.removeField(std::move(index));
}

View File

@ -1,50 +0,0 @@
#ifndef NECRONDA_HTTP_RESPONSE
#define NECRONDA_HTTP_RESPONSE
#include <string>
#include "HttpHeader.h"
#include "HttpStatusCode.h"
#include "../Socket.h"
class HttpResponse {
private:
HttpHeader header;
HttpStatusCode statuscode;
string version;
public:
HttpResponse();
explicit HttpResponse(Socket *socket);
explicit HttpResponse(int statuscode, string version = "1.1");
explicit HttpResponse(HttpStatusCode statuscode, string version = "1.1");
void parseHeader(Socket *socket);
void sendHeader(Socket *socket);
string getField(string index);
void setField(string index, string data);
bool isExistingField(string index);
HttpStatusCode getStatusCode();
string getVersion();
void setStatusCode(HttpStatusCode statuscode);
void setStatusCode(int statuscode);
void setVersion(string version);
void removeField(string index);
};
#endif

View File

@ -1,66 +0,0 @@
#include "HttpStatusCode.h"
/**
* Necronda Web Server 3.0
* HttpStatusCode.cpp - HTTP Status Code definition
* Lorenz Stechauner, 2018-05-16
* Reference: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
*/
HttpStatusCode httpStatusCodes[] = {
HttpStatusCode{100, "Informational", "Continue", ""},
HttpStatusCode{101, "Informational", "Switching Protocols", ""},
HttpStatusCode{200, "Success", "OK", ""},
HttpStatusCode{201, "Success", "Created", ""},
HttpStatusCode{202, "Success", "Accepted", ""},
HttpStatusCode{203, "Success", "Non-Authoritative Information", ""},
HttpStatusCode{204, "Success", "No Content", ""},
HttpStatusCode{205, "Success", "Reset Content", ""},
HttpStatusCode{206, "Success", "Partial Content", ""},
HttpStatusCode{300, "Redirection", "Multiple Choices", ""},
HttpStatusCode{301, "Redirection", "Moved Permanently", ""},
HttpStatusCode{302, "Redirection", "Found", ""},
HttpStatusCode{303, "Redirection", "See Other", ""},
HttpStatusCode{304, "Redirection", "Not Modified", ""},
HttpStatusCode{305, "Redirection", "Use Proxy", ""},
HttpStatusCode{307, "Redirection", "Temporary Redirect", ""},
HttpStatusCode{308, "Redirection", "Permanent Redirect", ""},
HttpStatusCode{400, "Client Error", "Bad Request", ""},
HttpStatusCode{401, "Client Error", "Unauthorized", ""},
HttpStatusCode{402, "Client Error", "Payment Required", ""},
HttpStatusCode{403, "Client Error", "Forbidden", ""},
HttpStatusCode{404, "Client Error", "Not Found", ""},
HttpStatusCode{405, "Client Error", "Method Not Allowed", ""},
HttpStatusCode{406, "Client Error", "Not Acceptable", ""},
HttpStatusCode{407, "Client Error", "Proxy Authentication Required", ""},
HttpStatusCode{408, "Client Error", "Request Timeout", ""},
HttpStatusCode{409, "Client Error", "Conflict", ""},
HttpStatusCode{410, "Client Error", "Gone", ""},
HttpStatusCode{411, "Client Error", "Length Required", ""},
HttpStatusCode{412, "Client Error", "Precondition Failed", ""},
HttpStatusCode{413, "Client Error", "Request Entity Too Large", ""},
HttpStatusCode{414, "Client Error", "Request-URI Too Long", ""},
HttpStatusCode{415, "Client Error", "Unsupported Media Type", ""},
HttpStatusCode{416, "Client Error", "Requested Range Not Satisfiable", ""},
HttpStatusCode{417, "Client Error", "Expectation Failed", ""},
HttpStatusCode{500, "Server Error", "Internal Server Error", ""},
HttpStatusCode{501, "Server Error", "Not Implemented", ""},
HttpStatusCode{502, "Server Error", "Bad Gateway", ""},
HttpStatusCode{503, "Server Error", "Service Unavailable", ""},
HttpStatusCode{504, "Server Error", "Gateway Timeout", ""},
HttpStatusCode{505, "Server Error", "HTTP Version Not Supported", ""},
};
HttpStatusCode getStatusCode(int statuscode) {
for (HttpStatusCode sc : httpStatusCodes) {
if (sc.code == statuscode) {
return sc;
}
}
throw (char *) "Invalid status code";
}

View File

@ -1,15 +0,0 @@
#ifndef NECRONDA_HTTP_STATUSCODE
#define NECRONDA_HTTP_STATUSCODE
typedef struct {
short code; // The status code (e.g. 200)
const char *type; // The status type type (e.g Success)
const char *message; // The status code message (e.g. OK)
const char *description; // The status code description (currently not used)
} HttpStatusCode;
HttpStatusCode getStatusCode(int statuscode);
#endif

View File

@ -1,41 +0,0 @@
//
// Created by lorenz on 5/30/18.
//
#include "procopen.h"
stds procopen(const char* command) {
int pipes[3][2];
pipe(pipes[PARENT_READ_PIPE]);
pipe(pipes[PARENT_WRITE_PIPE]);
pipe(pipes[PARENT_ERROR_PIPE]);
int pid = fork();
if(pid == 0) {
dup2(CHILD_READ_FD, STDIN_FILENO);
dup2(CHILD_WRITE_FD, STDOUT_FILENO);
dup2(CHILD_ERROR_FD, STDERR_FILENO);
close(CHILD_READ_FD);
close(CHILD_WRITE_FD);
close(CHILD_ERROR_FD);
close(PARENT_READ_FD);
close(PARENT_WRITE_FD);
close(PARENT_ERROR_FD);
system(command);
exit(0);
} else {
close(CHILD_READ_FD);
close(CHILD_WRITE_FD);
close(CHILD_ERROR_FD);
return stds{fdopen(PARENT_WRITE_FD, "w"), fdopen(PARENT_READ_FD, "r"), fdopen(PARENT_ERROR_FD, "r"), (pid_t) pid};
}
}

View File

@ -1,41 +0,0 @@
//
// Created by lorenz on 5/30/18.
//
#include <zconf.h>
#include <cstdlib>
#include <cstdio>
#ifndef NECRONDA_PROCOPEN
#define NECRONDA_PROCOPEN
#define PARENT_WRITE_PIPE 0
#define PARENT_READ_PIPE 1
#define PARENT_ERROR_PIPE 2
#define READ_FD 0
#define WRITE_FD 1
#define PARENT_READ_FD ( pipes[PARENT_READ_PIPE][READ_FD] )
#define PARENT_WRITE_FD ( pipes[PARENT_WRITE_PIPE][WRITE_FD] )
#define PARENT_ERROR_FD ( pipes[PARENT_ERROR_PIPE][READ_FD] )
#define CHILD_READ_FD ( pipes[PARENT_WRITE_PIPE][READ_FD] )
#define CHILD_WRITE_FD ( pipes[PARENT_READ_PIPE][WRITE_FD] )
#define CHILD_ERROR_FD ( pipes[PARENT_ERROR_PIPE][WRITE_FD] )
typedef struct {
FILE* stdin;
FILE* stdout;
FILE* stderr;
pid_t pid;
} stds;
stds procopen(const char* command);
#endif