292 Commits
v4.5 ... master

Author SHA1 Message Date
e93c478cc3 uri: Change file priority to .xhtml, .html, .php 2024-06-24 14:08:21 +02:00
1d0a545610 async: Check if SSL layer is ready 2024-06-07 11:54:50 +02:00
fb67f7e9b0 proxy_handler: Honor Transfer-Encoding 2024-05-11 14:24:44 +02:00
0dd9a9a843 Add XHTML support 2024-04-22 15:14:31 +02:00
6eaf5f5776 local_handler: Only send 417 when value invalid 2024-02-07 15:37:53 +01:00
75d36bb5bb Use correct color string 2024-02-07 15:34:21 +01:00
c6da5413d4 Add logging for 100 Continue 2024-02-07 15:32:44 +01:00
80d7626208 Implement Expect: 100-continue 2024-02-07 15:23:14 +01:00
e97809253a local_handler: Return early when not static 2024-02-07 11:55:17 +01:00
b26e80f18a sock: Add handling for want read/write 2024-02-06 23:33:24 +01:00
90b20d40d8 Add SSL_MODE_ENABLE_PARTIAL_WRITE to ssl context 2024-02-06 23:08:10 +01:00
34b860073c utils: Add application/sql as text 2024-01-05 18:24:04 +01:00
5d6bd07cfd websocket: Fix WebSocket upgrade 2024-01-04 01:25:44 +01:00
2a2c1ea442 socket: Honor EAGAIN for socket operations 2023-11-19 21:27:42 +01:00
fee4cc808a fastcgi: Add FIXME 2023-09-28 22:49:33 +02:00
0232331f99 Fix reverse proxy timeout issues 2023-09-08 02:51:57 +02:00
62b631c862 Fix FastCGI Non-Chunked bug 2023-09-08 02:18:52 +02:00
0f526d7b95 Fix FastCGI error handling 2023-07-13 23:18:10 +02:00
642286a838 Async: lock queue and make volatile 2023-07-11 18:15:15 +02:00
91a8959c8d Add FIXME for pipe overflow 2023-07-11 02:12:26 +02:00
197756bf15 Fix typo 2023-07-11 02:12:12 +02:00
f4697ce0f3 Fix typo 2023-07-11 01:57:48 +02:00
72c2e24050 Small improvements in async 2023-07-11 01:51:47 +02:00
745509cab1 Add debug message when joining mpmc workers 2023-07-11 01:50:36 +02:00
35d3612d9b Cleanup on request timeout 2023-07-11 01:50:00 +02:00
37671546ef Handle EBADF in async 2023-07-08 13:38:19 +02:00
beec199192 Add debug messages to terminate_gracefully() 2023-07-08 01:10:07 +02:00
afa0196277 Async: ignore ENOENT errors on remove 2023-07-08 01:05:18 +02:00
29a0775bf5 Update proxy 504/502 error codes responses (3) 2023-07-07 22:30:03 +02:00
46d661d5f3 Update proxy 504/502 error codes responses (2) 2023-07-07 22:24:40 +02:00
9ec1c1c3a2 Update proxy 504/502 error codes responses 2023-07-07 22:23:15 +02:00
d6b315c91c Update proxy closing behaviour 2023-07-07 22:13:47 +02:00
fd2abf9804 Handle EEXIST in async 2023-07-07 22:04:33 +02:00
ddb6623651 Handle connection closures from proxy peers in async 2023-07-07 21:56:38 +02:00
ef3e8475fb Honor Connection: closed received from reverse proxy peers 2023-07-07 21:30:56 +02:00
0cd63ff5e9 Fix typos 2023-07-07 21:28:07 +02:00
de3fcf8fc3 Honor proxy server timeout with one second buffer 2023-07-06 17:15:53 +02:00
0f40dcb5db Fix proxy_close to keep value of in_use 2023-07-06 14:57:06 +02:00
b7c8db01ac Fix proxy locking by adding volatile keyword to in_use 2023-07-06 12:03:08 +02:00
b6c9d7330d Free proxy connection slot when upgrading to WebSocket connection 2023-07-06 01:34:23 +02:00
c59977dada Fix proxy unlocking 2023-07-06 00:29:12 +02:00
77f0eeda6d Fix spacing 2023-07-06 00:20:27 +02:00
371bff0d07 Fix async and FastCGI timeout issues 2023-07-05 23:19:40 +02:00
2e3146f69a Fix FastCGI fds default value 2023-07-05 13:11:49 +02:00
733b73760c Fix payload usage in first FastCGI frame 2023-07-05 12:52:53 +02:00
a9fbd21f80 Add todo and fix comments 2023-07-05 00:42:06 +02:00
52ebad201f Fix sock_had_pending for pipe 2023-07-04 22:00:09 +02:00
7fe4abd379 Improve proxy error handling 2023-07-04 21:40:40 +02:00
56427e3003 Close ssl also when enc is not set in socket 2023-07-04 21:16:27 +02:00
914aa2d341 Fix socket.h sock_init() definition 2023-07-03 00:03:27 +02:00
db4bca6f13 Enlarge log message buffer from 16 to 256 2023-07-02 22:54:54 +02:00
f1ba02756a Output selected proxy slot 2023-07-02 22:52:12 +02:00
0b68c67982 Fix proxy try-loop 2023-07-02 17:45:53 +02:00
cb04af739c Fix nextcloud issues 2023-07-02 13:50:07 +02:00
cd25120362 Update reverse proxy message 2023-07-02 12:42:12 +02:00
13d6e30d01 Fix proxy loop 2023-07-02 12:21:26 +02:00
04f13c49af Unset socket now is -1 instead of 0 2023-07-02 12:21:00 +02:00
9aee302f6c Rename timeout to http_timeout 2023-07-02 00:14:39 +02:00
44e3b1332f Add first steps to honor timeout from server as proxy 2023-07-01 21:50:33 +02:00
808ebdb0a0 Add http_add_to_header_fiel() 2023-07-01 21:19:27 +02:00
31cd2e7e73 Fix FastCGI segfault error 2023-07-01 20:21:21 +02:00
bd8e71e83d Rename fcgi_cnx to fcgi_ctx in client struct 2023-07-01 20:13:51 +02:00
9aaa28f1ca Update php-fpm version 2023-07-01 19:26:20 +02:00
73e0cffa78 Hide .inc and .inc.php files 2023-01-30 23:00:56 +01:00
6f0371c46f Use getaddrinfo/getnameinfo instead of deprecated methods 2023-01-29 20:31:27 +01:00
ad6ffe5425 Use file descriptor in sock_set_socket_timeout_micros() 2023-01-29 11:57:37 +01:00
ab7e5cc722 Remove dns_server directive 2023-01-29 11:55:32 +01:00
40310faa4b Update error to support getaddrinfo 2023-01-29 11:38:48 +01:00
7a3adc6ed3 Remove gotos from proxy.c 2023-01-26 18:06:53 +01:00
bd73061462 Removing gotos in config.c 2023-01-26 17:31:20 +01:00
9ee0e11c86 Remove some gotos 2023-01-26 17:15:30 +01:00
240ed6bc25 Dumping stderr on FastCGI close 2023-01-26 15:01:48 +01:00
03dd126ca7 Add php error handling 2023-01-26 14:51:47 +01:00
17327006db Add async support for FastCGI 2023-01-26 13:34:04 +01:00
9237c791bb Set chunks_transferred to 0 2023-01-26 12:49:18 +01:00
f7a6214dbc Allow proxies to delay chunks 2023-01-26 12:46:30 +01:00
8053439212 Add chunk send/receiving functions 2023-01-26 11:41:10 +01:00
299e726341 Add pip abstraction for sock 2023-01-26 11:02:44 +01:00
e721e542f3 Add chunk_handler 2023-01-24 00:07:22 +01:00
df7dfb5107 Refactor uri.c
buf3 removed, unnecessary and not initialized
2023-01-23 23:43:14 +01:00
5f3ba2b971 Add sock_init() 2023-01-23 23:35:53 +01:00
1e0a7c95da Unset prefix at other position in async 2023-01-23 23:33:01 +01:00
0394c3afb4 Add new line in ws_frame_handler.c 2023-01-23 13:02:53 +01:00
32ea03079b Add sock_splice_all() 2023-01-23 12:55:41 +01:00
8b63d2cf32 Refactor proxy and socket a bit 2023-01-23 11:09:28 +01:00
d461ba6f29 Fix typo in workers.c 2023-01-23 10:09:34 +01:00
5b98fd0dab Catching error of async_thread() in main 2023-01-23 10:07:58 +01:00
0b1b0bcb56 Update async_check to check return value of poll() 2023-01-23 01:53:36 +01:00
44fc7f2e5c Add flags to sock_splice_chunked 2023-01-23 01:50:50 +01:00
c8b605aa90 Save proxy connection time 2023-01-13 02:59:48 +01:00
59b9bfd53e Finally remove compression from proxy 2023-01-13 02:49:25 +01:00
e8655102b9 Use sock in fastcgi 2023-01-13 02:36:41 +01:00
add135b3ad Use htons and ntohs in fastcgi 2023-01-12 20:40:40 +01:00
2d250621c8 Add time to log prefix 2023-01-12 20:29:34 +01:00
f96dc46ea7 Add sock_send_x and remove fastcgi compression 2023-01-12 19:51:26 +01:00
5138dafb8e Refactor compress a bit 2023-01-12 16:30:10 +01:00
4be5b0d195 lock magic in cache_handler 2023-01-12 16:29:05 +01:00
a84aa591d4 Really avoid double free in async 2023-01-12 15:25:40 +01:00
d7db1f0be9 Make error_str GNU_SOURCE compliant 2023-01-12 15:24:30 +01:00
116f7035ed Initialize cache after logger_init() 2023-01-12 15:10:16 +01:00
8abc0b8bfa Avoid double free in async 2023-01-12 15:09:05 +01:00
410d692372 Truly initialize thread name 2023-01-12 15:07:46 +01:00
f42ac83323 Add list_contains() to list 2023-01-12 15:07:03 +01:00
7d576b2466 Better lossy error code description 2023-01-12 13:13:24 +01:00
5acab8144d Rework error interface 2023-01-12 12:54:23 +01:00
4814035b62 Fix echo Makefile bug 2023-01-12 11:47:48 +01:00
15f400f376 Add status_code_t in http.h 2023-01-12 10:22:27 +01:00
32638fdd91 Fix list_remove() 2023-01-12 10:21:57 +01:00
07d6280864 Fix list pointer bug in proxy 2023-01-12 01:58:14 +01:00
820ce8dac9 Update logger_prefix 2023-01-12 00:34:04 +01:00
5bd9b3fb33 Hopeful fix 2023-01-11 23:53:43 +01:00
a3dcddf69d Add lock for clients list 2023-01-11 18:06:55 +01:00
487386158d Remove most memory leaks with valgrind 2023-01-11 17:50:11 +01:00
32f00175e4 Add lock to cache_handler 2023-01-11 15:55:39 +01:00
581b4dc5aa Fix flines() 2023-01-11 15:07:01 +01:00
611ceabfb2 Small fixes in tcp_acceptor 2023-01-11 14:49:06 +01:00
6b295cef95 Add Accept-Ranges lines 2023-01-11 14:30:12 +01:00
764b754a6f Add more utils 2023-01-11 11:06:36 +01:00
7699583c5f List/FastCGI cleanup 2023-01-11 00:34:17 +01:00
8b92d4ff91 Remove color_table 2023-01-10 23:54:47 +01:00
e8d8abdc5a Add -C --clean 2023-01-10 23:52:29 +01:00
15c6b7ddaa Create .sesimos/ in cache_init 2023-01-09 22:53:42 +01:00
e9a065f8aa Add -pthread to Makefile 2023-01-09 22:47:50 +01:00
0730429041 Fix error.c 2023-01-09 22:45:37 +01:00
af62a3065a Add http error handling 2023-01-09 22:01:50 +01:00
c36ad8c113 Add global error handling 2023-01-09 01:07:02 +01:00
7f7a07c4d2 Add HTTP status code descriptions 2023-01-08 23:13:47 +01:00
4ff22bd0c6 Handle timeouts in epoll 2023-01-08 22:19:16 +01:00
83ca2467de Add 1xx TODO 2023-01-08 20:34:04 +01:00
0a1fb977d6 Diversifying HTTP statuses 2023-01-08 01:37:45 +01:00
1405036cf2 Add 421 message 2023-01-07 16:30:23 +01:00
5f3cd03a6f Using 203 in /.sesimos/res/ 2023-01-07 02:48:43 +01:00
819b71f285 Add more HTTP status codes 2023-01-07 02:41:57 +01:00
ceaa384fce Using more diverse HTTP codes 2023-01-07 02:35:47 +01:00
99c4eb1c8a Update http_static 2023-01-07 00:21:25 +01:00
fb59b0d8c4 Using 504 instead of 503 2023-01-06 22:45:56 +01:00
946adb54d7 Handle HEAD for proxy 2023-01-06 10:41:45 +01:00
b369a1116e Handle HEAD as proxy 2023-01-05 22:55:00 +01:00
950bf19331 Use semaphores to keep track of proxy connections 2023-01-05 22:51:30 +01:00
993cb65724 Mark proxy connection free on error 2023-01-05 22:29:28 +01:00
c7be0adc66 Catch EBADF in async 2023-01-05 22:04:18 +01:00
4782707049 Working on TODOs in proxy.c 2023-01-05 21:59:23 +01:00
3d1451448d Update Makefile 2023-01-05 20:35:21 +01:00
1619e01174 Update async to use epoll instead of poll 2023-01-05 20:33:59 +01:00
6112e719e5 Improve list 2023-01-05 20:20:32 +01:00
6f2751f69b Added http_parse_request 2023-01-05 18:54:31 +01:00
925ff2b9e4 Add clock_cpu() 2023-01-05 18:45:48 +01:00
c67edd4195 Close all connections on exit 2023-01-04 00:40:09 +01:00
d8fd552b40 Memset uri/req/res 2023-01-03 23:59:23 +01:00
ec6457465c Init conf 2023-01-03 19:23:44 +01:00
490c8a2ae0 Add epoll todo 2023-01-03 19:04:25 +01:00
f87fb74ced Remove sock_poll 2023-01-03 18:58:58 +01:00
7b9844e267 Improve debug message 2023-01-03 18:53:32 +01:00
308534b883 Code cleanup 2023-01-03 18:44:50 +01:00
b93d97258c Reverse proxy error docs also with HEAD 2023-01-03 18:43:49 +01:00
cd80a194dd Use strcasecmp in http.c 2023-01-03 18:36:40 +01:00
ffc5c80b65 Format proxy errors if no content 2023-01-03 18:25:07 +01:00
896486b64a WebSocket async working 2023-01-03 16:12:45 +01:00
c1706edcea Update server version string 2023-01-03 14:56:31 +01:00
f8d3253a9f Fix reverse proxy error 2023-01-03 14:52:36 +01:00
ec3183e99b Outsource globe.svg 2023-01-03 14:42:01 +01:00
27d514ae12 Cleanup 2023-01-03 14:34:54 +01:00
e82869e993 Using list in http header 2023-01-03 14:05:11 +01:00
a54a6af497 Add list_insert/append_ptr 2023-01-03 14:04:03 +01:00
c13bea5ab4 Refactor proxy a bit 2023-01-03 12:58:18 +01:00
fd1630a554 Add 405 to /.sesimos/res/ 2023-01-03 11:27:12 +01:00
f7f245c931 Outsource default favicons 2023-01-03 11:08:17 +01:00
e72cb57b4a Fix mpmc worker termination 2023-01-03 10:34:08 +01:00
1cf9172194 Make client list dynamic 2023-01-03 10:05:30 +01:00
f416c2f05c Change format_duration to format minutes as XX:XX 2023-01-02 22:20:33 +01:00
300e3dc0aa Botch sock_strerror 2023-01-02 22:12:34 +01:00
26869c0df5 Improve list tests 2023-01-02 22:01:51 +01:00
c7488b0f59 Add list.c 2023-01-02 21:48:06 +01:00
4e6930692e add todo 2023-01-02 20:12:43 +01:00
24cb993cb8 Remove parameter client_num from fastcgi_init() 2023-01-02 20:11:28 +01:00
fc9538fa6d Update sock_check to sock_has_pending 2023-01-02 19:56:29 +01:00
9d8120966d Handle SIGPIPE 2023-01-02 19:48:12 +01:00
3c5ecacecc Refactor async.c 2023-01-02 18:16:18 +01:00
606865e5dc Handle proxy earlier 2023-01-02 17:54:30 +01:00
b79c9c710b Async locking 2023-01-02 17:53:44 +01:00
4b4e7bd257 Refactor async_exec 2023-01-02 17:32:13 +01:00
8d6b7105f3 Using sock_recv in sock_check 2023-01-02 17:28:40 +01:00
95e2ddb24c Fix clock_micros() 2023-01-02 17:28:08 +01:00
535e1add96 Make errno in sock transparent 2023-01-02 17:03:39 +01:00
51d85cc99f Async check if closed 2023-01-02 16:52:18 +01:00
a4c0093fbe Small improvements 2023-01-02 15:43:26 +01:00
6ff6f0c73b Reduce client_ctx_t footprint 2023-01-02 15:20:02 +01:00
555e8f62a1 Fix FastCGI error handling 2023-01-02 14:33:48 +01:00
03c2c7b364 Outsource default documents to actual files 2023-01-02 14:26:59 +01:00
abb40a659c Fix proxy pointer arithmetic 2022-12-31 11:44:52 +01:00
6eb3b99bfb Update Makefile 2022-12-31 11:37:44 +01:00
9cb278eb2a Remove tcp_closer 2022-12-31 11:20:29 +01:00
33feabdec0 Update server string Sesimos -> sesimos 2022-12-31 02:30:35 +01:00
7995a9699e TRACE now works for http 2022-12-31 02:27:27 +01:00
680344fbec TRACE working again 2022-12-31 02:20:32 +01:00
e667a79ab2 Closing connection if no keep alive 2022-12-31 02:14:20 +01:00
816c8c0bfc Actually using async 2022-12-31 02:00:01 +01:00
1547805e4c Add sock_set_timeout() 2022-12-31 01:36:24 +01:00
96f3225f51 Add clock_micros() 2022-12-31 00:53:36 +01:00
9ad1ecf1da Rev proxy working again 2022-12-31 00:28:20 +01:00
28d7cf68df Outsource from request_handler 2022-12-30 16:48:48 +01:00
204317f46a Remove responder 2022-12-30 16:06:49 +01:00
a56002a409 More workers 2022-12-29 23:21:40 +01:00
db966b3a66 Merge worker .h files 2022-12-29 22:00:32 +01:00
5d27cf0398 Update workers 2022-12-29 21:47:42 +01:00
7a2acb0e66 Add workers 2022-12-29 21:41:40 +01:00
cff5d558d6 Fix responder name 2022-12-29 21:17:00 +01:00
665e4ceabc Splitting in request_handler and responder 2022-12-29 18:51:12 +01:00
f241913620 rename fastcgi_conn to fastcgi_cnx_t 2022-12-29 17:09:56 +01:00
2969e435d1 Small fixes 2022-12-29 17:02:57 +01:00
c4289ac018 Set logger prefix in request handler 2022-12-29 12:18:39 +01:00
672745f6df Delete client.h/.c 2022-12-29 11:47:59 +01:00
f92c26c350 async - check if fd is already ready 2022-12-29 11:31:52 +01:00
f9b3cc29ab Update logger to use format 2022-12-29 11:09:45 +01:00
5c72a0cb60 Request handler 2022-12-29 10:27:54 +01:00
cf3cff0746 Reset errno after EINTR 2022-12-29 10:14:14 +01:00
b90ed61e03 Async working with connection handlers 2022-12-29 01:49:00 +01:00
dfc659dec0 Renamed logger_alive to alive 2022-12-29 00:02:33 +01:00
096aeae23b Async working (in concept) 2022-12-28 16:40:31 +01:00
cd97eca7d3 Add client_ctx_t in client_handler 2022-12-28 15:17:19 +01:00
ce658ac965 move lib/cache to cache_handler 2022-12-19 15:27:38 +01:00
dd4f768cc4 Logger and config refactor 2022-12-19 14:36:00 +01:00
45514f90ca Multi processing -> multi threading 2022-12-18 22:18:11 +01:00
44913c1e0e Refactor 2022-12-15 19:55:52 +01:00
b244f86c72 Remove unneccessary malloc in compress 2022-12-15 19:49:59 +01:00
a3c1ecc0bf Refactor uri.h 2022-12-15 17:53:49 +01:00
3227e615fe Use getopt_long 2022-12-15 11:26:40 +01:00
3ce72975b8 Remove shm from config 2022-12-15 10:43:43 +01:00
1f20c70772 Add union for sock addr 2022-12-15 10:26:28 +01:00
fabb55d94b Cleanup 2022-12-13 23:49:12 +01:00
6d473bfa49 small changes 2022-12-13 23:43:22 +01:00
8e83d6aa5f Outsource geoip 2022-12-13 23:41:29 +01:00
b422b37806 Renamed rev_proxy -> proxy 2022-12-13 20:36:21 +01:00
74c97a512f Rename logger() 2022-12-13 19:49:20 +01:00
7653c3117e Add architecture.md 2022-12-10 23:39:36 +01:00
483b386100 Remove some global variables 2022-12-10 22:44:26 +01:00
bfa9cf4fcd Add client_ctx_t 2022-12-10 22:33:30 +01:00
9ac67dbfd3 Clean up logger 2022-12-10 21:56:25 +01:00
2efe65fc74 Cleanup code 2022-12-10 21:40:41 +01:00
2937bdaded Add logger 2022-12-10 20:48:47 +01:00
782c7440b0 Websocket uuid as char array 2022-12-10 02:21:02 +01:00
88346fe722 Do not use shard object file anymore 2022-12-10 01:49:16 +01:00
0f75aeea7a Add tests for some utils 2022-12-10 01:33:16 +01:00
933aac0f09 Use OpenSSL EVP api for hashing 2022-12-09 14:27:06 +01:00
7f1299feb4 Fix rev proxy ret bug 2022-11-11 03:05:33 +01:00
8435048150 Handle fastcgit chunk return 2022-11-11 02:23:12 +01:00
206ae3264d Hopefully fix bug 2022-11-11 02:18:01 +01:00
5e050512ad Fix sock_recv return handling 2022-11-10 23:51:57 +01:00
db053121f2 Refactor Makefile 2022-11-10 20:42:24 +01:00
89a9d4b9d6 Fix chunked splicing 2022-11-10 20:21:25 +01:00
bc7c3591a2 Renamed docs/ to doc/ 2022-11-01 11:10:29 +01:00
1859c432c8 Fix chunked 2022-10-21 00:01:11 +02:00
33ec943e8b Fix Makefile 2022-10-20 23:51:06 +02:00
b30f9fa56d Implement Transfer-Encoding chunked for requests 2022-10-20 23:10:07 +02:00
90e324cf87 Remove client buffer 2022-10-20 23:00:11 +02:00
e7e1e7b18f Add debian target to Makefile 2022-10-20 22:07:51 +02:00
63b1ca5d6a Update file header comments 2022-10-20 22:03:46 +02:00
54313551fc Use (void) instead of void 2022-10-20 22:03:46 +02:00
92779e5dba Use volatile sig_atomic_t instead of int 2022-10-20 22:03:46 +02:00
100eb1597d Update Makefile to be a real Makefile 2022-10-20 22:03:46 +02:00
f4c3345445 Fix spacing 2022-10-20 22:03:16 +02:00
893316ebfa Remove debug message 2022-10-20 21:32:50 +02:00
6a511732af Add debug print for websocket 2022-08-27 17:37:54 +02:00
bb895c5bca Fix WebSocket connection close handling 2022-08-27 14:01:33 +02:00
a2f4bf57e0 Bump version to 4.6 2022-08-18 14:49:49 +02:00
635271ec50 Change default config file name 2022-08-18 14:36:31 +02:00
2b4569aabe Rename Necronda server to Sesimos 2022-08-18 14:17:12 +02:00
f361fce561 Remove second trailing new-line in websocket.c 2022-08-18 12:31:56 +02:00
c92742275a Implement WebSocket reverse proxy 2022-08-18 03:07:54 +02:00
041e4d43a7 Code style 2022-08-17 22:35:51 +02:00
170337d4d5 Do not use select() anymore 2022-08-17 22:34:01 +02:00
f0b27b3b37 Fix revproxy and fastcgi http header field merging 2022-08-17 19:11:20 +02:00
ee8aedce91 Add base64_encode() to utils 2022-08-17 12:57:07 +02:00
0648c75baa Refactor code a bit 2022-08-16 22:06:50 +02:00
b6ba58d406 Fix memory leak on location rewrite in rev_proxy 2022-08-16 21:03:50 +02:00
abe0e326cb Remove redundant compressable-mime-type 2022-08-16 20:40:55 +02:00
4fe067ed7d Remove strncpy 2022-08-16 20:16:46 +02:00
41e12d6293 Fix http_get_header_field(_len) 2022-08-16 20:08:00 +02:00
a738f1abfe Fix http parser bugs 2022-08-16 20:04:08 +02:00
b6309eec39 Update readme 2022-08-16 19:20:01 +02:00
9923a76ba7 Update readme 2022-08-16 19:15:59 +02:00
ea4cdff233 Update example.conf 2022-08-16 19:08:02 +02:00
6526b5cbcb Rename necronda-server.* to server.* 2022-08-16 19:02:28 +02:00
0119945e03 Fix header in client.h 2022-08-16 18:58:38 +02:00
3fe1fe023a Fix shared memory management on error 2022-08-16 18:52:10 +02:00
bc1c6d3498 Update http header parser 2022-08-16 18:35:03 +02:00
50bb537074 Refactor code style 2022-08-15 22:06:10 +02:00
c060ee5bb6 Add TODO 2022-07-10 21:28:00 +02:00
4062883cb3 Update rev proxy error document handling 2022-07-10 21:26:01 +02:00
e0e44e9c26 Fix reverse proxy retry issue 2022-07-08 11:37:07 +02:00
f0d8a3db4c Fix array out of bounds for reverse proxy location 2022-07-07 20:01:30 +02:00
557e176d3d Add redirection fix for reverse proxy 2022-07-07 19:36:54 +02:00
77 changed files with 7817 additions and 4009 deletions

10
.gitignore vendored
View File

@ -1,8 +1,12 @@
*
!src
!src/**
!docs
!docs/**
!res
!res/**
!doc
!doc/**
!test
!test/**
!Makefile
!.gitignore
!README.md
!*.md

146
Makefile
View File

@ -1,26 +1,134 @@
CC=gcc
CFLAGS=-std=gnu11 -Wall
LIBS=-lssl -lcrypto -lmagic -lz -lmaxminddb -lbrotlienc
CFLAGS=-std=gnu11 -Wno-unused-but-set-variable -D_DEFAULT_SOURCE -D_GNU_SOURCE -D_BSD_SOURCE -D_SVID_SOURCE -D_POSIX_C_SOURCE=200809L
LDFLAGS=-pthread -lssl -lcrypto -lmagic -lz -lmaxminddb -lbrotlienc
DEBIAN_OPTS=-D CACHE_MAGIC_FILE="\"/usr/share/file/magic.mgc\"" -D PHP_FPM_SOCKET="\"/var/run/php/php7.4-fpm.sock\""
DEBIAN_OPTS=-D CACHE_MAGIC_FILE="\"/usr/share/file/magic.mgc\"" -D PHP_FPM_SOCKET="\"/var/run/php/php8.2-fpm.sock\""
.PHONY: all prod debug default debian permit clean test
all: prod
default: bin bin/lib bin/worker bin/res bin/sesimos
prod: CFLAGS += -O3
prod: default
debug: CFLAGS += -Wall -pedantic
debug: default
debian: CFLAGS += $(DEBIAN_OPTS)
debian: prod
test: CFLAGS += -include test/mock_*.h
test: bin bin/test
bin/test
bin:
mkdir -p bin
bin/lib:
mkdir -p bin/lib
bin/worker:
mkdir -p bin/worker
bin/res:
mkdir -p bin/res
bin/test: test/mock_*.c test/test_*.c \
src/lib/utils.c src/lib/sock.c src/lib/list.c src/lib/http.c src/lib/http_static.c src/logger.c src/lib/error.c
$(CC) -o $@ $(CFLAGS) $^ -lcriterion
bin/%.o: src/%.c
$(CC) -c -o $@ $(CFLAGS) $<
bin/lib/%.o: src/lib/%.c
$(CC) -c -o $@ $(CFLAGS) $<
bin/worker/%.o: src/worker/%.c
$(CC) -c -o $@ $(CFLAGS) $<
bin/res/%.o: bin/res/%.txt
objcopy -I binary --rename-section .data=.rodata -O elf64-x86-64 $^ $@
bin/res/%.txt: res/%.*
cp $^ $@
/bin/echo -ne "\x00" >> $@
bin/sesimos: bin/server.o bin/logger.o bin/cache_handler.o bin/async.o bin/workers.o \
bin/worker/request_handler.o bin/worker/tcp_acceptor.o \
bin/worker/fastcgi_handler.o bin/worker/local_handler.o bin/worker/proxy_handler.o \
bin/worker/proxy_peer_handler.o \
bin/worker/ws_frame_handler.o bin/worker/chunk_handler.o bin/worker/fastcgi_frame_handler.o \
bin/lib/http_static.o bin/res/default.o bin/res/proxy.o bin/res/style.o \
bin/res/icon_error.o bin/res/icon_info.o bin/res/icon_success.o bin/res/icon_warning.o \
bin/res/globe.o \
bin/lib/compress.o bin/lib/config.o bin/lib/fastcgi.o bin/lib/geoip.o bin/lib/error.o \
bin/lib/http.o bin/lib/proxy.o bin/lib/sock.o bin/lib/uri.o \
bin/lib/utils.o bin/lib/websocket.o bin/lib/mpmc.o bin/lib/list.o
$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS)
bin/server.o: src/server.h src/defs.h src/cache_handler.h src/lib/config.h src/lib/sock.h \
src/lib/proxy.h src/lib/geoip.h src/lib/utils.h src/logger.h
bin/logger.o: src/logger.h
bin/cache_handler.o: src/cache_handler.h src/lib/utils.h src/lib/uri.h src/lib/compress.h src/logger.h
bin/async.o: src/async.h src/logger.h
bin/workers.o: src/workers.h src/lib/mpmc.h src/worker/func.h
bin/worker/request_handler.o: src/worker/func.h
bin/worker/tcp_acceptor.o: src/worker/func.h
bin/worker/fastcgi_handler.o: src/worker/func.h
bin/worker/local_handler.o: src/worker/func.h
bin/worker/proxy_handler.o: src/worker/func.h
bin/worker/proxy_peer_handler.o: src/worker/func.h
bin/worker/ws_frame_handler.o: src/worker/func.h
bin/worker/fastcgi_frame_handler.o: src/worker/func.h
bin/worker/chunk_handler.o: src/worker/func.h
bin/lib/compress.o: src/lib/compress.h
bin/lib/config.o: src/lib/config.h src/lib/utils.h src/lib/uri.h src/logger.h
bin/lib/fastcgi.o: src/lib/fastcgi.h src/server.h src/lib/utils.h src/lib/compress.h src/lib/http.h \
src/lib/uri.h src/lib/include/fastcgi.h src/logger.h
bin/lib/error.o: src/lib/error.h
bin/lib/geoip.o: src/lib/geoip.h
bin/lib/http.o: src/lib/http.h src/lib/utils.h src/lib/compress.h src/lib/sock.h src/logger.h
bin/lib/list.o: src/lib/list.h
bin/lib/mpmc.o: src/lib/mpmc.h src/logger.h
bin/lib/proxy.o: src/lib/proxy.h src/defs.h src/server.h src/lib/compress.h src/logger.h
bin/lib/sock.o: src/lib/sock.h
bin/lib/uri.o: src/lib/uri.h src/lib/utils.h
bin/lib/utils.o: src/lib/utils.h
bin/lib/websocket.o: src/lib/websocket.h src/defs.h src/lib/utils.h src/lib/sock.h src/logger.h
packages:
@echo "Installing packages..."
sudo apt install gcc php-fpm libmagic-dev libssl-dev libmaxminddb-dev
@echo "Finished downloading!"
permit:
sudo setcap 'cap_net_bind_service=+ep' "$(shell pwd)/bin/necronda-server"
sudo setcap 'cap_net_bind_service=+ep' "$(shell pwd)/bin/sesimos"
compile:
@mkdir -p bin
$(CC) src/lib/*.c -o bin/libnecrondaserver.so --shared -fPIC $(CFLAGS) $(LIBS)
$(CC) src/necronda-server.c -o bin/necronda-server $(CFLAGS) $(LIBS) \
-Lbin -lnecrondaserver -Wl,-rpath=$(shell pwd)/bin
compile-prod:
@mkdir -p bin
$(CC) src/lib/*.c -o bin/libnecrondaserver.so --shared -fPIC $(CFLAGS) $(LIBS) $(DEBIAN_OPTS) -O3
$(CC) src/necronda-server.c -o bin/necronda-server $(CFLAGS) $(LIBS) $(DEBIAN_OPTS) -O3 \
-Lbin -lnecrondaserver -Wl,-rpath=$(shell pwd)/bin
clean:
rm -rf bin/*

View File

@ -1,15 +1,18 @@
Necronda web server
===================
Sesimos Secure, simple, modern web server
===========================================
## Features
* Full IPv4 and IPv6 support
* TLS Server Name Inspection (SNI)
* Serving local files via HTTP and HTTPS
* File compression ([gzip](https://www.gzip.org/), [Brotli](https://www.brotli.org/)) and disk cache for compressed files
* File compression ([gzip](https://www.gzip.org/), [Brotli](https://www.brotli.org/))
* Disk cache for compressed files
* Reverse proxy for other HTTP and HTTPS servers
* Transparent WebSocket reverse proxy
* FastCGI support (e.g. [PHP-FPM](https://php-fpm.org/))
* Automatic path info detection (e.g. `/my/file/extra/path` -> script: `/my/file.php`, path info: `extra/path`)
* Support for [MaxMind's GeoIP Database](https://www.maxmind.com/en/geoip2-services-and-databases)
* Optional DNS reverse lookup for connecting hosts
* Automatic URL rewrite (e.g. `/index.html` -> `/`, `/test.php` -> `/test`)
@ -18,18 +21,17 @@ Necronda web server
## Configuration
See [docs/example.conf](docs/example.conf) for more details.
See [doc/example.conf](doc/example.conf) for more details.
### Global directives
* `geoip_dir` (optional) - path to a directory containing GeoIP databases
* `dns_server` (optional) - address of a DNS server
### Configuration
* `[cert <cert-name]` - begins section for a certificate
* `[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>`

22
architecture.md Normal file
View File

@ -0,0 +1,22 @@
# Architecture
* logger (1)
* listener (2) - 80, 443
* cache_handler (1)
* connection_initializer
* request_handler
* local_handler
* proxy_handler
* ws_handler
* fastcgi_handler
* -> logger
* main -> listener
* listener -> connection_handler
* connection_initializer -> request_handler
* request_handler -> local_handler -> request_handler
* local_handler -> fastcgi_handler -> request_handler
* request_handler -> rp_handler -> request_handler
* proxy_handler -> ws_handler -> request_handler

View File

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

22
res/default.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>%1$i %2$s - %7$s</title>
<meta charset="UTF-8"/>
<meta name="theme-color" content="%6$s"/>
<meta name="color-scheme" content="light dark"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico"/>
<link rel="stylesheet" type="text/css" href="/.sesimos/res/style.css"/>
<link rel="alternate icon" type="image/svg+xml" sizes="any" href="%5$s"/>
<style>html{--color:var(--%4$s);}</style>
</head>
<body>
<main>
<section>
%3$s%9$s <div class="footer"><a href="https://%7$s/">%7$s</a> - %10$s</div>
</section>
%8$s </main>
</body>
</html>

3
res/globe.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg">
<path d="M2,32 a30,30,0,1,0,60,0 a30,30,0,1,0,-60,0 L62,32 M6,16 L58,16 M6,48 L58,48 M32,2 L32,62 a15,30,0,1,0,0,-60 a15,30,0,1,0,0,60 Z" stroke="#008000" stroke-width="2" fill="#00000000"/>
</svg>

After

Width:  |  Height:  |  Size: 266 B

3
res/icon_error.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<text x="4" y="12" fill="#C00000" style="font-family:'Arial',sans-serif">:(</text>
</svg>

After

Width:  |  Height:  |  Size: 158 B

3
res/icon_info.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<text x="4" y="12" fill="#606060" style="font-family:'Arial',sans-serif">:)</text>
</svg>

After

Width:  |  Height:  |  Size: 158 B

3
res/icon_success.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<text x="4" y="12" fill="#008000" style="font-family:'Arial',sans-serif">:)</text>
</svg>

After

Width:  |  Height:  |  Size: 158 B

3
res/icon_warning.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<text x="4" y="12" fill="#E0C000" style="font-family:'Arial',sans-serif">:)</text>
</svg>

After

Width:  |  Height:  |  Size: 158 B

31
res/proxy.html Normal file
View File

@ -0,0 +1,31 @@
<section class="error-ctx">
<div class="box%1$s">
<div class="content">
<span>Client</span>
<img src="/.sesimos/res/globe.svg"/>
<span>Your Browser</span>
</div>
<div class="arrow request%2$s"></div>
</div>
<div class="border%8$s"></div>
<div class="box%3$s">
<div class="content">
<span>Reverse Proxy</span>
<h3>%10$03i</h3>
<h4>%11$s</h4>
<span>%15$s</span>
</div>
<div class="arrow request%4$s"></div>
<div class="arrow response%5$s"></div>
</div>
<div class="border%9$s"></div>
<div class="box%6$s">
<div class="content">
<span>Server</span>
<h3>%12$s</h3>
<h4>%13$s</h4>
<span>%14$s</span>
</div>
<div class="arrow response%7$s"></div>
</div>
</section>

66
res/style.css Normal file
View File

@ -0,0 +1,66 @@
html{
font-family:"Arial",sans-serif;
--error:#C00000;
--warning:#E0C000;
--success:#008000;
--info:#606060;
--soft:#808080;
}
body{background-color:#F0F0F0;margin:0;}
main{max-width:650px;margin:2em auto;}
section{margin:2em 1em;background-color:#FFFFFF;border: 1px solid var(--color);border-radius:4px;padding:1em;}
h1,h2,h3,h4,h5,h6{text-align:center;color:var(--color);font-weight:normal;}
h1{font-size:3em;margin:0.125em 0;}
h2{font-size:1.5em;margin:0.25em 0 1em 0;}
p{text-align:center;font-size:0.875em;}
div.footer{color:var(--soft);font-size:0.75em;text-align:center;margin:2em 0 0.5em 0;}
div.footer a{color:var(--soft);}
ul,ol{width:fit-content;margin:auto;}
pre{width:fit-content;margin:2em auto 0 auto;}
section.error-ctx{display:flex;padding:0;border:none;}
div.box{flex:100% 1 1;border:1px solid var(--info);color:var(--info);position:relative;padding:1em;box-sizing:border-box;text-align:center;}
div.box.error{border-color:var(--error);color:var(--error);}
div.box.success{border-color:var(--success);color:var(--success);}
div.arrow{position:absolute;height:20px;width:30px;z-index:10;background-repeat:no-repeat;background-size:contain;}
div.arrow.response{left:-17.5px;bottom:calc(33.3333% - 10px);}
div.arrow.request{right:-17.5px;top:calc(33.3333% - 10px);}
div.border{flex:1px 0 0;background-color:var(--info);}
div.border.error{background-color:var(--error);}
div.border.success{background-color:var(--success);}
div.content>span{display:block;color:var(--soft);font-size:0.75em;}
div.content>img{height:3.75rem;margin:0.75rem auto;display:block;}
h3{font-size:2.25em;margin:0.75rem 0 0 0;color:unset;height:2.5rem;}
h4{font-size:1em;margin:0 0 0.75rem 0;color:unset;height:1.25rem;}
div.arrow.request.success{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiMwMDgwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}
div.arrow.request.error{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjRkZGRkZGIiBzdHJva2U9IiNDMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}
div.arrow.response.success{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iIzAwODAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}
div.arrow.response.error{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iI0MwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}
@media(prefers-color-scheme:dark){
html{color:#FFFFFF;--soft:#404040;}
body{background-color:#101010;}
section{background-color:#181818;}
div.arrow.request.success{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjMTgxODE4IiBzdHJva2U9IiMwMDgwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}
div.arrow.request.error{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEsMSBMMjUsMSBMMjksMTAgTDI1LDE5IEwxLDE5IiBmaWxsPSIjMTgxODE4IiBzdHJva2U9IiNDMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPgo=');}
div.arrow.response.success{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiMxODE4MTgiIHN0cm9rZT0iIzAwODAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}
div.arrow.response.error{background-image:url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAiIGhlaWdodD0iMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTI5LDE5IEw1LDE5IEwxLDEwIEw1LDEgTDI5LDEiIGZpbGw9IiMxODE4MTgiIHN0cm9rZT0iI0MwMDAwMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PC9zdmc+Cg==');}
}
@media(min-width:650px){
div.box:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px;border-right:none;}
div.box:last-child{border-top-right-radius:4px;border-bottom-right-radius:4px;border-left:none;}
div.box:not(:last-child):not(:first-child){border-left:none;border-right:none;}
}
@media(max-width:650px){
section.error-ctx{flex-direction:column;height:unset;}
div.box:first-child{border-top-left-radius:4px;border-top-right-radius:4px;border-bottom:none;padding-top:1em;}
div.box:last-child{border-bottom-right-radius:4px;border-bottom-left-radius:4px;border-top:none;padding-bottom:1em;}
div.box:not(:last-child):not(:first-child){border-top:none;border-bottom:none;}
div.arrow.response{transform:rotate(90deg);top:-10px;left:calc(33.3333% - 22.5px);right:unset;}
div.arrow.request{transform:rotate(90deg);bottom:-10px;right:calc(33.3333% - 22.5px);top:unset;}
}

407
src/async.c Normal file
View File

@ -0,0 +1,407 @@
/**
* Sesimos - secure, simple, modern web server
* @brief Async handler
* @file src/async.c
* @author Lorenz Stechauner
* @date 2022-12-28
*/
#include "async.h"
#include "logger.h"
#include "lib/list.h"
#include "lib/utils.h"
#include <poll.h>
#include <sys/epoll.h>
#include <signal.h>
#include <errno.h>
#include <memory.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include <openssl/ssl.h>
#define ASYNC_MAX_EVENTS 16
typedef struct {
int fd;
sock *socket;
async_evt_t events;
int flags;
void *arg;
void (*cb)(void *);
void (*to_cb)(void *);
void (*err_cb)(void *);
} evt_listen_t;
typedef struct {
int n;
evt_listen_t *q[ASYNC_MAX_EVENTS];
} listen_queue_t;
static volatile listen_queue_t listen1, listen2, *listen_q = &listen1;
static volatile sig_atomic_t alive = 1;
static pthread_t thread = -1;
static sem_t lock;
static int epoll_fd;
static short async_a2p(async_evt_t events) {
short ret = 0;
if (events & ASYNC_IN) ret |= POLLIN;
if (events & ASYNC_PRI) ret |= POLLPRI;
if (events & ASYNC_OUT) ret |= POLLOUT;
if (events & ASYNC_ERR) ret |= POLLERR;
if (events & ASYNC_HUP) ret |= POLLHUP;
if (events & ASYNC_RDNORM) ret |= POLLRDNORM;
if (events & ASYNC_RDBAND) ret |= POLLRDBAND;
if (events & ASYNC_WRNORM) ret |= POLLWRNORM;
if (events & ASYNC_WRBAND) ret |= POLLWRBAND;
if (events & ASYNC_MSG) ret |= POLLMSG;
return ret;
}
static unsigned int async_a2e(async_evt_t events) {
unsigned int ret = 0;
if (events & ASYNC_IN) ret |= EPOLLIN;
if (events & ASYNC_PRI) ret |= EPOLLPRI;
if (events & ASYNC_OUT) ret |= EPOLLOUT;
if (events & ASYNC_ERR) ret |= EPOLLERR;
if (events & ASYNC_HUP) ret |= EPOLLHUP;
if (events & ASYNC_RDNORM) ret |= EPOLLRDNORM;
if (events & ASYNC_RDBAND) ret |= EPOLLRDBAND;
if (events & ASYNC_WRNORM) ret |= EPOLLWRNORM;
if (events & ASYNC_WRBAND) ret |= EPOLLWRBAND;
if (events & ASYNC_MSG) ret |= EPOLLMSG;
return ret;
}
static async_evt_t async_p2a(short events) {
async_evt_t ret = 0;
if (events & POLLIN) ret |= ASYNC_IN;
if (events & POLLPRI) ret |= ASYNC_PRI;
if (events & POLLOUT) ret |= ASYNC_OUT;
if (events & POLLERR) ret |= ASYNC_ERR;
if (events & POLLHUP) ret |= ASYNC_HUP;
if (events & POLLRDNORM) ret |= ASYNC_RDNORM;
if (events & POLLRDBAND) ret |= ASYNC_RDBAND;
if (events & POLLWRNORM) ret |= ASYNC_WRNORM;
if (events & POLLWRBAND) ret |= ASYNC_WRBAND;
if (events & POLLMSG) ret |= ASYNC_MSG;
return ret;
}
static async_evt_t async_e2a(unsigned int events) {
async_evt_t ret = 0;
if (events & EPOLLIN) ret |= ASYNC_IN;
if (events & EPOLLPRI) ret |= ASYNC_PRI;
if (events & EPOLLOUT) ret |= ASYNC_OUT;
if (events & EPOLLERR) ret |= ASYNC_ERR;
if (events & EPOLLHUP) ret |= ASYNC_HUP;
if (events & EPOLLRDNORM) ret |= ASYNC_RDNORM;
if (events & EPOLLRDBAND) ret |= ASYNC_RDBAND;
if (events & EPOLLWRNORM) ret |= ASYNC_WRNORM;
if (events & EPOLLWRBAND) ret |= ASYNC_WRBAND;
if (events & EPOLLMSG) ret |= ASYNC_MSG;
return ret;
}
static short async_e2p(unsigned int events) {
return async_a2p(async_e2a(events));
}
static unsigned int async_p2e(short events) {
return async_a2e(async_p2a(events));
}
static int async_add_to_queue(evt_listen_t *evt) {
while (sem_wait(&lock) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
return -1;
}
}
evt_listen_t *ptr = malloc(sizeof(evt_listen_t));
if (ptr == NULL) {
sem_post(&lock);
return -1;
}
memcpy(ptr, evt, sizeof(*evt));
listen_q->q[listen_q->n++] = ptr;
sem_post(&lock);
return 0;
}
static int async_exec(evt_listen_t *evt, async_evt_t r_events) {
int ret, e = errno;
if (r_events & evt->events) {
// specified event(s) occurred
if (!(evt->flags & ASYNC_IGNORE_PENDING) && evt->socket && !sock_has_pending(evt->socket, 0)) {
evt->err_cb(evt->arg);
ret = 0;
} else {
evt->cb(evt->arg);
ret = (evt->flags & ASYNC_KEEP) ? 1 : 0;
}
} else if (r_events & (POLLERR | POLLHUP | POLLNVAL)) {
// error occurred
evt->err_cb(evt->arg);
ret = 0;
} else {
// no event occurred
ret = -1;
}
errno = e;
return ret;
}
static int async_check(evt_listen_t *evt) {
struct pollfd fds[1] = {{
.fd = evt->fd,
.events = async_a2p(evt->events)
}};
// check, if fd is already ready
if (evt->events & ASYNC_IN && evt->socket && evt->socket->enc && SSL_pending(evt->socket->ssl) > 0) {
// ssl layer already ready
if (async_exec(evt, ASYNC_IN) == 0)
return 1;
}
switch (poll(fds, 1, 0)) {
case 1:
// fd already ready
if (async_exec(evt, async_p2a(fds[0].revents)) == 0)
return 1;
break;
case -1:
error("Unable to poll");
return -1;
}
return 0;
}
static int async_add(evt_listen_t *evt) {
if (async_check(evt) == 1)
return 0;
int ret = async_add_to_queue(evt);
if (ret == 0 && thread != -1)
pthread_kill(thread, SIGUSR1);
return ret;
}
int async_fd(int fd, async_evt_t events, int flags, void *arg, void cb(void *), void to_cb(void *), void err_cb(void *)) {
evt_listen_t evt = {
.fd = fd,
.socket = NULL,
.events = events,
.flags = flags,
.arg = arg,
.cb = cb,
.to_cb = to_cb,
.err_cb = err_cb,
};
return async_add(&evt);
}
int async(sock *s, async_evt_t events, int flags, void *arg, void cb(void *), void to_cb(void *), void err_cb(void *)) {
evt_listen_t evt = {
.fd = s->socket,
.socket = s,
.events = events,
.flags = flags,
.arg = arg,
.cb = cb,
.to_cb = to_cb,
.err_cb = err_cb,
};
return async_add(&evt);
}
int async_init(void) {
if (sem_init(&lock, 0, 1) != 0) {
return -1;
}
listen1.n = 0;
listen2.n = 0;
if ((epoll_fd = epoll_create1(0)) == -1) {
async_free();
return -1;
}
return 0;
}
void async_free(void) {
int e = errno;
sem_destroy(&lock);
close(epoll_fd);
errno = e;
}
void async_thread(void) {
struct epoll_event ev, events[ASYNC_MAX_EVENTS];
int num_fds;
long ts, min_ts, cur_ts;
volatile listen_queue_t *l;
evt_listen_t **local;
if ((local = list_create(sizeof(evt_listen_t *), 16)) == NULL) {
critical("Unable to create async local list");
return;
}
thread = pthread_self();
// main event loop
while (alive) {
// swap listen queue
while (sem_wait(&lock) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
critical("Unable to lock async queue");
return;
}
}
l = listen_q;
listen_q = (listen_q == &listen1) ? &listen2 : &listen1;
sem_post(&lock);
// fill local list and epoll instance with previously added queue entries
for (int i = 0; i < l->n; i++) {
evt_listen_t *evt = l->q[i];
local = list_append(local, &evt);
if (local == NULL) {
critical("Unable to resize async local list");
return;
}
ev.events = async_a2e(evt->events);
ev.data.ptr = evt;
while (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, evt->fd, &ev) == -1) {
if (errno == EEXIST) {
// fd already exists, delete old one
warning("Unable to add file descriptor to epoll instance");
errno = 0;
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, evt->fd, NULL) != -1)
continue;
} else if (errno == EBADF) {
// fd probably already closed
warning("Unable to add file descriptor to epoll instance");
errno = 0;
local = list_delete(local, &evt);
if (local == NULL) {
critical("Unable to resize async local list");
return;
}
break;
}
critical("Unable to add file descriptor to epoll instance");
return;
}
}
// reset size of queue
l->n = 0;
// TODO timeout calculation = O(n)
// calculate wait timeout
min_ts = -1000, cur_ts = clock_micros();
for (int i = 0; i < list_size(local); i++) {
evt_listen_t *evt = local[i];
if (!evt->socket || evt->socket->timeout_us < 0) continue;
ts = evt->socket->ts_last + evt->socket->timeout_us - cur_ts;
if (min_ts == -1000 || ts < min_ts) min_ts = ts;
}
// epoll is used in level-triggered mode, so buffers are taken into account
if ((num_fds = epoll_wait(epoll_fd, events, ASYNC_MAX_EVENTS, (int) (min_ts / 1000))) == -1) {
if (errno == EINTR) {
// interrupt
errno = 0;
continue;
} else {
// other error
critical("Unable to poll for events");
return;
}
}
for (int i = 0; i < num_fds; i++) {
evt_listen_t *evt = events[i].data.ptr;
if (!list_contains(local, &evt)) continue;
if (async_exec(evt, async_e2a(events[i].events)) == 0) {
logger_set_prefix("");
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, evt->fd, NULL) == -1) {
if (errno == EBADF || errno == ENOENT) {
// already closed fd or not found, do not die
warning("Unable to remove file descriptor from epoll instance");
errno = 0;
} else {
critical("Unable to remove file descriptor from epoll instance");
return;
}
}
local = list_delete(local, &evt);
if (local == NULL) {
critical("Unable to resize async local list");
return;
}
free(evt);
}
logger_set_prefix("");
}
// check, if some socket ran into a timeout
cur_ts = clock_micros();
for (int i = 0; i < list_size(local); i++) {
evt_listen_t *evt = local[i];
if (!evt->socket) continue;
if (evt->socket->timeout_us >= 0 && (cur_ts - evt->socket->ts_last) >= evt->socket->timeout_us) {
evt->to_cb(evt->arg);
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, evt->fd, NULL) == -1) {
if (errno == EBADF || errno == ENOENT) {
// already closed fd or not found, do not die
warning("Unable to remove file descriptor from epoll instance");
errno = 0;
} else {
critical("Unable to remove file descriptor from epoll instance");
return;
}
}
local = list_remove(local, i--);
}
}
logger_set_prefix("");
errno = 0;
}
// cleanup
for (int i = 0; i < list_size(local); i++) {
free(local[i]);
}
list_free(local);
}
void async_stop(void) {
alive = 0;
}

45
src/async.h Normal file
View File

@ -0,0 +1,45 @@
/**
* Sesimos - secure, simple, modern web server
* @brief Async handler (header file)
* @file src/async.h
* @author Lorenz Stechauner
* @date 2022-12-28
*/
#ifndef SESIMOS_ASYNC_H
#define SESIMOS_ASYNC_H
#include "lib/sock.h"
#define ASYNC_KEEP 1
#define ASYNC_IGNORE_PENDING 2
#define ASYNC_IN 0x001
#define ASYNC_PRI 0x002
#define ASYNC_OUT 0x004
#define ASYNC_ERR 0x008
#define ASYNC_HUP 0x010
#define ASYNC_RDNORM 0x040
#define ASYNC_RDBAND 0x080
#define ASYNC_WRNORM 0x100
#define ASYNC_WRBAND 0x200
#define ASYNC_MSG 0x400
#define ASYNC_WAIT_READ ASYNC_IN
#define ASYNC_WAIT_WRITE ASYNC_OUT
typedef unsigned int async_evt_t;
int async(sock *s, async_evt_t events, int flags, void *arg, void cb(void *), void to_cb(void *), void err_cb(void *));
int async_fd(int fd, async_evt_t events, int flags, void *arg, void cb(void *), void to_cb(void *), void err_cb(void *));
int async_init(void);
void async_free(void);
void async_thread(void);
void async_stop(void);
#endif //SESIMOS_ASYNC_H

455
src/cache_handler.c Normal file
View File

@ -0,0 +1,455 @@
/**
* sesimos - secure, simple, modern web server
* @brief File cache implementation
* @file src/cache_handler.c
* @author Lorenz Stechauner
* @date 2020-12-19
*/
#include "logger.h"
#include "cache_handler.h"
#include "lib/utils.h"
#include "lib/compress.h"
#include "lib/config.h"
#include <stdio.h>
#include <magic.h>
#include <string.h>
#include <errno.h>
#include <openssl/evp.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#define CACHE_BUF_SIZE 16
static magic_t magic;
static pthread_t thread;
static sem_t sem_free, sem_used, sem_lock, sem_cache, sem_magic;
volatile sig_atomic_t alive = 1;
typedef struct {
int rd;
int wr;
cache_entry_t *msgs[CACHE_BUF_SIZE];
} buf_t;
static buf_t buffer;
static int magic_init(void) {
if ((magic = magic_open(MAGIC_MIME)) == NULL) {
critical("Unable to open magic cookie");
return 1;
}
if (magic_load(magic, CACHE_MAGIC_FILE) != 0) {
critical("Unable to load magic cookie: %s", magic_error(magic));
return 1;
}
return 0;
}
static void magic_mime_type(const char *restrict filename, char *buf) {
while (sem_wait(&sem_magic) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
critical("Unable to lock magic semaphore");
return;
}
}
magic_setflags(magic, MAGIC_MIME_TYPE);
const char *type = magic_file(magic, filename);
if (strstarts(type, "text/")) {
if (strends(filename, ".css")) {
strcpy(buf, "text/css");
sem_post(&sem_magic);
return;
} else if (strends(filename, ".js")) {
strcpy(buf, "application/javascript");
sem_post(&sem_magic);
return;
} else if (strends(filename, ".xhtml")) {
strcpy(buf, "application/xhtml+xml");
sem_post(&sem_magic);
return;
}
}
strcpy(buf, type);
sem_post(&sem_magic);
}
static void magic_charset(const char *restrict filename, char *buf) {
while (sem_wait(&sem_magic) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
critical("Unable to lock magic semaphore");
return;
}
}
magic_setflags(magic, MAGIC_MIME_ENCODING);
strcpy(buf, magic_file(magic, filename));
sem_post(&sem_magic);
}
static void cache_free(void) {
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
host_config_t *hc = &config.hosts[i];
if (hc->type == CONFIG_TYPE_UNSET) break;
if (hc->type != CONFIG_TYPE_LOCAL) continue;
munmap(hc->cache, sizeof(cache_t));
}
magic_close(magic);
sem_destroy(&sem_lock);
sem_destroy(&sem_free);
sem_destroy(&sem_used);
sem_destroy(&sem_cache);
sem_destroy(&sem_magic);
}
static cache_entry_t *cache_get_entry(cache_t *cache, const char *filename) {
// search entry
cache_entry_t *entry;
for (int i = 0; i < CACHE_ENTRIES; i++) {
entry = &cache->entries[i];
if (entry->filename[0] == 0) break;
if (streq(entry->filename, filename)) {
// found
return entry;
}
}
// not found
return NULL;
}
static cache_entry_t *cache_get_new_entry(cache_t *cache) {
// globally lock cache
while (sem_wait(&sem_cache) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
return NULL;
}
}
// search empty slot
cache_entry_t *entry;
for (int i = 0; i < CACHE_ENTRIES; i++) {
entry = &cache->entries[i];
if (entry->filename[0] == 0) {
// unlock cache
sem_post(&sem_cache);
return entry;
}
}
// unlock cache
sem_post(&sem_cache);
// not found
return NULL;
}
static void cache_process_entry(cache_entry_t *entry) {
char buf[16384], comp_buf[16384], filename_comp_gz[256], filename_comp_br[256];
info("Hashing file %s", entry->filename);
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
EVP_DigestInit(ctx, EVP_sha256());
FILE *file = fopen(entry->filename, "rb");
int compress = mime_is_compressible(entry->meta.type);
compress_ctx comp_ctx;
FILE *comp_file_gz = NULL, *comp_file_br = NULL;
if (compress) {
sprintf(buf, "%.*s/.sesimos", entry->webroot_len, entry->filename);
if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
error("Unable to create directory %s", buf);
goto comp_err;
}
sprintf(buf, "%.*s/.sesimos/cache", entry->webroot_len, entry->filename);
if (mkdir(buf, 0700) != 0 && errno != EEXIST) {
error("Unable to create directory %s", buf);
goto comp_err;
}
errno = 0;
char *rel_path = entry->filename + entry->webroot_len + 1;
for (int j = 0; j < strlen(rel_path); j++) {
buf[j] = (char) ((rel_path[j] == '/') ? '_' : rel_path[j]);
}
buf[strlen(rel_path)] = 0;
int p_len_gz = snprintf(filename_comp_gz, sizeof(filename_comp_gz),
"%.*s/.sesimos/cache/%s.gz",
entry->webroot_len, entry->filename, buf);
int p_len_br = snprintf(filename_comp_br, sizeof(filename_comp_br),
"%.*s/.sesimos/cache/%s.br",
entry->webroot_len, entry->filename, buf);
if (p_len_gz < 0 || p_len_gz >= sizeof(filename_comp_gz) || p_len_br < 0 || p_len_br >= sizeof(filename_comp_br)) {
error("Unable to open cached file: File name for compressed file too long");
goto comp_err;
}
info("Compressing file %s", entry->filename);
comp_file_gz = fopen(filename_comp_gz, "wb");
comp_file_br = fopen(filename_comp_br, "wb");
if (comp_file_gz == NULL || comp_file_br == NULL) {
error("Unable to open cached file");
comp_err:
compress = 0;
} else {
if ((compress_init(&comp_ctx, COMPRESS_GZ | COMPRESS_BR | (mime_is_text(entry->meta.type) ? COMPRESS_UTF8 : 0))) != 0) {
error("Unable to init compression");
compress = 0;
fclose(comp_file_gz);
fclose(comp_file_br);
}
}
}
for (unsigned long read, avail_in, avail_out; (read = fread(buf, 1, sizeof(buf), file)) > 0;) {
EVP_DigestUpdate(ctx, buf, read);
if (compress) {
avail_in = read;
do {
avail_out = sizeof(comp_buf);
compress_compress_mode(&comp_ctx, COMPRESS_GZ, buf + read - avail_in, &avail_in, comp_buf, &avail_out, feof(file));
fwrite(comp_buf, 1, sizeof(comp_buf) - avail_out, comp_file_gz);
} while (avail_in != 0 || avail_out != sizeof(comp_buf));
avail_in = read;
do {
avail_out = sizeof(comp_buf);
compress_compress_mode(&comp_ctx, COMPRESS_BR, buf + read - avail_in, &avail_in, comp_buf, &avail_out, feof(file));
fwrite(comp_buf, 1, sizeof(comp_buf) - avail_out, comp_file_br);
} while (avail_in != 0 || avail_out != sizeof(comp_buf));
}
}
if (compress) {
compress_free(&comp_ctx);
fclose(comp_file_gz);
fclose(comp_file_br);
info("Finished compressing file %s", entry->filename);
strcpy(entry->meta.filename_comp_gz, filename_comp_gz);
strcpy(entry->meta.filename_comp_br, filename_comp_br);
} else {
memset(entry->meta.filename_comp_gz, 0, sizeof(entry->meta.filename_comp_gz));
memset(entry->meta.filename_comp_br, 0, sizeof(entry->meta.filename_comp_br));
}
unsigned int md_len;
unsigned char hash[EVP_MAX_MD_SIZE];
EVP_DigestFinal(ctx, hash, &md_len);
EVP_MD_CTX_free(ctx);
memset(entry->meta.etag, 0, sizeof(entry->meta.etag));
for (int j = 0; j < md_len; j++) {
sprintf(entry->meta.etag + j * 2, "%02x", hash[j]);
}
fclose(file);
entry->flags &= !CACHE_DIRTY;
info("Finished hashing file %s", entry->filename);
}
static void *cache_thread(void *arg) {
logger_set_name("cache");
while (alive) {
pthread_testcancel();
if (sem_wait(&sem_used) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
error("Unable to lock semaphore");
errno = 0;
break;
}
}
cache_entry_t *entry = buffer.msgs[buffer.wr];
buffer.wr = (buffer.wr + 1) % CACHE_BUF_SIZE;
cache_process_entry(entry);
// unlock slot in buffer
sem_post(&sem_free);
}
cache_free();
return NULL;
}
int cache_init(void) {
char buf[512];
int ret, fd;
if ((ret = magic_init()) != 0)
return ret;
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
host_config_t *hc = &config.hosts[i];
if (hc->type == CONFIG_TYPE_UNSET) break;
if (hc->type != CONFIG_TYPE_LOCAL) continue;
sprintf(buf, "%s/.sesimos", hc->local.webroot);
if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
critical("Unable to create directory %s", buf);
return 1;
}
errno = 0;
sprintf(buf, "%s/.sesimos/metadata", hc->local.webroot);
if ((fd = open(buf, O_CREAT | O_RDWR, 0600)) == -1) {
critical("Unable to open file %s", buf);
return 1;
}
if (ftruncate(fd, sizeof(cache_t)) == -1) {
critical("Unable to truncate file %s", buf);
return 1;
}
if ((hc->cache = mmap(NULL, sizeof(cache_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == NULL) {
critical("Unable to map file %s", buf);
close(fd);
return 1;
}
close(fd);
errno = 0;
}
// try to initialize all three semaphores
if (sem_init(&sem_lock, 0, 1) != 0 ||
sem_init(&sem_free, 0, 1) != 0 ||
sem_init(&sem_used, 0, 0) != 0 ||
sem_init(&sem_cache, 0, 1) != 0 ||
sem_init(&sem_magic, 0, 1) != 0)
{
critical("Unable to initialize semaphore");
return -1;
}
// initialize read/write heads
buffer.rd = 0;
buffer.wr = 0;
pthread_create(&thread, NULL, cache_thread, NULL);
return 0;
}
void cache_stop(void) {
alive = 0;
pthread_kill(thread, SIGUSR1);
}
int cache_join(void) {
return pthread_join(thread, NULL);
}
static void cache_mark_entry_dirty(cache_entry_t *entry) {
if (entry->flags & CACHE_DIRTY)
return;
entry->flags |= CACHE_DIRTY;
memset(entry->meta.etag, 0, sizeof(entry->meta.etag));
memset(entry->meta.filename_comp_gz, 0, sizeof(entry->meta.filename_comp_gz));
memset(entry->meta.filename_comp_br, 0, sizeof(entry->meta.filename_comp_br));
while (sem_wait(&sem_free) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
error("Unable to lock semaphore");
errno = 0;
}
return;
}
// try to lock buffer
while (sem_wait(&sem_lock) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
error("Unable to lock semaphore");
errno = 0;
}
return;
}
// write to buffer
buffer.msgs[buffer.rd] = entry;
buffer.rd = (buffer.rd + 1) % CACHE_BUF_SIZE;
// unlock buffer
sem_post(&sem_lock);
// unlock slot in buffer for logger
sem_post(&sem_used);
}
static void cache_update_entry(cache_entry_t *entry, const char *filename, const char *webroot) {
entry->meta.mtime = stat_mtime(filename);
entry->webroot_len = (unsigned char) strlen(webroot);
strcpy(entry->filename, filename);
magic_mime_type(filename, entry->meta.type);
magic_charset(filename, entry->meta.charset);
cache_mark_entry_dirty(entry);
}
void cache_mark_dirty(cache_t *cache, const char *filename) {
cache_entry_t *entry = cache_get_entry(cache, filename);
if (entry) cache_mark_entry_dirty(entry);
}
void cache_init_uri(cache_t *cache, http_uri *uri) {
if (!uri->filename)
return;
cache_entry_t *entry = cache_get_entry(cache, uri->filename);
if (!entry) {
// no entry found -> create new entry
if ((entry = cache_get_new_entry(cache))) {
cache_update_entry(entry, uri->filename, uri->webroot);
uri->meta = &entry->meta;
} else {
warning("No empty cache entry slot found");
}
} else {
uri->meta = &entry->meta;
if (entry->flags & CACHE_DIRTY)
return;
// check, if file has changed
if (uri->meta->mtime != stat_mtime(uri->filename)) {
// modify time has changed
cache_update_entry(entry, uri->filename, uri->webroot);
}
}
}

46
src/cache_handler.h Normal file
View File

@ -0,0 +1,46 @@
/**
* sesimos - secure, simple, modern web server
* @brief File cache implementation (header file)
* @file src/cache_handler.h
* @author Lorenz Stechauner
* @date 2020-12-19
*/
#ifndef SESIMOS_CACHE_HANDLER_H
#define SESIMOS_CACHE_HANDLER_H
#include "lib/uri.h"
#define CACHE_ENTRIES 1024
#define CACHE_DIRTY 1
#ifndef CACHE_MAGIC_FILE
# define CACHE_MAGIC_FILE "/usr/share/file/misc/magic.mgc"
#endif
typedef struct {
char filename[256];
unsigned char webroot_len;
unsigned char flags;
metadata_t meta;
} cache_entry_t;
typedef struct {
char sig[6];
unsigned char ver;
cache_entry_t entries[CACHE_ENTRIES];
} cache_t;
int cache_init(void);
void cache_stop(void);
int cache_join(void);
void cache_mark_dirty(cache_t *cache, const char *filename);
void cache_init_uri(cache_t *cache, http_uri *uri);
#endif //SESIMOS_CACHE_HANDLER_H

View File

@ -1,898 +0,0 @@
/**
* Necronda Web Server
* Client connection and request handlers
* src/client.c
* Lorenz Stechauner, 2020-12-03
*/
#include "lib/utils.h"
#include "lib/config.h"
#include "lib/sock.h"
#include "lib/http.h"
#include "lib/rev_proxy.h"
#include "lib/fastcgi.h"
#include "lib/cache.h"
#include "lib/geoip.h"
#include "lib/compress.h"
#include <string.h>
#include <sys/select.h>
#include <errno.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <signal.h>
#include <arpa/inet.h>
int server_keep_alive = 1;
struct timeval client_timeout = {.tv_sec = CLIENT_TIMEOUT, .tv_usec = 0};
int server_keep_alive;
char *log_client_prefix, *log_conn_prefix, *log_req_prefix, *client_geoip;
char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr, *client_host_str;
struct timeval client_timeout;
host_config *get_host_config(const char *host) {
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
host_config *hc = &config->hosts[i];
if (hc->type == CONFIG_TYPE_UNSET) break;
if (strcmp(hc->name, host) == 0) return hc;
if (hc->name[0] == '*' && hc->name[1] == '.') {
const char *pos = strstr(host, hc->name + 1);
if (pos != NULL && strlen(pos) == strlen(hc->name + 1)) return hc;
}
}
return NULL;
}
void client_terminate() {
server_keep_alive = 0;
}
int client_websocket_handler() {
// TODO implement client_websocket_handler
return 0;
}
int client_request_handler(sock *client, unsigned long client_num, unsigned int req_num) {
struct timespec begin, end;
long ret;
int client_keep_alive;
char buf0[1024], buf1[1024];
char msg_buf[8192], msg_pre_buf_1[4096], msg_pre_buf_2[4096], err_msg[256];
char msg_content[1024];
char buffer[CHUNK_SIZE];
char host[256], *host_ptr, *hdr_connection;
msg_buf[0] = 0;
err_msg[0] = 0;
msg_content[0] = 0;
host_config *conf = NULL;
FILE *file = NULL;
long content_length = 0;
int accept_if_modified_since = 0;
int use_fastcgi = 0;
int use_rev_proxy = 0;
int p_len;
fastcgi_conn fcgi_conn = {.socket = 0, .req_id = 0};
http_status custom_status;
http_res res = {.version = "1.1", .status = http_get_status(501), .hdr.field_num = 0};
http_status_ctx ctx = {.status = 0, .origin = NONE};
clock_gettime(CLOCK_MONOTONIC, &begin);
fd_set socket_fds;
FD_ZERO(&socket_fds);
FD_SET(client->socket, &socket_fds);
client_timeout.tv_sec = CLIENT_TIMEOUT;
client_timeout.tv_usec = 0;
ret = select(client->socket + 1, &socket_fds, NULL, NULL, &client_timeout);
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
http_add_header_field(&res.hdr, "Server", SERVER_STR);
if (ret <= 0) {
if (errno != 0) {
return 1;
}
client_keep_alive = 0;
res.status = http_get_status(408);
goto respond;
}
clock_gettime(CLOCK_MONOTONIC, &begin);
http_req req;
ret = http_receive_request(client, &req);
if (ret != 0) {
client_keep_alive = 0;
if (ret < 0) {
goto abort;
} else if (ret == 1) {
sprintf(err_msg, "Unable to parse http header: Invalid header format.");
} else if (ret == 2) {
sprintf(err_msg, "Unable to parse http header: Invalid method.");
} else if (ret == 3) {
sprintf(err_msg, "Unable to parse http header: Invalid version.");
} else if (ret == 4) {
sprintf(err_msg, "Unable to parse http header: Header contains illegal characters.");
} else if (ret == 5) {
sprintf(err_msg, "Unable to parse http header: End of header not found.");
}
res.status = http_get_status(400);
goto respond;
}
hdr_connection = http_get_header_field(&req.hdr, "Connection");
client_keep_alive = hdr_connection != NULL &&
(strcmp(hdr_connection, "keep-alive") == 0 || strcmp(hdr_connection, "Keep-Alive") == 0);
host_ptr = http_get_header_field(&req.hdr, "Host");
if (host_ptr != NULL && strlen(host_ptr) > 255) {
host[0] = 0;
res.status = http_get_status(400);
sprintf(err_msg, "Host header field is too long.");
goto respond;
} else if (host_ptr == NULL || strchr(host_ptr, '/') != NULL) {
if (strchr(client_addr_str, ':') == NULL) {
strcpy(host, client_addr_str);
} else {
sprintf(host, "[%s]", client_addr_str);
}
res.status = http_get_status(400);
sprintf(err_msg, "The client provided no or an invalid Host header field.");
goto respond;
} else {
strcpy(host, host_ptr);
}
sprintf(log_req_prefix, "[%6i][%s%*s%s]%s ", getpid(), BLD_STR, INET6_ADDRSTRLEN, host, CLR_STR, log_client_prefix);
log_prefix = log_req_prefix;
print(BLD_STR "%s %s" CLR_STR, req.method, req.uri);
conf = get_host_config(host);
if (conf == NULL) {
print("Host unknown, redirecting to default");
res.status = http_get_status(307);
sprintf(buf0, "https://%s%s", DEFAULT_HOST, req.uri);
http_add_header_field(&res.hdr, "Location", buf0);
goto respond;
}
http_uri uri;
unsigned char dir_mode = conf->type == CONFIG_TYPE_LOCAL ? conf->local.dir_mode : URI_DIR_MODE_NO_VALIDATION;
ret = uri_init(&uri, conf->local.webroot, req.uri, dir_mode);
if (ret != 0) {
if (ret == 1) {
sprintf(err_msg, "Invalid URI: has to start with slash.");
res.status = http_get_status(400);
} else if (ret == 2) {
sprintf(err_msg, "Invalid URI: contains relative path change (/../).");
res.status = http_get_status(400);
} else if (ret == 3) {
sprintf(err_msg, "The specified webroot directory does not exist.");
res.status = http_get_status(404);
} else {
res.status = http_get_status(500);
}
goto respond;
}
if (dir_mode != URI_DIR_MODE_NO_VALIDATION) {
ssize_t size = sizeof(buf0);
url_decode(req.uri, buf0, &size);
int change_proto = strncmp(uri.uri, "/.well-known/", 13) != 0 && !client->enc;
if (strcmp(uri.uri, buf0) != 0 || change_proto) {
res.status = http_get_status(308);
size = sizeof(buf0);
url_encode(uri.uri, buf0, &size);
if (change_proto) {
p_len = snprintf(buf1, sizeof(buf1), "https://%s%s", host, buf0);
if (p_len < 0 || p_len >= sizeof(buf1)) {
res.status = http_get_status(500);
print(ERR_STR "Header field 'Location' too long" CLR_STR);
goto respond;
}
http_add_header_field(&res.hdr, "Location", buf1);
} else {
http_add_header_field(&res.hdr, "Location", buf0);
}
goto respond;
}
} else if (!client->enc) {
res.status = http_get_status(308);
sprintf(buf0, "https://%s%s", host, req.uri);
http_add_header_field(&res.hdr, "Location", buf0);
goto respond;
}
if (http_get_header_field(&req.hdr, "Transfer-Encoding") != NULL) {
sprintf(err_msg, "This server is unable to process requests with the Transfer-Encoding header field.");
res.status = http_get_status(501);
goto respond;
}
if (conf->type == CONFIG_TYPE_LOCAL) {
if (strcmp(req.method, "TRACE") == 0) {
res.status = http_get_status(200);
http_add_header_field(&res.hdr, "Content-Type", "message/http");
content_length = snprintf(msg_buf, sizeof(msg_buf) - content_length, "%s %s HTTP/%s\r\n",
req.method, req.uri, req.version);
for (int i = 0; i < req.hdr.field_num; i++) {
content_length += snprintf(msg_buf + content_length, sizeof(msg_buf) - content_length, "%s: %s\r\n",
req.hdr.fields[i][0], req.hdr.fields[i][1]);
}
goto respond;
}
if (strncmp(uri.req_path, "/.well-known/", 13) == 0) {
http_add_header_field(&res.hdr, "Access-Control-Allow-Origin", "*");
}
if (strncmp(uri.req_path, "/.well-known/", 13) != 0 && strstr(uri.path, "/.") != NULL) {
res.status = http_get_status(403);
sprintf(err_msg, "Parts of this URI are hidden.");
goto respond;
} else if (uri.filename == NULL && (int) uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
res.status = http_get_status(403);
sprintf(err_msg, "It is not allowed to list the contents of this directory.");
goto respond;
} else if (uri.filename == NULL && (int) !uri.is_static && (int) uri.is_dir && strlen(uri.pathinfo) == 0) {
// TODO list directory contents
res.status = http_get_status(501);
sprintf(err_msg, "Listing contents of an directory is currently not implemented.");
goto respond;
} else if (uri.filename == NULL || (strlen(uri.pathinfo) > 0 && (int) uri.is_static)) {
res.status = http_get_status(404);
goto respond;
} else if (strlen(uri.pathinfo) != 0 && conf->local.dir_mode != URI_DIR_MODE_INFO) {
res.status = http_get_status(404);
goto respond;
}
if (uri.is_static) {
res.status = http_get_status(200);
http_add_header_field(&res.hdr, "Accept-Ranges", "bytes");
if (strcmp(req.method, "GET") != 0 && strcmp(req.method, "HEAD") != 0) {
res.status = http_get_status(405);
goto respond;
}
if (http_get_header_field(&req.hdr, "Content-Length") != NULL) {
res.status = http_get_status(400);
sprintf(err_msg, "A GET request must not contain a payload");
goto respond;
}
ret = uri_cache_init(&uri);
if (ret != 0) {
res.status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with internal file cache.");
goto respond;
}
char *last_modified = http_format_date(uri.meta->stat.st_mtime, buf0, sizeof(buf0));
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
sprintf(buf1, "%s; charset=%s", uri.meta->type, uri.meta->charset);
http_add_header_field(&res.hdr, "Content-Type", buf1);
char *accept_encoding = http_get_header_field(&req.hdr, "Accept-Encoding");
int enc = 0;
if (accept_encoding != NULL) {
if (uri.meta->filename_comp_br[0] != 0 && strstr(accept_encoding, "br") != NULL) {
file = fopen(uri.meta->filename_comp_br, "rb");
if (file == NULL) {
cache_filename_comp_invalid(uri.filename);
} else {
http_add_header_field(&res.hdr, "Content-Encoding", "br");
enc = COMPRESS_BR;
}
} else if (uri.meta->filename_comp_gz[0] != 0 && strstr(accept_encoding, "gzip") != NULL) {
file = fopen(uri.meta->filename_comp_gz, "rb");
if (file == NULL) {
cache_filename_comp_invalid(uri.filename);
} else {
http_add_header_field(&res.hdr, "Content-Encoding", "gzip");
enc = COMPRESS_GZ;
}
}
if (enc != 0) {
http_add_header_field(&res.hdr, "Vary", "Accept-Encoding");
}
}
if (uri.meta->etag[0] != 0) {
if (enc) {
sprintf(buf0, "%s-%s", uri.meta->etag,
(enc & COMPRESS_BR) ? "br" : (enc & COMPRESS_GZ) ? "gzip" : "");
http_add_header_field(&res.hdr, "ETag", buf0);
} else {
http_add_header_field(&res.hdr, "ETag", uri.meta->etag);
}
}
if (strncmp(uri.meta->type, "text/", 5) == 0) {
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=3600");
} else {
http_add_header_field(&res.hdr, "Cache-Control", "public, max-age=86400");
}
char *if_modified_since = http_get_header_field(&req.hdr, "If-Modified-Since");
char *if_none_match = http_get_header_field(&req.hdr, "If-None-Match");
if ((if_none_match != NULL && strstr(if_none_match, uri.meta->etag) == NULL) ||
(accept_if_modified_since && if_modified_since != NULL &&
strcmp(if_modified_since, last_modified) == 0)) {
res.status = http_get_status(304);
goto respond;
}
char *range = http_get_header_field(&req.hdr, "Range");
if (range != NULL) {
if (strlen(range) <= 6 || strncmp(range, "bytes=", 6) != 0) {
res.status = http_get_status(416);
http_remove_header_field(&res.hdr, "Content-Type", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Last-Modified", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "ETag", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
goto respond;
}
range += 6;
char *ptr = strchr(range, '-');
if (ptr == NULL) {
res.status = http_get_status(416);
goto respond;
}
file = fopen(uri.filename, "rb");
fseek(file, 0, SEEK_END);
unsigned long file_len = ftell(file);
fseek(file, 0, SEEK_SET);
if (file_len == 0) {
content_length = 0;
goto respond;
}
long num1 = 0;
long num2 = (long) file_len - 1;
if (ptr != range) num1 = (long) strtoul(range, NULL, 10);
if (ptr[1] != 0) num2 = (long) strtoul(ptr + 1, NULL, 10);
if (num1 >= file_len || num2 >= file_len || num1 > num2) {
res.status = http_get_status(416);
goto respond;
}
sprintf(buf0, "bytes %li-%li/%li", num1, num2, file_len);
http_add_header_field(&res.hdr, "Content-Range", buf0);
res.status = http_get_status(206);
fseek(file, num1, SEEK_SET);
content_length = num2 - num1 + 1;
goto respond;
}
if (file == NULL) {
file = fopen(uri.filename, "rb");
}
fseek(file, 0, SEEK_END);
content_length = ftell(file);
fseek(file, 0, SEEK_SET);
} else {
int mode;
if (strcmp(uri.filename + strlen(uri.filename) - 4, ".ncr") == 0) {
mode = FASTCGI_NECRONDA;
} else if (strcmp(uri.filename + strlen(uri.filename) - 4, ".php") == 0) {
mode = FASTCGI_PHP;
} else {
res.status = http_get_status(500);
print(ERR_STR "Invalid FastCGI extension: %s" CLR_STR, uri.filename);
goto respond;
}
struct stat statbuf;
stat(uri.filename, &statbuf);
char *last_modified = http_format_date(statbuf.st_mtime, buf0, sizeof(buf0));
http_add_header_field(&res.hdr, "Last-Modified", last_modified);
res.status = http_get_status(200);
if (fastcgi_init(&fcgi_conn, mode, client_num, req_num, client, &req, &uri) != 0) {
res.status = http_get_status(503);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
goto respond;
}
char *client_content_length = http_get_header_field(&req.hdr, "Content-Length");
if (client_content_length != NULL) {
unsigned long client_content_len = strtoul(client_content_length, NULL, 10);
ret = fastcgi_receive(&fcgi_conn, client, client_content_len);
if (ret != 0) {
if (ret < 0) {
goto abort;
} else {
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
}
res.status = http_get_status(502);
goto respond;
}
}
fastcgi_close_stdin(&fcgi_conn);
ret = fastcgi_header(&fcgi_conn, &res, err_msg);
if (ret != 0) {
if (ret < 0) goto abort;
goto respond;
}
char *status = http_get_header_field(&res.hdr, "Status");
if (status != NULL) {
int status_code = (int) strtoul(status, NULL, 10);
res.status = http_get_status(status_code);
http_remove_header_field(&res.hdr, "Status", HTTP_REMOVE_ALL);
if (res.status == NULL && status_code >= 100 && status_code <= 999) {
custom_status.code = status_code;
strcpy(custom_status.type, "");
strcpy(custom_status.msg, status + 4);
res.status = &custom_status;
} else if (res.status == NULL) {
res.status = http_get_status(500);
sprintf(err_msg, "The status code was set to an invalid or unknown value.");
goto respond;
}
}
const char *content_length_f = http_get_header_field(&res.hdr, "Content-Length");
content_length = (content_length_f == NULL) ? -1 : strtol(content_length_f, NULL, 10);
const char *content_type = http_get_header_field(&res.hdr, "Content-Type");
const char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
if (content_encoding == NULL &&
content_type != NULL &&
strncmp(content_type, "text/html", 9) == 0 &&
content_length != -1 &&
content_length <= sizeof(msg_content) - 1)
{
fastcgi_dump(&fcgi_conn, msg_content, sizeof(msg_content));
goto respond;
}
use_fastcgi = 1;
if (content_length != -1 && content_length < 1024000) {
use_fastcgi |= FASTCGI_COMPRESS_HOLD;
}
content_length = -1;
int http_comp = http_get_compression(&req, &res);
if (http_comp & COMPRESS) {
if (http_comp & COMPRESS_BR) {
use_fastcgi |= FASTCGI_COMPRESS_BR;
sprintf(buf0, "br");
} else if (http_comp & COMPRESS_GZ) {
use_fastcgi |= FASTCGI_COMPRESS_GZ;
sprintf(buf0, "gzip");
}
http_add_header_field(&res.hdr, "Vary", "Accept-Encoding");
http_add_header_field(&res.hdr, "Content-Encoding", buf0);
http_remove_header_field(&res.hdr, "Content-Length", HTTP_REMOVE_ALL);
}
if (http_get_header_field(&res.hdr, "Content-Length") == NULL) {
http_add_header_field(&res.hdr, "Transfer-Encoding", "chunked");
}
}
} else if (conf->type == CONFIG_TYPE_REVERSE_PROXY) {
print("Reverse proxy for " BLD_STR "%s:%i" CLR_STR, conf->rev_proxy.hostname, conf->rev_proxy.port);
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
ret = rev_proxy_init(&req, &res, &ctx, conf, client, &custom_status, err_msg);
use_rev_proxy = (ret == 0);
// Let 300 be formatted by origin server
if (use_rev_proxy && res.status->code >= 301 && res.status->code < 600) {
const char *content_type = http_get_header_field(&res.hdr, "Content-Type");
const char *content_length_f = http_get_header_field(&res.hdr, "Content-Length");
const char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
if (content_encoding == NULL && content_type != NULL && content_length_f != NULL &&
strncmp(content_type, "text/html", 9) == 0)
{
long content_len = strtol(content_length_f, NULL, 10);
if (content_len <= sizeof(msg_content) - 1) {
ctx.status = res.status->code;
ctx.origin = res.status->code >= 400 ? SERVER : NONE;
use_rev_proxy = 0;
rev_proxy_dump(msg_content, content_len);
}
}
}
/*
char *content_encoding = http_get_header_field(&res.hdr, "Content-Encoding");
if (use_rev_proxy && content_encoding == NULL) {
int http_comp = http_get_compression(&req, &res);
if (http_comp & COMPRESS_BR) {
use_rev_proxy |= REV_PROXY_COMPRESS_BR;
} else if (http_comp & COMPRESS_GZ) {
use_rev_proxy |= REV_PROXY_COMPRESS_GZ;
}
}
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
int chunked = transfer_encoding != NULL && strcmp(transfer_encoding, "chunked") == 0;
http_remove_header_field(&res.hdr, "Transfer-Encoding", HTTP_REMOVE_ALL);
ret = sprintf(buf0, "%s%s%s",
(use_rev_proxy & REV_PROXY_COMPRESS_BR) ? "br" :
((use_rev_proxy & REV_PROXY_COMPRESS_GZ) ? "gzip" : ""),
((use_rev_proxy & REV_PROXY_COMPRESS) && chunked) ? ", " : "",
chunked ? "chunked" : "");
if (ret > 0) {
http_add_header_field(&res.hdr, "Transfer-Encoding", buf0);
}
*/
} else {
print(ERR_STR "Unknown host type: %i" CLR_STR, conf->type);
res.status = http_get_status(501);
}
respond:
if (!use_rev_proxy) {
if (conf != NULL && conf->type == CONFIG_TYPE_LOCAL && uri.is_static && res.status->code == 405) {
http_add_header_field(&res.hdr, "Allow", "GET, HEAD, TRACE");
}
if (http_get_header_field(&res.hdr, "Accept-Ranges") == NULL) {
http_add_header_field(&res.hdr, "Accept-Ranges", "none");
}
if (!use_fastcgi && file == NULL) {
http_remove_header_field(&res.hdr, "Date", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Server", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Cache-Control", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Content-Type", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Content-Encoding", HTTP_REMOVE_ALL);
http_add_header_field(&res.hdr, "Date", http_get_date(buf0, sizeof(buf0)));
http_add_header_field(&res.hdr, "Server", SERVER_STR);
http_add_header_field(&res.hdr, "Cache-Control", "no-cache");
http_add_header_field(&res.hdr, "Content-Type", "text/html; charset=UTF-8");
// TODO list Locations on 3xx Redirects
const http_doc_info *info = http_get_status_info(res.status);
const http_status_msg *http_msg = http_get_error_msg(res.status);
if (res.status->code >= 300 && res.status->code < 400 && msg_content[0] == 0) {
const char *location = http_get_header_field(&res.hdr, "Location");
if (location != NULL) {
snprintf(msg_content, sizeof(msg_content),
"<ul>\n\t<li><a href=\"%1$s\">%1$s</a></li>\n</ul>\n", location);
}
}
char *rev_proxy_doc = "";
if (conf != NULL && conf->type == CONFIG_TYPE_REVERSE_PROXY) {
const http_status *status = http_get_status(ctx.status);
char stat_str[8];
sprintf(stat_str, "%03i", ctx.status);
sprintf(msg_pre_buf_2, http_rev_proxy_document,
" success",
(ctx.origin == CLIENT_REQ) ? " error" : " success",
(ctx.origin == INTERNAL) ? " error" : " success",
(ctx.origin == SERVER_REQ) ? " error" : (ctx.status == 0 ? "" : " success"),
(ctx.origin == CLIENT_RES) ? " error" : " success",
(ctx.origin == SERVER) ? " error" : (ctx.status == 0 ? "" : " success"),
(ctx.origin == SERVER_RES) ? " error" : (ctx.status == 0 ? "" : " success"),
(ctx.origin == INTERNAL) ? " error" : " success",
(ctx.origin == INTERNAL || ctx.origin == SERVER) ? " error" : " success",
res.status->code,
res.status->msg,
(ctx.status == 0) ? "???" : stat_str,
(status != NULL) ? status->msg : "",
host);
rev_proxy_doc = msg_pre_buf_2;
}
sprintf(msg_pre_buf_1, info->doc, res.status->code, res.status->msg,
http_msg != NULL ? http_msg->msg : "", err_msg[0] != 0 ? err_msg : "");
content_length = snprintf(msg_buf, sizeof(msg_buf), http_default_document, res.status->code,
res.status->msg, msg_pre_buf_1, info->mode, info->icon, info->color, host,
rev_proxy_doc, msg_content[0] != 0 ? msg_content : "");
}
if (content_length >= 0) {
sprintf(buf0, "%li", content_length);
http_remove_header_field(&res.hdr, "Content-Length", HTTP_REMOVE_ALL);
http_add_header_field(&res.hdr, "Content-Length", buf0);
} else if (http_get_header_field(&res.hdr, "Transfer-Encoding") == NULL) {
server_keep_alive = 0;
}
}
char *conn = http_get_header_field(&res.hdr, "Connection");
int close_proxy = conn == NULL || (strcmp(conn, "keep-alive") != 0 && strcmp(conn, "Keep-Alive") != 0);
http_remove_header_field(&res.hdr, "Connection", HTTP_REMOVE_ALL);
http_remove_header_field(&res.hdr, "Keep-Alive", HTTP_REMOVE_ALL);
if (server_keep_alive && client_keep_alive) {
http_add_header_field(&res.hdr, "Connection", "keep-alive");
sprintf(buf0, "timeout=%i, max=%i", CLIENT_TIMEOUT, REQ_PER_CONNECTION);
http_add_header_field(&res.hdr, "Keep-Alive", buf0);
} else {
http_add_header_field(&res.hdr, "Connection", "close");
}
http_send_response(client, &res);
clock_gettime(CLOCK_MONOTONIC, &end);
char *location = http_get_header_field(&res.hdr, "Location");
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
print("%s%s%03i %s%s%s (%s)%s", http_get_status_color(res.status), use_rev_proxy ? "-> " : "", res.status->code,
res.status->msg, location != NULL ? " -> " : "", location != NULL ? location : "",
format_duration(micros, buf0), CLR_STR);
// TODO access/error log file
if (strcmp(req.method, "HEAD") != 0) {
unsigned long snd_len = 0;
unsigned long len;
if (msg_buf[0] != 0) {
ret = sock_send(client, msg_buf, content_length, 0);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
}
snd_len += ret;
} else if (file != NULL) {
while (snd_len < content_length) {
len = fread(buffer, 1, CHUNK_SIZE, file);
if (snd_len + len > content_length) {
len = content_length - snd_len;
}
ret = sock_send(client, buffer, len, feof(file) ? 0 : MSG_MORE);
if (ret <= 0) {
print(ERR_STR "Unable to send: %s" CLR_STR, sock_strerror(client));
break;
}
snd_len += ret;
}
} else if (use_fastcgi) {
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
int chunked = transfer_encoding != NULL && strcmp(transfer_encoding, "chunked") == 0;
int flags = (chunked ? FASTCGI_CHUNKED : 0) | (use_fastcgi & (FASTCGI_COMPRESS | FASTCGI_COMPRESS_HOLD));
ret = fastcgi_send(&fcgi_conn, client, flags);
} else if (use_rev_proxy) {
char *transfer_encoding = http_get_header_field(&res.hdr, "Transfer-Encoding");
int chunked = transfer_encoding != NULL && strstr(transfer_encoding, "chunked") != NULL;
char *content_len = http_get_header_field(&res.hdr, "Content-Length");
unsigned long len_to_send = 0;
if (content_len != NULL) {
len_to_send = strtol(content_len, NULL, 10);
}
int flags = (chunked ? REV_PROXY_CHUNKED : 0) | (use_rev_proxy & REV_PROXY_COMPRESS);
ret = rev_proxy_send(client, len_to_send, flags);
}
if (ret < 0) {
client_keep_alive = 0;
}
}
if (close_proxy && rev_proxy.socket != 0) {
print(BLUE_STR "Closing proxy connection" CLR_STR);
sock_close(&rev_proxy);
}
clock_gettime(CLOCK_MONOTONIC, &end);
micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
print("Transfer complete: %s", format_duration(micros, buf0));
uri_free(&uri);
abort:
if (fcgi_conn.socket != 0) {
shutdown(fcgi_conn.socket, SHUT_RDWR);
close(fcgi_conn.socket);
fcgi_conn.socket = 0;
}
http_free_req(&req);
http_free_res(&res);
if (client->buf != NULL) {
free(client->buf);
client->buf = NULL;
client->buf_off = 0;
client->buf_len = 0;
}
return !client_keep_alive;
}
int client_connection_handler(sock *client, unsigned long client_num) {
struct timespec begin, end;
int ret, req_num;
char buf[1024];
clock_gettime(CLOCK_MONOTONIC, &begin);
if (dns_server[0] != 0) {
sprintf(buf, "dig @%s +short +time=1 -x %s", dns_server, client_addr_str);
FILE *dig = popen(buf, "r");
if (dig == NULL) {
print(ERR_STR "Unable to start dig: %s" CLR_STR "\n", strerror(errno));
goto dig_err;
}
unsigned long read = fread(buf, 1, sizeof(buf), dig);
ret = pclose(dig);
if (ret != 0) {
print(ERR_STR "Dig terminated with exit code %i" CLR_STR "\n", ret);
goto dig_err;
}
char *ptr = memchr(buf, '\n', read);
if (ptr == buf || ptr == NULL) {
goto dig_err;
}
ptr[-1] = 0;
client_host_str = malloc(strlen(buf) + 1);
strcpy(client_host_str, buf);
} else {
dig_err:
client_host_str = NULL;
}
client_geoip = malloc(GEOIP_MAX_SIZE);
long str_off = 0;
for (int i = 0; i < MAX_MMDB && mmdbs[i].filename != NULL; i++) {
int gai_error, mmdb_res;
MMDB_lookup_result_s result = MMDB_lookup_string(&mmdbs[i], client_addr_str, &gai_error, &mmdb_res);
if (mmdb_res != MMDB_SUCCESS) {
print(ERR_STR "Unable to lookup geoip info: %s" CLR_STR "\n", MMDB_strerror(mmdb_res));
continue;
} else if (gai_error != 0) {
print(ERR_STR "Unable to lookup geoip info" CLR_STR "\n");
continue;
} else if (!result.found_entry) {
continue;
}
MMDB_entry_data_list_s *list;
mmdb_res = MMDB_get_entry_data_list(&result.entry, &list);
if (mmdb_res != MMDB_SUCCESS) {
print(ERR_STR "Unable to lookup geoip info: %s" CLR_STR "\n", MMDB_strerror(mmdb_res));
continue;
}
long prev = str_off;
if (str_off != 0) {
str_off--;
}
mmdb_json(list, client_geoip, &str_off, GEOIP_MAX_SIZE);
if (prev != 0) {
client_geoip[prev - 1] = ',';
}
MMDB_free_entry_data_list(list);
}
char client_cc[3];
client_cc[0] = 0;
if (str_off == 0) {
free(client_geoip);
client_geoip = NULL;
} else {
char *pos = client_geoip;
pos = strstr(pos, "\"country\":");
if (pos != NULL) {
pos = strstr(pos, "\"iso_code\":");
pos += 12;
snprintf(client_cc, sizeof(client_cc), "%s", pos);
}
}
print("Connection accepted from %s %s%s%s[%s]", client_addr_str, client_host_str != NULL ? "(" : "",
client_host_str != NULL ? client_host_str : "", client_host_str != NULL ? ") " : "",
client_cc[0] != 0 ? client_cc : "N/A");
client_timeout.tv_sec = CLIENT_TIMEOUT;
client_timeout.tv_usec = 0;
if (setsockopt(client->socket, SOL_SOCKET, SO_RCVTIMEO, &client_timeout, sizeof(client_timeout)) < 0)
goto set_timeout_err;
if (setsockopt(client->socket, SOL_SOCKET, SO_SNDTIMEO, &client_timeout, sizeof(client_timeout)) < 0) {
set_timeout_err:
print(ERR_STR "Unable to set timeout for socket: %s" CLR_STR, strerror(errno));
return 1;
}
if (client->enc) {
client->ssl = SSL_new(client->ctx);
SSL_set_fd(client->ssl, client->socket);
SSL_set_accept_state(client->ssl);
ret = SSL_accept(client->ssl);
client->_last_ret = ret;
client->_errno = errno;
client->_ssl_error = ERR_get_error();
if (ret <= 0) {
print(ERR_STR "Unable to perform handshake: %s" CLR_STR, sock_strerror(client));
ret = -1;
goto close;
}
}
req_num = 0;
ret = 0;
while (ret == 0 && server_keep_alive && req_num < REQ_PER_CONNECTION) {
ret = client_request_handler(client, client_num, req_num++);
log_prefix = log_conn_prefix;
}
close:
sock_close(client);
if (rev_proxy.socket != 0) {
print(BLUE_STR "Closing proxy connection" CLR_STR);
sock_close(&rev_proxy);
}
clock_gettime(CLOCK_MONOTONIC, &end);
unsigned long micros = (end.tv_nsec - begin.tv_nsec) / 1000 + (end.tv_sec - begin.tv_sec) * 1000000;
print("Connection closed (%s)", format_duration(micros, buf));
return 0;
}
int client_handler(sock *client, unsigned long client_num, struct sockaddr_in6 *client_addr) {
int ret;
struct sockaddr_in6 *server_addr;
struct sockaddr_storage server_addr_storage;
char *color_table[] = {"\x1B[31m", "\x1B[32m", "\x1B[33m", "\x1B[34m", "\x1B[35m", "\x1B[36m"};
signal(SIGINT, client_terminate);
signal(SIGTERM, client_terminate);
client_addr_str_ptr = malloc(INET6_ADDRSTRLEN);
inet_ntop(client_addr->sin6_family, (void *) &client_addr->sin6_addr, client_addr_str_ptr, INET6_ADDRSTRLEN);
if (strncmp(client_addr_str_ptr, "::ffff:", 7) == 0) {
client_addr_str = client_addr_str_ptr + 7;
} else {
client_addr_str = client_addr_str_ptr;
}
socklen_t len = sizeof(server_addr_storage);
getsockname(client->socket, (struct sockaddr *) &server_addr_storage, &len);
server_addr = (struct sockaddr_in6 *) &server_addr_storage;
server_addr_str_ptr = malloc(INET6_ADDRSTRLEN);
inet_ntop(server_addr->sin6_family, (void *) &server_addr->sin6_addr, server_addr_str_ptr, INET6_ADDRSTRLEN);
if (strncmp(server_addr_str_ptr, "::ffff:", 7) == 0) {
server_addr_str = server_addr_str_ptr + 7;
} else {
server_addr_str = server_addr_str_ptr;
}
log_req_prefix = malloc(256);
log_client_prefix = malloc(256);
sprintf(log_client_prefix, "[%s%4i%s]%s[%*s][%5i]%s", (int) client->enc ? HTTPS_STR : HTTP_STR,
ntohs(server_addr->sin6_port), CLR_STR, color_table[client_num % 6], INET6_ADDRSTRLEN, client_addr_str,
ntohs(client_addr->sin6_port), CLR_STR);
log_conn_prefix = malloc(256);
sprintf(log_conn_prefix, "[%6i][%*s]%s ", getpid(), INET6_ADDRSTRLEN, server_addr_str, log_client_prefix);
log_prefix = log_conn_prefix;
print("Started child process with PID %i", getpid());
ret = client_connection_handler(client, client_num);
free(client_addr_str_ptr);
client_addr_str_ptr = NULL;
free(server_addr_str_ptr);
server_addr_str_ptr = NULL;
if (client_host_str != NULL) {
free(client_host_str);
client_host_str = NULL;
}
free(log_conn_prefix);
log_conn_prefix = NULL;
free(log_req_prefix);
log_req_prefix = NULL;
free(log_client_prefix);
log_client_prefix = NULL;
return ret;
}

28
src/defs.h Normal file
View File

@ -0,0 +1,28 @@
/**
* sesimos - secure, simple, modern web server
* @brief Definitions
* @file src/defs.h
* @author Lorenz Stechauner
* @date 2021-05-04
*/
#ifndef SESIMOS_DEF_H
#define SESIMOS_DEF_H
#define SERVER_VERSION "5.0-wip"
#define SERVER_STR "sesimos/" SERVER_VERSION
#define SERVER_STR_HTML "sesimos&nbsp;web&nbsp;server&nbsp;" SERVER_VERSION
#define CHUNK_SIZE 8192
#define MAX_PROXY_CNX_PER_HOST 16
#define ADDRSTRLEN 39
#ifndef DEFAULT_HOST
# define DEFAULT_HOST "www.necronda.net"
#endif
#ifndef SERVER_NAME
# define SERVER_NAME DEFAULT_HOST
#endif
#endif //SESIMOS_DEF_H

View File

@ -1,397 +0,0 @@
/**
* Necronda Web Server
* File cache implementation
* src/lib/cache.c
* Lorenz Stechauner, 2020-12-19
*/
#include "cache.h"
#include "utils.h"
#include "compress.h"
#include <stdio.h>
#include <magic.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <openssl/sha.h>
#include <malloc.h>
int cache_continue = 1;
magic_t magic;
cache_entry *cache;
int magic_init() {
magic = magic_open(MAGIC_MIME);
if (magic == NULL) {
fprintf(stderr, ERR_STR "Unable to open magic cookie: %s" CLR_STR "\n", strerror(errno));
return -1;
}
if (magic_load(magic, CACHE_MAGIC_FILE) != 0) {
fprintf(stderr, ERR_STR "Unable to load magic cookie: %s" CLR_STR "\n", magic_error(magic));
return -2;
}
return 0;
}
void cache_process_term() {
cache_continue = 0;
}
int cache_process() {
signal(SIGINT, cache_process_term);
signal(SIGTERM, cache_process_term);
int shm_id = shmget(CACHE_SHM_KEY, CACHE_ENTRIES * sizeof(cache_entry), 0);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
return -1;
}
shmdt(cache);
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
return -2;
}
cache = shm_rw;
if (mkdir("/var/necronda/", 0755) < 0 && errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda/': %s" CLR_STR "\n", strerror(errno));
return -3;
}
if (mkdir("/var/necronda/server/", 0755) < 0 && errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory '/var/necronda/server/': %s" CLR_STR "\n", strerror(errno));
return -3;
}
FILE *cache_file = fopen("/var/necronda/server/cache", "rb");
if (cache_file != NULL) {
fread(cache, sizeof(cache_entry), CACHE_ENTRIES, cache_file);
fclose(cache_file);
}
for (int i = 0; i < CACHE_ENTRIES; i++) {
cache[i].is_updating = 0;
}
FILE *file;
char *buf = malloc(CACHE_BUF_SIZE);
char *comp_buf = malloc(CACHE_BUF_SIZE);
char filename_comp_gz[256];
char filename_comp_br[256];
unsigned long read;
int compress;
SHA_CTX ctx;
unsigned char hash[SHA_DIGEST_LENGTH];
int cache_changed = 0;
int p_len_gz, p_len_br;
int ret;
while (cache_continue) {
for (int i = 0; i < CACHE_ENTRIES; i++) {
if (cache[i].filename[0] != 0 && cache[i].meta.etag[0] == 0 && !cache[i].is_updating) {
cache[i].is_updating = 1;
fprintf(stdout, "[cache] Hashing file %s\n", cache[i].filename);
SHA1_Init(&ctx);
file = fopen(cache[i].filename, "rb");
compress = mime_is_compressible(cache[i].meta.type);
compress_ctx comp_ctx;
FILE *comp_file_gz = NULL;
FILE *comp_file_br = NULL;
if (compress) {
sprintf(buf, "%.*s/.necronda-server", cache[i].webroot_len, cache[i].filename);
if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory %s: %s" CLR_STR "\n", buf, strerror(errno));
goto comp_err;
}
sprintf(buf, "%.*s/.necronda-server/cache", cache[i].webroot_len, cache[i].filename);
if (mkdir(buf, 0700) != 0 && errno != EEXIST) {
fprintf(stderr, ERR_STR "Unable to create directory %s: %s" CLR_STR "\n", buf, strerror(errno));
goto comp_err;
}
char *rel_path = cache[i].filename + cache[i].webroot_len + 1;
for (int j = 0; j < strlen(rel_path); j++) {
char ch = rel_path[j];
if (ch == '/') ch = '_';
buf[j] = ch;
}
buf[strlen(rel_path)] = 0;
p_len_gz = snprintf(filename_comp_gz, sizeof(filename_comp_gz),
"%.*s/.necronda-server/cache/%s.gz",
cache[i].webroot_len, cache[i].filename, buf);
p_len_br = snprintf(filename_comp_br, sizeof(filename_comp_br),
"%.*s/.necronda-server/cache/%s.br",
cache[i].webroot_len, cache[i].filename, buf);
if (p_len_gz < 0 || p_len_gz >= sizeof(filename_comp_gz) ||
p_len_br < 0 || p_len_br >= sizeof(filename_comp_br))
{
fprintf(stderr, ERR_STR "Unable to open cached file: "
"File name for compressed file too long" CLR_STR "\n");
goto comp_err;
}
fprintf(stdout, "[cache] Compressing file %s\n", cache[i].filename);
comp_file_gz = fopen(filename_comp_gz, "wb");
comp_file_br = fopen(filename_comp_br, "wb");
if (comp_file_gz == NULL || comp_file_br == NULL) {
fprintf(stderr, ERR_STR "Unable to open cached file: %s" CLR_STR "\n", strerror(errno));
comp_err:
compress = 0;
} else {
ret = compress_init(&comp_ctx, COMPRESS_GZ | COMPRESS_BR);
if (ret != 0) {
fprintf(stderr, ERR_STR "Unable to init compression: %s" CLR_STR "\n", strerror(errno));
compress = 0;
fclose(comp_file_gz);
fclose(comp_file_br);
}
}
}
while ((read = fread(buf, 1, CACHE_BUF_SIZE, file)) > 0) {
SHA1_Update(&ctx, buf, read);
if (compress) {
unsigned long avail_in, avail_out;
avail_in = read;
do {
avail_out = CACHE_BUF_SIZE;
compress_compress_mode(&comp_ctx, COMPRESS_GZ,buf + read - avail_in, &avail_in,
comp_buf, &avail_out, feof(file));
fwrite(comp_buf, 1, CACHE_BUF_SIZE - avail_out, comp_file_gz);
} while (avail_in != 0 || avail_out != CACHE_BUF_SIZE);
avail_in = read;
do {
avail_out = CACHE_BUF_SIZE;
compress_compress_mode(&comp_ctx, COMPRESS_BR, buf + read - avail_in, &avail_in,
comp_buf, &avail_out, feof(file));
fwrite(comp_buf, 1, CACHE_BUF_SIZE - avail_out, comp_file_br);
} while (avail_in != 0 || avail_out != CACHE_BUF_SIZE);
}
}
if (compress) {
compress_free(&comp_ctx);
fclose(comp_file_gz);
fclose(comp_file_br);
fprintf(stdout, "[cache] Finished compressing file %s\n", cache[i].filename);
strcpy(cache[i].meta.filename_comp_gz, filename_comp_gz);
strcpy(cache[i].meta.filename_comp_br, filename_comp_br);
} else {
memset(cache[i].meta.filename_comp_gz, 0, sizeof(cache[i].meta.filename_comp_gz));
memset(cache[i].meta.filename_comp_br, 0, sizeof(cache[i].meta.filename_comp_br));
}
SHA1_Final(hash, &ctx);
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
for (int j = 0; j < SHA_DIGEST_LENGTH; j++) {
sprintf(cache[i].meta.etag + j * 2, "%02x", hash[j]);
}
fclose(file);
fprintf(stdout, "[cache] Finished hashing file %s\n", cache[i].filename);
cache[i].is_updating = 0;
cache_changed = 1;
}
}
if (cache_changed) {
cache_changed = 0;
cache_file = fopen("/var/necronda/server/cache", "wb");
if (cache_file == NULL) {
fprintf(stderr, ERR_STR "Unable to open cache file: %s" CLR_STR "\n", strerror(errno));
free(buf);
free(comp_buf);
return -1;
}
fwrite(cache, sizeof(cache_entry), CACHE_ENTRIES, cache_file);
fclose(cache_file);
} else {
sleep(1);
}
}
free(buf);
free(comp_buf);
return 0;
}
int cache_init() {
if (magic_init() != 0) {
return -1;
}
int shm_id = shmget(CACHE_SHM_KEY, CACHE_ENTRIES * sizeof(cache_entry), IPC_CREAT | IPC_EXCL | 0600);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to create shared memory: %s" CLR_STR "\n", strerror(errno));
return -2;
}
void *shm = shmat(shm_id, NULL, SHM_RDONLY);
if (shm == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (ro): %s" CLR_STR "\n", strerror(errno));
return -3;
}
cache = shm;
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
fprintf(stderr, ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR "\n", strerror(errno));
return -4;
}
cache = shm_rw;
memset(cache, 0, CACHE_ENTRIES * sizeof(cache_entry));
shmdt(shm_rw);
cache = shm;
pid_t pid = fork();
if (pid == 0) {
// child
if (cache_process() == 0) {
return 0;
} else {
return -6;
}
} else if (pid > 0) {
// parent
fprintf(stderr, "Started child process with PID %i as cache-updater\n", pid);
return pid;
} else {
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
return -5;
}
}
int cache_unload() {
int shm_id = shmget(CACHE_SHM_KEY, 0, 0);
if (shm_id < 0) {
fprintf(stderr, ERR_STR "Unable to get shared memory id: %s" CLR_STR "\n", strerror(errno));
shmdt(cache);
return -1;
} else if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
fprintf(stderr, ERR_STR "Unable to configure shared memory: %s" CLR_STR "\n", strerror(errno));
shmdt(cache);
return -1;
}
shmdt(cache);
return 0;
}
int cache_update_entry(int entry_num, const char *filename, const char *webroot) {
void *cache_ro = cache;
int shm_id = shmget(CACHE_SHM_KEY, 0, 0);
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
return -1;
}
cache = shm_rw;
struct stat statbuf;
stat(filename, &statbuf);
memcpy(&cache[entry_num].meta.stat, &statbuf, sizeof(statbuf));
cache[entry_num].webroot_len = (unsigned char) strlen(webroot);
strcpy(cache[entry_num].filename, filename);
magic_setflags(magic, MAGIC_MIME_TYPE);
const char *type = magic_file(magic, filename);
char type_new[24];
sprintf(type_new, "%s", type);
if (strncmp(type, "text/", 5) == 0) {
if (strcmp(filename + strlen(filename) - 4, ".css") == 0) {
sprintf(type_new, "text/css");
} else if (strcmp(filename + strlen(filename) - 3, ".js") == 0) {
sprintf(type_new, "application/javascript");
}
}
strcpy(cache[entry_num].meta.type, type_new);
magic_setflags(magic, MAGIC_MIME_ENCODING);
strcpy(cache[entry_num].meta.charset, magic_file(magic, filename));
memset(cache[entry_num].meta.etag, 0, sizeof(cache[entry_num].meta.etag));
memset(cache[entry_num].meta.filename_comp_gz, 0, sizeof(cache[entry_num].meta.filename_comp_gz));
memset(cache[entry_num].meta.filename_comp_br, 0, sizeof(cache[entry_num].meta.filename_comp_br));
cache[entry_num].is_updating = 0;
shmdt(shm_rw);
cache = cache_ro;
return 0;
}
int cache_filename_comp_invalid(const char *filename) {
void *cache_ro = cache;
int shm_id = shmget(CACHE_SHM_KEY, 0, 0);
void *shm_rw = shmat(shm_id, NULL, 0);
if (shm_rw == (void *) -1) {
print(ERR_STR "Unable to attach shared memory (rw): %s" CLR_STR, strerror(errno));
return -1;
}
cache = shm_rw;
int i;
for (i = 0; i < CACHE_ENTRIES; i++) {
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(filename) &&
strcmp(cache[i].filename, filename) == 0) {
if (cache[i].is_updating) {
return 0;
} else {
break;
}
}
}
memset(cache[i].meta.etag, 0, sizeof(cache[i].meta.etag));
memset(cache[i].meta.filename_comp_gz, 0, sizeof(cache[i].meta.filename_comp_gz));
memset(cache[i].meta.filename_comp_br, 0, sizeof(cache[i].meta.filename_comp_br));
cache[i].is_updating = 0;
shmdt(shm_rw);
cache = cache_ro;
return 0;
}
int uri_cache_init(http_uri *uri) {
if (uri->filename == NULL) {
return 0;
}
int i;
for (i = 0; i < CACHE_ENTRIES; i++) {
if (cache[i].filename[0] != 0 && strlen(cache[i].filename) == strlen(uri->filename) &&
strcmp(cache[i].filename, uri->filename) == 0) {
uri->meta = &cache[i].meta;
if (cache[i].is_updating) {
return 0;
} else {
break;
}
}
}
if (uri->meta == NULL) {
for (i = 0; i < CACHE_ENTRIES; i++) {
if (cache[i].filename[0] == 0) {
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
return -1;
}
uri->meta = &cache[i].meta;
break;
}
}
} else {
struct stat statbuf;
stat(uri->filename, &statbuf);
if (memcmp(&uri->meta->stat.st_mtime, &statbuf.st_mtime, sizeof(statbuf.st_mtime)) != 0) {
if (cache_update_entry(i, uri->filename, uri->webroot) != 0) {
return -1;
}
}
}
return 0;
}

View File

@ -1,49 +0,0 @@
/**
* Necronda Web Server
* File cache implementation (header file)
* src/lib/cache.h
* Lorenz Stechauner, 2020-12-19
*/
#ifndef NECRONDA_SERVER_CACHE_H
#define NECRONDA_SERVER_CACHE_H
#include "uri.h"
#define CACHE_SHM_KEY 255641
#define CACHE_ENTRIES 1024
#define CACHE_BUF_SIZE 16384
#ifndef CACHE_MAGIC_FILE
# define CACHE_MAGIC_FILE "/usr/share/file/misc/magic.mgc"
#endif
typedef struct {
char filename[256];
unsigned char webroot_len;
unsigned char is_updating:1;
meta_data meta;
} cache_entry;
extern cache_entry *cache;
extern int cache_continue;
int magic_init();
void cache_process_term();
int cache_process();
int cache_init();
int cache_unload();
int cache_update_entry(int entry_num, const char *filename, const char *webroot);
int cache_filename_comp_invalid(const char *filename);
int uri_cache_init(http_uri *uri);
#endif //NECRONDA_SERVER_CACHE_H

View File

@ -1,35 +1,37 @@
/**
* Necronda Web Server
* Compression interface
* src/lib/compress.c
* Lorenz Stechauner, 2021-05-05
* sesimos - secure, simple, modern web server
* @brief Compression interface
* @file src/lib/compress.c
* @author Lorenz Stechauner
* @date 2021-05-05
*/
#include "compress.h"
#include <malloc.h>
#include <errno.h>
int compress_init(compress_ctx *ctx, int mode) {
ctx->gzip = NULL;
ctx->brotli = NULL;
ctx->mode = 0;
int ret;
if (mode & COMPRESS_GZ) {
ctx->mode |= COMPRESS_GZ;
ctx->gzip = malloc(sizeof(z_stream));
ctx->gzip->zalloc = Z_NULL;
ctx->gzip->zfree = Z_NULL;
ctx->gzip->opaque = Z_NULL;
ret = deflateInit2(ctx->gzip, COMPRESS_LEVEL_GZIP, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY);
if (ret != Z_OK) return -1;
ctx->gzip.zalloc = Z_NULL;
ctx->gzip.zfree = Z_NULL;
ctx->gzip.opaque = Z_NULL;
ctx->gzip.data_type = (mode & COMPRESS_UTF8) ? Z_TEXT : Z_UNKNOWN;
if (deflateInit2(&ctx->gzip, COMPRESS_LEVEL_GZIP, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY) != 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);
if ((ctx->brotli = BrotliEncoderCreateInstance(NULL, NULL, NULL)) == NULL)
return -1;
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_MODE, (mode & COMPRESS_UTF8) ? BROTLI_MODE_TEXT : ((mode & COMPRESS_WOFF) ? BROTLI_MODE_FONT : BROTLI_MODE_GENERIC));
BrotliEncoderSetParameter(ctx->brotli, BROTLI_PARAM_QUALITY, COMPRESS_LEVEL_BROTLI);
}
return 0;
}
@ -41,39 +43,46 @@ int compress_compress(compress_ctx *ctx, const char *in, unsigned long *in_len,
return compress_compress_mode(ctx, ctx->mode, in, in_len, out, out_len, finish);
}
static int compress_brotli(BrotliEncoderState *ctx, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish) {
int ret = BrotliEncoderCompressStream(
ctx, finish ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
in_len, (const unsigned char**) &in, out_len, (unsigned char **) &out, NULL);
return (ret == BROTLI_TRUE) ? 0 : -1;
}
static int compress_gzip(z_stream *gzip, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish) {
gzip->next_in = (unsigned char*) in;
gzip->avail_in = *in_len;
gzip->next_out = (unsigned char*) out;
gzip->avail_out = *out_len;
int ret = deflate(gzip, finish ? Z_FINISH : Z_NO_FLUSH);
*in_len = gzip->avail_in;
*out_len = gzip->avail_out;
return ret;
}
int compress_compress_mode(compress_ctx *ctx, int mode, const char *in, unsigned long *in_len, char *out, unsigned long *out_len, int finish) {
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;
return compress_gzip(&ctx->gzip, in, in_len, out, out_len, finish);
} 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;
return compress_brotli(ctx->brotli, in, in_len, out, out_len, finish);
} else {
errno = EINVAL;
return -2;
return -1;
}
}
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;
}
if (ctx->mode & COMPRESS_GZ) {
deflateEnd(&ctx->gzip);
}
ctx->mode = 0;
return 0;
}

View File

@ -1,12 +1,13 @@
/**
* Necronda Web Server
* Compression interface (header file)
* src/lib/compress.h
* Lorenz Stechauner, 2021-05-05
* sesimos - secure, simple, modern web server
* @brief Compression interface (header file)
* @file src/lib/compress.h
* @author Lorenz Stechauner
* @date 2021-05-05
*/
#ifndef NECRONDA_SERVER_COMPRESS_H
#define NECRONDA_SERVER_COMPRESS_H
#ifndef SESIMOS_COMPRESS_H
#define SESIMOS_COMPRESS_H
#include <zlib.h>
#include <brotli/encode.h>
@ -17,21 +18,21 @@
#define COMPRESS_GZ 1
#define COMPRESS_BR 2
#define COMPRESS 3
#define COMPRESS_UTF8 4
#define COMPRESS_WOFF 8
typedef struct {
int mode;
z_stream *gzip;
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(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_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
#endif //SESIMOS_COMPRESS_H

View File

@ -1,138 +1,71 @@
/**
* Necronda Web Server
* Configuration file loader
* src/lib/config.c
* Lorenz Stechauner, 2021-01-05
* sesimos - secure, simple, modern web server
* @brief Configuration file loader
* @file src/lib/config.c
* @author Lorenz Stechauner
* @date 2021-01-05
*/
#include "../logger.h"
#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];
config_t config;
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;
static int config_parse_line(char *line, char *section, int *i, int *j) {
int mode = 0;
char section = 0;
char *ptr = NULL;
char *source, *target;
while ((ptr = strsep(&conf, "\r\n")) != NULL) {
line++;
char *comment = strchr(ptr, '#');
char *ptr = line;
char *comment = strpbrk(ptr, "#\r\n");
if (comment != NULL) comment[0] = 0;
len = strlen(ptr);
char *end_ptr = ptr + len - 1;
unsigned long len = strlen(ptr);
char *end_ptr = (len > 0) ? ptr + len - 1 : ptr;
while (end_ptr[0] == ' ' || end_ptr[0] == '\t') {
end_ptr[0] = 0;
end_ptr--;
}
len = strlen(ptr);
if (len == 0) continue;
if (len == 0) return 0;
if (ptr[0] == '[') {
if (ptr[len - 1] != ']') goto err;
if (ptr[len - 1] != ']') return -1;
ptr++;
int l = 0;
if (strncmp(ptr, "host", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
ptr += 4;
while (ptr[0] == ' ' || ptr[0] == '\t' || ptr[0] == ']') ptr++;
while (ptr[l] != ' ' && ptr[l] != '\t' && ptr[l] != ']') l++;
if (l == 0) goto err;
snprintf(tmp_config->hosts[i].name, sizeof(tmp_config->hosts[i].name), "%.*s", l, ptr);
i++;
section = 'h';
if (l == 0) return -1;
snprintf(config.hosts[*i].name, sizeof(config.hosts[*i].name), "%.*s", l, ptr);
++(*i);
*section = 'h';
} else if (strncmp(ptr, "cert", 4) == 0 && (ptr[4] == ' ' || ptr[4] == '\t')) {
ptr += 4;
while (ptr[0] == ' ' || ptr[0] == '\t' || ptr[0] == ']') ptr++;
while (ptr[l] != ' ' && ptr[l] != '\t' && ptr[l] != ']') l++;
if (l == 0) goto err;
snprintf(tmp_config->certs[j].name, sizeof(tmp_config->certs[j].name), "%.*s", l, ptr);
j++;
section = 'c';
if (l == 0) return -1;
snprintf(config.certs[*j].name, sizeof(config.certs[*j].name), "%.*s", l, ptr);
++(*j);
*section = 'c';
} else {
goto err;
return -1;
}
continue;
} else if (section == 0) {
return 0;
} 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;
target = config.geoip_dir;
} else {
goto err;
return -1;
}
} else if (section == 'c') {
cert_config *cc = &tmp_config->certs[j - 1];
} else if (*section == 'c') {
cert_config_t *cc = &config.certs[*j - 1];
if (len > 12 && strncmp(ptr, "certificate", 11) == 0 && (ptr[11] == ' ' || ptr[11] == '\t')) {
source = ptr + 11;
target = cc->full_chain;
@ -140,15 +73,15 @@ int config_load(const char *filename) {
source = ptr + 11;
target = cc->priv_key;
} else {
goto err;
return -1;
}
} else if (section == 'h') {
host_config *hc = &tmp_config->hosts[i - 1];
} else if (*section == 'h') {
host_config_t *hc = &config.hosts[*i - 1];
if (len > 8 && strncmp(ptr, "webroot", 7) == 0 && (ptr[7] == ' ' || ptr[7] == '\t')) {
source = ptr + 7;
target = hc->local.webroot;
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
goto err;
return -1;
} else {
hc->type = CONFIG_TYPE_LOCAL;
}
@ -160,15 +93,15 @@ int config_load(const char *filename) {
target = NULL;
mode = 1;
if (hc->type != 0 && hc->type != CONFIG_TYPE_LOCAL) {
goto err;
return -1;
} else {
hc->type = CONFIG_TYPE_LOCAL;
}
} else if (len > 9 && strncmp(ptr, "hostname", 8) == 0 && (ptr[8] == ' ' || ptr[8] == '\t')) {
source = ptr + 8;
target = hc->rev_proxy.hostname;
target = hc->proxy.hostname;
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
return -1;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
}
@ -177,98 +110,119 @@ int config_load(const char *filename) {
target = NULL;
mode = 2;
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
return -1;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
}
} else if (strcmp(ptr, "http") == 0) {
} else if (streq(ptr, "http")) {
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
return -1;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
hc->rev_proxy.enc = 0;
hc->proxy.enc = 0;
}
continue;
} else if (strcmp(ptr, "https") == 0) {
return 0;
} else if (streq(ptr, "https")) {
if (hc->type != 0 && hc->type != CONFIG_TYPE_REVERSE_PROXY) {
goto err;
return -1;
} else {
hc->type = CONFIG_TYPE_REVERSE_PROXY;
hc->rev_proxy.enc = 1;
hc->proxy.enc = 1;
}
continue;
return 0;
} else {
goto err;
return -1;
}
} else {
goto err;
return -1;
}
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 (strlen(source) == 0) return -1;
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;
if (streq(source, "forbidden")) {
config.hosts[*i - 1].local.dir_mode = URI_DIR_MODE_FORBIDDEN;
} else if (streq(source, "info")) {
config.hosts[*i - 1].local.dir_mode = URI_DIR_MODE_INFO;
} else if (streq(source, "list")) {
config.hosts[*i - 1].local.dir_mode = URI_DIR_MODE_LIST;
} else {
goto err;
return -1;
}
} else if (mode == 2) {
tmp_config->hosts[i - 1].rev_proxy.port = (unsigned short) strtoul(source, NULL, 10);
config.hosts[*i - 1].proxy.port = (unsigned short) strtoul(source, NULL, 10);
}
return 0;
}
int config_load(const char *filename) {
FILE *file = fopen(filename, "r");
if (file == NULL) {
critical("Unable to open config file");
return -1;
}
memset(&config, 0, sizeof(config));
int i = 0, j = 0;
char section = 0;
char *line = NULL;
size_t line_len = 0;
for (int line_num = 1; getline(&line, &line_len, file) != -1; line_num++) {
if (config_parse_line(line, &section, &i, &j) != 0) {
critical("Unable to parse config file (line %i)", line_num);
free(line);
fclose(file);
return -2;
}
}
free(line);
fclose(file);
for (int k = 0; k < i; k++) {
host_config *hc = &tmp_config->hosts[k];
host_config_t *hc = &config.hosts[k];
if (hc->type == CONFIG_TYPE_LOCAL) {
char *webroot = tmp_config->hosts[k].local.webroot;
if (webroot[strlen(webroot) - 1] == '/') {
webroot[strlen(webroot) - 1] = 0;
char *webroot = config.hosts[k].local.webroot;
while (webroot[strlen(webroot) - 1] == '/') webroot[strlen(webroot) - 1] = 0;
}
if (hc->cert_name[0] == 0) {
critical("Unable to parse config file: host config (%s) does not contain a certificate", hc->name);
return -2;
}
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) {
if (streq(config.certs[m].name, hc->cert_name)) {
hc->cert = m;
found = 1;
break;
}
}
if (!found) {
err2:
free(tmp_config);
fprintf(stderr, ERR_STR "Unable to parse config file" CLR_STR "\n");
critical("Unable to parse config file: certificate (%s) for host config (%s) not found", hc->cert_name, hc->name);
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;
}
host_config_t *get_host_config(const char *host) {
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
host_config_t *hc = &config.hosts[i];
if (hc->type == CONFIG_TYPE_UNSET) break;
if (streq(hc->name, host)) return hc;
if (hc->name[0] == '*' && hc->name[1] == '.') {
const char *pos = strstr(host, hc->name + 1);
if (pos != NULL && strlen(pos) == strlen(hc->name + 1)) return hc;
}
}
return NULL;
}

View File

@ -1,16 +1,17 @@
/**
* Necronda Web Server
* Configuration file loader (header file)
* src/lib/config.h
* Lorenz Stechauner, 2021-01-05
* sesimos - secure, simple, modern web server
* @brief Configuration file loader (header file)
* @file src/lib/config.h
* @author Lorenz Stechauner
* @date 2021-01-05
*/
#ifndef NECRONDA_SERVER_CONFIG_H
#define NECRONDA_SERVER_CONFIG_H
#ifndef SESIMOS_CONFIG_H
#define SESIMOS_CONFIG_H
#include "uri.h"
#include "../cache_handler.h"
#define CONFIG_SHM_KEY 255642
#define CONFIG_MAX_HOST_CONFIG 64
#define CONFIG_MAX_CERT_CONFIG 64
@ -19,7 +20,7 @@
#define CONFIG_TYPE_REVERSE_PROXY 2
#ifndef DEFAULT_CONFIG_FILE
# define DEFAULT_CONFIG_FILE "/etc/necronda/server.conf"
# define DEFAULT_CONFIG_FILE "/etc/sesimos/server.conf"
#endif
@ -28,37 +29,36 @@ typedef struct {
char name[256];
char cert_name[256];
int cert;
cache_t *cache;
union {
struct {
char hostname[256];
unsigned short port;
unsigned char enc:1;
} rev_proxy;
} proxy;
struct {
char webroot[256];
unsigned char dir_mode:2;
} local;
};
} host_config;
} host_config_t;
typedef struct {
char name[256];
char full_chain[256];
char priv_key[256];
} cert_config;
} cert_config_t;
typedef struct {
host_config hosts[CONFIG_MAX_HOST_CONFIG];
cert_config certs[CONFIG_MAX_CERT_CONFIG];
} t_config;
host_config_t hosts[CONFIG_MAX_HOST_CONFIG];
cert_config_t certs[CONFIG_MAX_CERT_CONFIG];
char geoip_dir[256];
} config_t;
extern t_config *config;
extern char geoip_dir[256], dns_server[256];
int config_init();
extern config_t config;
int config_load(const char *filename);
int config_unload();
host_config_t *get_host_config(const char *host);
#endif //NECRONDA_SERVER_CONFIG_H
#endif //SESIMOS_CONFIG_H

87
src/lib/error.c Normal file
View File

@ -0,0 +1,87 @@
/**
* sesimos - secure, simple, modern web server
* @brief Error interface
* @file src/lib/error.c
* @author Lorenz Stechauner
* @date 2023-01-08
*/
#include "error.h"
#include "../logger.h"
#include <errno.h>
#include <string.h>
#include <netdb.h>
extern const char *sock_error_str(unsigned long err);
extern const char *http_error_str(int err);
extern const char *MMDB_strerror(int err);
extern const char *ERR_reason_error_string(unsigned long err);
static int error_compress(unsigned long err) {
int comp = ((int) err & 0xFFFF) | (((int) err >> 8) & 0xFF0000);
if (err & 0xFF0000) warning("Lossy error code compression! (%08lX -> %08X)", err, comp);
return comp;
}
static unsigned long error_decompress(int err) {
return (err & 0xFFFF) | ((err << 8) & 0xFF000000);
}
const char *error_str(int err_no, char *buf, int buf_len) {
buf[0] = 0;
int e = err_no & 0x00FFFFFF;
switch (err_no >> 24) {
case 0x00: return strerror_r(e, buf, buf_len);
case 0x01: return sock_error_str(error_decompress(e));
case 0x02: return ERR_reason_error_string(error_decompress(e));
case 0x03: return MMDB_strerror(e);
case 0x04: return http_error_str(e);
case 0x05: return gai_strerror(e);
}
return buf;
}
void error_ssl(unsigned long err) {
errno = 0x01000000 | error_compress(err);
}
void error_ssl_err(unsigned long err) {
errno = 0x02000000 | error_compress(err);
}
void error_mmdb(int err) {
errno = 0x03000000 | err;
}
void error_http(int err) {
errno = 0x04000000 | err;
}
void error_gai(int err) {
errno = 0x05000000 | err;
}
static int error_get(unsigned char prefix) {
return (errno >> 24 != prefix) ? 0 : errno & 0x00FFFFFF;
}
int error_get_sys() {
return error_get(0x00);
}
int error_get_ssl() {
return error_get(0x01);
}
int error_get_ssl_err() {
return error_get(0x02);
}
int error_get_mmdb() {
return error_get(0x03);
}
int error_get_http() {
return error_get(0x04);
}

32
src/lib/error.h Normal file
View File

@ -0,0 +1,32 @@
/**
* sesimos - secure, simple, modern web server
* @brief Error interface (header fie)
* @file src/lib/error.h
* @author Lorenz Stechauner
* @date 2023-01-08
*/
#ifndef SESIMOS_ERROR_H
#define SESIMOS_ERROR_H
const char *error_str(int err_no, char *buf, int buf_len);
void error_ssl(unsigned long err);
void error_ssl_err(unsigned long err);
void error_mmdb(int err);
void error_http(int err);
void error_gai(int err);
int error_get_sys();
int error_get_ssl();
int error_get_mmdb();
int error_get_http();
#endif //SESIMOS_ERROR_H

View File

@ -1,43 +1,42 @@
/**
* Necronda Web Server
* FastCGI interface implementation
* src/lib/fastcgi.c
* Lorenz Stechauner, 2020-12-26
* sesimos - secure, simple, modern web server
* @brief FastCGI interface implementation
* @file src/lib/fastcgi.c
* @author Lorenz Stechauner
* @date 2020-12-26
*/
#include "../defs.h"
#include "fastcgi.h"
#include "utils.h"
#include "compress.h"
#include "../necronda-server.h"
#include "../logger.h"
#include "list.h"
#include "../workers.h"
#include <sys/un.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
char *fastcgi_add_param(char *buf, const char *key, const char *value) {
char *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);
*((int *) ptr) = htonl(0x80000000 | key_len);
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);
*((int *) ptr) = htonl(0x80000000 | val_len);
ptr += 4;
}
@ -49,63 +48,70 @@ char *fastcgi_add_param(char *buf, const char *key, const char *value) {
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;
}
int fastcgi_send_data(fastcgi_cnx_t *cnx, unsigned char type, unsigned short len, void *data) {
// build header
FCGI_Header header = {
.version = FCGI_VERSION_1,
.requestIdB1 = req_id >> 8,
.requestIdB0 = req_id & 0xFF,
.type = type,
.requestId = htons(cnx->req_id),
.contentLength = htons(len),
.paddingLength = 0,
.reserved = 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;
// send FastCGI header with MSG_MORE flag
if (sock_send_x(&cnx->socket, &header, sizeof(header), (len != 0) ? MSG_MORE : 0) == -1) {
error("Unable to send to FastCGI socket");
return -1;
}
char param_buf[4096];
char buf0[256];
char *param_ptr = param_buf + sizeof(header);
// send data (if available)
if (sock_send_x(&cnx->socket, data, len, 0) == -1) {
error("Unable to send to FastCGI socket");
return -1;
}
// return bytes sent totally
return len + (int) sizeof(header);
}
int fastcgi_init(fastcgi_cnx_t *conn, int mode, unsigned int req_num, const sock *client, const http_req *req, const http_uri *uri) {
conn->mode = mode;
conn->header_sent = 0;
conn->req_id = (req_num + 1) & 0xFFFF;
conn->webroot = uri->webroot;
conn->err = NULL;
conn->fd_err_bytes = 0;
conn->fd_out = -1;
conn->fd_err = -1;
sock_init(&conn->out, -1, SOCK_PIPE);
conn->socket.enc = 0;
if ((conn->socket.socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
error("Unable to create unix socket");
return -1;
}
struct sockaddr_un sock_addr = { AF_UNIX };
if (conn->mode == FASTCGI_BACKEND_PHP) {
strcpy(sock_addr.sun_path, PHP_FPM_SOCKET);
}
if (connect(conn->socket.socket, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) != 0) {
error("Unable to connect to FastCGI (unix) socket");
return -1;
}
FCGI_BeginRequestBody begin = {
.role = htons(FCGI_RESPONDER),
.flags = 0,
.reserved = {0},
};
if (fastcgi_send_data(conn, FCGI_BEGIN_REQUEST, sizeof(begin), &begin) == -1)
return -1;
char param_buf[4096], buf0[256], *param_ptr = param_buf;
param_ptr = fastcgi_add_param(param_ptr, "REDIRECT_STATUS", "CGI");
param_ptr = fastcgi_add_param(param_ptr, "DOCUMENT_ROOT", uri->webroot);
param_ptr = fastcgi_add_param(param_ptr, "GATEWAY_INTERFACE", "CGI/1.1");
@ -121,16 +127,16 @@ int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned
socklen_t len = sizeof(addr_storage);
getsockname(client->socket, (struct sockaddr *) &addr_storage, &len);
addr = (struct sockaddr_in6 *) &addr_storage;
sprintf(buf0, "%i", addr->sin6_port);
sprintf(buf0, "%i", ntohs(addr->sin6_port));
param_ptr = fastcgi_add_param(param_ptr, "SERVER_PORT", buf0);
len = sizeof(addr_storage);
getpeername(client->socket, (struct sockaddr *) &addr_storage, &len);
addr = (struct sockaddr_in6 *) &addr_storage;
sprintf(buf0, "%i", addr->sin6_port);
sprintf(buf0, "%i", ntohs(addr->sin6_port));
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_PORT", buf0);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_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_ADDR", conn->r_addr);
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_HOST", conn->r_host != NULL ? conn->r_host : conn->r_addr);
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_IDENT", "");
//param_ptr = fastcgi_add_param(param_ptr, "REMOTE_USER", "");
@ -149,19 +155,21 @@ int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned
param_ptr = fastcgi_add_param(param_ptr, "PATH_INFO", buf0);
//param_ptr = fastcgi_add_param(param_ptr, "AUTH_TYPE", "");
char *content_length = http_get_header_field(&req->hdr, "Content-Length");
const char *content_length = http_get_header_field(&req->hdr, "Content-Length");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_LENGTH", content_length != NULL ? content_length : "");
char *content_type = http_get_header_field(&req->hdr, "Content-Type");
const char *content_type = http_get_header_field(&req->hdr, "Content-Type");
param_ptr = fastcgi_add_param(param_ptr, "CONTENT_TYPE", content_type != NULL ? content_type : "");
if (client_geoip != NULL) {
param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", client_geoip);
}
//if (conn->ctx->geoip[0] != 0) {
// param_ptr = fastcgi_add_param(param_ptr, "REMOTE_INFO", conn->ctx->geoip);
//}
for (int i = 0; i < req->hdr.field_num; i++) {
for (int i = 0; i < list_size(req->hdr.fields); i++) {
const http_field *f = &req->hdr.fields[i];
const char *name = http_field_get_name(f);
char *ptr = buf0;
ptr += sprintf(ptr, "HTTP_");
for (int j = 0; j < strlen(req->hdr.fields[i][0]); j++, ptr++) {
char ch = req->hdr.fields[i][0][j];
for (int j = 0; j < strlen(name); j++, ptr++) {
char ch = name[j];
if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) {
ch = ch;
} else if (ch >= 'a' && ch <= 'z') {
@ -172,218 +180,233 @@ int fastcgi_init(fastcgi_conn *conn, int mode, unsigned int client_num, unsigned
ptr[0] = ch;
ptr[1] = 0;
}
param_ptr = fastcgi_add_param(param_ptr, buf0, req->hdr.fields[i][1]);
param_ptr = fastcgi_add_param(param_ptr, buf0, http_field_get_value(f));
}
unsigned short param_len = param_ptr - param_buf - sizeof(header);
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;
}
if (fastcgi_send_data(conn, FCGI_PARAMS, param_ptr - param_buf, param_buf) == -1)
return -1;
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;
}
if (fastcgi_send_data(conn, FCGI_PARAMS, 0, NULL) == -1)
return -1;
int pipes[2][2];
if (pipe(pipes[0]) == -1 || pipe(pipes[1]) == -1)
return -1;
conn->fd_out = pipes[1][1];
conn->out.socket = pipes[1][0];
sock_set_timeout(&conn->out, FASTCGI_TIMEOUT);
conn->fd_err = pipes[0][1];
conn->err = fdopen(pipes[0][0], "r");
return 0;
}
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
};
int fastcgi_close_cnx(fastcgi_cnx_t *cnx) {
int e = errno;
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;
}
if (cnx->err) fclose(cnx->err);
cnx->err = NULL;
sock_close(&cnx->socket);
sock_close(&cnx->out);
if (cnx->fd_err != -1) close(cnx->fd_err);
if (cnx->fd_out != -1) close(cnx->fd_out);
cnx->fd_err = -1;
cnx->fd_out = -1;
errno = e;
return 0;
}
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 fastcgi_close_stdin(fastcgi_cnx_t *cnx) {
return (fastcgi_send_data(cnx, FCGI_STDIN, 0, NULL) == -1) ? -1 : 0;
}
int fastcgi_php_error(fastcgi_cnx_t *cnx, char *err_msg) {
char *line = NULL, *line_ptr = NULL;
size_t line_len = 0;
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);
log_lvl_t msg_type = LOG_INFO;
// FIXME php fastcgi sends multiple calls with '; ' as delimiter
for (long ret; cnx->fd_err_bytes > 0 && (ret = getline(&line, &line_len, cnx->err)) != -1; cnx->fd_err_bytes -= ret) {
if (ret > 0) line[ret - 1] = 0;
line_ptr = line;
if (strstarts(line_ptr, "PHP message: ")) {
line_ptr += 13;
if (strstarts(line_ptr, "PHP Warning: ")) {
msg_type = LOG_WARNING;
} else if (strstarts(line_ptr, "PHP Fatal error: ")) {
msg_type = LOG_ERROR;
} else if (strstarts(line_ptr, "PHP Parse error: ")) {
msg_type = LOG_ERROR;
} else if (strstarts(line_ptr, "PHP Notice: ")) {
msg_type = LOG_NOTICE;
}
if (len == 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;
}
logmsgf(msg_type, "%s", line_ptr);
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);
if (err_msg && msg_type <= LOG_ERROR && line_ptr != line) {
strcpy_rem_webroot(err_msg, line_ptr, cnx->webroot);
err = 1;
}
if (ptr3 == NULL) {
break;
}
ptr2 = ptr3 + 1;
}
next:
if (ptr1 == NULL) {
break;
}
ptr0 = ptr1 + 13;
}
free(msg_str);
// cleanup
free(line);
return err;
}
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
int fastcgi_recv_frame(fastcgi_cnx_t *cnx) {
FCGI_Header header;
char *content;
unsigned short content_len, req_id;
long ret;
int err = 0;
unsigned short req_id, content_len;
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 (sock_recv_x(&cnx->socket, &header, sizeof(header), 0) == -1)
return -1;
req_id = ntohs(header.requestId);
content_len = ntohs(header.contentLength);
if (req_id != cnx->req_id) {
warning("Invalid request id from FastCGI socket");
char content[256 * 256];
sock_recv_x(&cnx->socket, content, content_len + header.paddingLength, 0);
return -1;
}
if (req_id != conn->req_id) {
if (header.type == FCGI_STDOUT || header.type == FCGI_STDERR) {
char buf[256];
if (header.type == FCGI_STDOUT && !cnx->header_sent) {
char content[256 * 256];
if (sock_recv_x(&cnx->socket, content, content_len + header.paddingLength, 0) == -1)
return -1;
char *h_pos = strstr(content, "\r\n\r\n");
long header_len = h_pos - content + 4;
if (h_pos != NULL) {
uint64_t len;
len = header_len;
if (write(cnx->fd_out, &len, sizeof(len)) == -1)
return -1;
if (write(cnx->fd_out, content, len) == -1)
return -1;
cnx->header_sent = 1;
len = content_len - header_len;
if (len > 0) {
if (write(cnx->fd_out, &len, sizeof(len)) == -1)
return -1;
if (write(cnx->fd_out, content + header_len, len) == -1)
return -1;
}
return header.type;
}
} else if (header.type == FCGI_STDOUT) {
uint64_t len = content_len;
if (write(cnx->fd_out, &len, sizeof(len)) == -1)
return -1;
}
int fd = cnx->fd_out;
if (header.type == FCGI_STDERR) {
fd = cnx->fd_err;
cnx->fd_err_bytes += content_len + 1;
}
for (long ret, sent = 0; sent < content_len; sent += ret) {
// FIXME if pipe is full thread gets stuck
if ((ret = splice(cnx->socket.socket, 0, fd, 0, content_len - sent, 0)) == -1) {
if (errno == EINTR) {
errno = 0, ret = 0;
continue;
} else {
return -1;
}
}
}
if (header.type == FCGI_STDERR) write(fd, "\n", 1);
if (sock_recv_x(&cnx->socket, buf, header.paddingLength, 0) == -1)
return -1;
return header.type;
}
char content[256 * 256];
if (sock_recv_x(&cnx->socket, content, content_len + header.paddingLength, 0) == -1)
return -1;
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;
cnx->app_status = ntohl(body->appStatus);
if (body->protocolStatus != FCGI_REQUEST_COMPLETE)
error("FastCGI protocol error: %i", body->protocolStatus);
} else {
print(ERR_STR "Unknown FastCGI type: %i" CLR_STR, header.type);
warning("Unknown FastCGI type: %i", header.type);
return -1;
}
free(content);
return header.type;
}
if (err) {
int fastcgi_header(fastcgi_cnx_t *cnx, http_res *res, char *err_msg) {
long ret, len;
char content[CLIENT_MAX_HEADER_SIZE];
if ((len = sock_recv_chunk_header(&cnx->out)) == -1) {
res->status = http_get_status(500);
return 2;
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
error("Unable to receive from FastCGI socket (1)");
return -1;
}
conn->out_buf = content;
conn->out_len = content_len;
conn->out_off = (unsigned short) (strstr(content, "\r\n\r\n") - content + 4);
if ((ret = sock_recv_x(&cnx->out, content, len, 0)) == -1) {
res->status = http_get_status(500);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
error("Unable to receive from FastCGI socket (2)");
return -1;
}
content[ret] = 0;
char *buf = content;
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;
char *h_pos = strstr(content, "\r\n\r\n");
if (h_pos == NULL) {
error("Unable to parse header: End of header not found");
return -1;
}
long header_len = h_pos - content + 4;
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;
error("Unable to parse header: Header contains illegal characters");
return -1;
}
}
if (fastcgi_php_error(cnx, err_msg) != 0) {
res->status = http_get_status(500);
return 1;
}
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;
error("Unable to parse header: Invalid header format");
return -1;
}
ret = http_parse_header_field(&res->hdr, ptr, pos0);
ret = http_parse_header_field(&res->hdr, ptr, pos0, 0);
if (ret != 0) return (int) ret;
if (pos0[2] == '\r' && pos0[3] == '\n') {
return 0;
}
@ -393,230 +416,37 @@ int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg) {
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;
}
int fastcgi_dump(fastcgi_cnx_t *cnx, char *buf, long len) {
return sock_recv_x(&cnx->socket, buf, len, 0) == -1 ? -1 : 0;
}
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;
}
int fastcgi_receive(fastcgi_cnx_t *cnx, sock *client, unsigned long len) {
char buf[CHUNK_SIZE];
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));
for (long to_send = (long) len, ret; to_send > 0; to_send -= ret) {
if ((ret = sock_recv(client, buf, (to_send > sizeof(buf)) ? sizeof(buf) : to_send, 0)) <= 0) {
error("Unable to receive");
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);
if (fastcgi_send_data(cnx, FCGI_STDIN, ret, buf) == -1)
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);
int fastcgi_receive_chunked(fastcgi_cnx_t *cnx, sock *client) {
for (long ret;;) {
if ((ret = sock_recv_chunk_header(client)) < 0) {
return (int) ret;
} else if (ret == 0) {
break;
}
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;
if ((ret = fastcgi_receive(cnx, client, ret)) < 0)
return (int) ret;
}
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;
}

View File

@ -1,57 +1,63 @@
/**
* Necronda Web Server
* FastCGI interface implementation (header file)
* src/lib/fastcgi.h
* Lorenz Stechauner, 2020-12-26
* sesimos - secure, simple, modern web server
* @brief FastCGI interface implementation (header file)
* @file src/lib/fastcgi.h
* @author Lorenz Stechauner
* @date 2020-12-26
*/
#ifndef NECRONDA_SERVER_FASTCGI_H
#define NECRONDA_SERVER_FASTCGI_H
#ifndef SESIMOS_FASTCGI_H
#define SESIMOS_FASTCGI_H
#include "include/fastcgi.h"
#include "http.h"
#include "uri.h"
#define FASTCGI_CHUNKED 1
#define FASTCGI_COMPRESS_GZ 2
#define FASTCGI_COMPRESS_BR 4
#define FASTCGI_COMPRESS 6
#define FASTCGI_COMPRESS_HOLD 8
#define FASTCGI_SOCKET_TIMEOUT 1
#define FASTCGI_TIMEOUT 3600
#define FASTCGI_PHP 1
#define FASTCGI_NECRONDA 2
#define FASTCGI_BACKEND_PHP 1
#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 char header_sent:1;
sock socket, out;
int fd_err, fd_out;
long fd_err_bytes;
FILE *err;
unsigned short req_id;
char *out_buf;
int app_status;
const char *webroot;
unsigned short out_len;
unsigned short out_off;
} fastcgi_conn;
char *r_addr;
char *r_host;
} fastcgi_cnx_t;
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_init(fastcgi_cnx_t *conn, int mode, unsigned int req_num, const sock *client, const http_req *req, const http_uri *uri);
int fastcgi_close_stdin(fastcgi_conn *conn);
int fastcgi_close_cnx(fastcgi_cnx_t *cnx);
int fastcgi_php_error(const fastcgi_conn *conn, const char *msg, int msg_len, char *err_msg);
int fastcgi_close_stdin(fastcgi_cnx_t *cnx);
int fastcgi_header(fastcgi_conn *conn, http_res *res, char *err_msg);
int fastcgi_php_error(fastcgi_cnx_t *cnx, char *err_msg);
int fastcgi_send(fastcgi_conn *conn, sock *client, int flags);
int fastcgi_recv_frame(fastcgi_cnx_t *cnx);
int fastcgi_dump(fastcgi_conn *conn, char *buf, long len);
int fastcgi_header(fastcgi_cnx_t *cnx, http_res *res, char *err_msg);
int fastcgi_receive(fastcgi_conn *conn, sock *client, unsigned long len);
long fastcgi_send(fastcgi_cnx_t *cnx, sock *client);
#endif //NECRONDA_SERVER_FASTCGI_H
int fastcgi_dump(fastcgi_cnx_t *cnx, char *buf, long len);
int fastcgi_receive(fastcgi_cnx_t *cnx, sock *client, unsigned long len);
int fastcgi_receive_chunked(fastcgi_cnx_t *cnx, sock *client);
#endif //SESIMOS_FASTCGI_H

View File

@ -1,13 +1,33 @@
/**
* Necronda Web Server
* MaxMind GeoIP Database interface
* src/lib/geoip.c
* Lorenz Stechauner, 2021-05-04
* sesimos - secure, simple, modern web server
* @brief MaxMind GeoIP Database interface
* @file src/lib/geoip.c
* @author Lorenz Stechauner
* @date 2021-05-04
*/
#include "geoip.h"
#include "../logger.h"
#include "error.h"
#include "utils.h"
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len) {
#include <memory.h>
#include <dirent.h>
#include <errno.h>
static MMDB_s mmdbs[GEOIP_MAX_MMDB];
static void mmdb_error(int err) {
if (err == MMDB_SUCCESS) {
errno = 0;
} else if (err == MMDB_IO_ERROR) {
// errno already set
} else {
error_mmdb(err);
}
}
static MMDB_entry_data_list_s *geoip_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len) {
switch (list->entry_data.type) {
case MMDB_DATA_TYPE_MAP:
*str_off += sprintf(str + *str_off, "{");
@ -31,7 +51,7 @@ MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long
*str_off += sprintf(str + *str_off, "%llu", (unsigned long long) list->entry_data.uint128);
break;
case MMDB_DATA_TYPE_INT32:
*str_off += sprintf(str + *str_off, "%i", list->entry_data.uint32);
*str_off += sprintf(str + *str_off, "%i", list->entry_data.int32);
break;
case MMDB_DATA_TYPE_BOOLEAN:
*str_off += sprintf(str + *str_off, "%s", list->entry_data.boolean ? "true" : "false");
@ -43,13 +63,14 @@ MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long
*str_off += sprintf(str + *str_off, "%f", list->entry_data.double_value);
break;
}
if (list->entry_data.type != MMDB_DATA_TYPE_MAP && list->entry_data.type != MMDB_DATA_TYPE_ARRAY) {
if (list->entry_data.type != MMDB_DATA_TYPE_MAP && list->entry_data.type != MMDB_DATA_TYPE_ARRAY)
return list->next;
}
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);
next = geoip_json(next, str, str_off, str_len);
if (list->entry_data.type == MMDB_DATA_TYPE_MAP) {
stat = !stat;
if (stat) {
@ -58,12 +79,130 @@ MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long
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, "]");
if (i != list->entry_data.data_size - 1)
*str_off += sprintf(str + *str_off, ",");
}
*str_off += sprintf(str + *str_off, (list->entry_data.type == MMDB_DATA_TYPE_MAP) ? "}" : "]");
return next;
}
int geoip_init(const char *directory) {
char buf[512];
memset(mmdbs, 0, sizeof(mmdbs));
if (directory[0] == 0)
return 0;
DIR *geoip_dir;
if ((geoip_dir = opendir(directory)) == NULL)
return -1;
struct dirent *entry;
int i = 0, status;
while ((entry = readdir(geoip_dir)) != NULL) {
if (!strends(entry->d_name, ".mmdb"))
continue;
if (i >= GEOIP_MAX_MMDB) {
critical("Unable to initialize geoip: Too many .mmdb files");
closedir(geoip_dir);
return 1;
}
sprintf(buf, "%s/%s", directory, entry->d_name);
if ((status = MMDB_open(buf, 0, &mmdbs[i])) != MMDB_SUCCESS) {
mmdb_error(status);
critical("Unable to initialize geoip: Unable to open .mmdb file");
closedir(geoip_dir);
return 1;
}
i++;
}
closedir(geoip_dir);
if (i == 0) {
critical("Unable to initialize geoip: No .mmdb files found in %s", directory);
return 1;
}
return 0;
}
void geoip_free() {
for (int i = 0; i < GEOIP_MAX_MMDB && mmdbs[i].file_content != NULL; i++) {
MMDB_close(&mmdbs[i]);
}
}
int geoip_lookup_country(struct sockaddr *addr, char *str) {
for (int i = 0; i < GEOIP_MAX_MMDB && mmdbs[i].file_content != NULL; i++) {
// lookup
int mmdb_res;
MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&mmdbs[i], addr, &mmdb_res);
if (mmdb_res != MMDB_SUCCESS) {
return -1;
} else if (!result.found_entry) {
continue;
}
// get country iso code
MMDB_entry_data_s data;
int status;
if ((status = MMDB_get_value(&result.entry, &data, "country", "iso_code", NULL)) != MMDB_SUCCESS) {
if (status == MMDB_IO_ERROR) {
}
return -1;
}
// no, or invalid data
if (!data.has_data || data.type != MMDB_DATA_TYPE_UTF8_STRING)
continue;
// return country code
sprintf(str, "%.2s", data.utf8_string);
return 0;
}
// not found
return 1;
}
int geoip_lookup_json(struct sockaddr *addr, char *json, long len) {
long str_off = 0;
for (int i = 0; i < GEOIP_MAX_MMDB && mmdbs[i].filename != NULL; i++) {
int mmdb_res;
MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&mmdbs[i], addr, &mmdb_res);
if (mmdb_res != MMDB_SUCCESS) {
mmdb_error(mmdb_res);
error("Unable to lookup geoip info");
continue;
} else if (!result.found_entry) {
continue;
}
MMDB_entry_data_list_s *list;
if ((mmdb_res = MMDB_get_entry_data_list(&result.entry, &list)) != MMDB_SUCCESS) {
mmdb_error(mmdb_res);
error("Unable to lookup geoip info");
continue;
}
long prev = str_off;
if (str_off != 0) {
str_off--;
}
geoip_json(list, json, &str_off, len);
if (prev != 0) {
json[prev - 1] = ',';
}
MMDB_free_entry_data_list(list);
}
return 0;
}

View File

@ -1,18 +1,24 @@
/**
* Necronda Web Server
* MaxMind GeoIP Database interface (header file)
* src/lib/geoip.h
* Lorenz Stechauner, 2021-05-04
* sesimos - secure, simple, modern web server
* @brief MaxMind GeoIP Database interface (header file)
* @file src/lib/geoip.h
* @author Lorenz Stechauner
* @date 2021-05-04
*/
#ifndef NECRONDA_SERVER_GEOIP_H
#define NECRONDA_SERVER_GEOIP_H
#ifndef SESIMOS_GEOIP_H
#define SESIMOS_GEOIP_H
#include <maxminddb.h>
#define GEOIP_MAX_SIZE 8192
#define GEOIP_MAX_JSON_SIZE 8192
#define GEOIP_MAX_MMDB 3
MMDB_entry_data_list_s *mmdb_json(MMDB_entry_data_list_s *list, char *str, long *str_off, long str_len);
int geoip_init(const char *directory);
#endif //NECRONDA_SERVER_GEOIP_H
void geoip_free();
int geoip_lookup_country(struct sockaddr *addr, char *str);
#endif //SESIMOS_GEOIP_H

View File

@ -1,18 +1,56 @@
/**
* Necronda Web Server
* HTTP implementation
* src/lib/http.c
* Lorenz Stechauner, 2020-12-09
* sesimos - secure, simple, modern web server
* @brief HTTP implementation
* @file src/lib/http.c
* @author Lorenz Stechauner
* @date 2020-12-09
*/
#include "http.h"
#include "utils.h"
#include "compress.h"
#include "list.h"
#include "error.h"
#include <string.h>
#include <errno.h>
void http_append_to_header_field(http_field *field, const char *value, unsigned long len);
static int http_error(int err) {
if (err == 0) {
errno = 0;
} else if (err == HTTP_ERROR_SYSCALL) {
// errno already set
} else {
error_http(err);
}
return -1;
}
const char *http_error_str(int err) {
switch (err) {
case HTTP_ERROR_TOO_MANY_HEADER_FIELDS:
return "too many header fields";
case HTTP_ERROR_EOH_NOT_FOUND:
return "end of http header not found";
case HTTP_ERROR_HEADER_MALFORMED:
return "http header malformed";
case HTTP_ERROR_INVALID_VERSION:
return "invalid http version";
case HTTP_ERROR_URI_TOO_LONG:
return "uri too long";
case HTTP_ERROR_GENERAL:
default:
return "unknown error";
}
}
void http_to_camel_case(char *str, int mode) {
char last = '-';
char ch;
if (mode == HTTP_PRESERVE)
return;
char ch, last = '-';
for (int i = 0; i < strlen(str); i++) {
ch = str[i];
if (mode == HTTP_CAMEL && last == '-' && ch >= 'a' && ch <= 'z') {
@ -24,12 +62,50 @@ void http_to_camel_case(char *str, int mode) {
}
}
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]);
const char *http_field_get_name(const http_field *field) {
if (field->type == HTTP_FIELD_NORMAL) {
return field->normal.name;
} else if (field->type == HTTP_FIELD_EX_VALUE) {
return field->ex_value.name;
} else if (field->type == HTTP_FIELD_EX_NAME) {
return field->ex_name.name;
}
hdr->field_num = 0;
return NULL;
}
const char *http_field_get_value(const http_field *field) {
if (field->type == HTTP_FIELD_NORMAL) {
return field->normal.value;
} else if (field->type == HTTP_FIELD_EX_VALUE) {
return field->ex_value.value;
} else if (field->type == HTTP_FIELD_EX_NAME) {
return field->ex_name.value;
}
return NULL;
}
void http_free_field(http_field *f) {
if (f->type == HTTP_FIELD_NORMAL) {
f->normal.name[0] = 0;
f->normal.value[0] = 0;
} else if (f->type == HTTP_FIELD_EX_VALUE) {
f->ex_value.name[0] = 0;
free(f->ex_value.value);
f->ex_value.value = NULL;
} else if (f->type == HTTP_FIELD_EX_NAME) {
free(f->ex_name.name);
free(f->ex_name.value);
f->ex_name.name = NULL;
f->ex_name.value = NULL;
}
}
void http_free_hdr(http_hdr *hdr) {
for (int i = 0; i < list_size(hdr->fields); i++) {
http_free_field(&hdr->fields[i]);
}
list_free(hdr->fields);
hdr->last_field_num = -1;
}
void http_free_req(http_req *req) {
@ -42,174 +118,238 @@ 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;
}
int http_init_hdr(http_hdr *hdr) {
hdr->last_field_num = -1;
hdr->fields = list_create(sizeof(http_field), HTTP_INIT_HEADER_FIELD_NUM);
if (hdr->fields == NULL)
return http_error(HTTP_ERROR_SYSCALL);
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;
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr, int flags) {
if (hdr->last_field_num > list_size(hdr->fields))
return http_error(HTTP_ERROR_GENERAL);
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;
char *pos1 = (char *) buf, *pos2 = (char *) end_ptr;
if (buf[0] == ' ' || buf[0] == '\t') {
if (hdr->last_field_num == -1)
return http_error(HTTP_ERROR_GENERAL);
http_field *f = &hdr->fields[(int) hdr->last_field_num];
str_trim_lws(&pos1, &pos2);
http_append_to_header_field(f, pos1, pos2 - pos1);
return 0;
}
pos1 = memchr(buf, ':', end_ptr - buf);
if (pos1 == NULL)
return http_error(HTTP_ERROR_GENERAL);
long len1 = pos1 - buf;
pos1++;
str_trim_lws(&pos1, &pos2);
long len2 = pos2 - pos1;
char header_name[256];
sprintf(header_name, "%.*s", (int) len1, buf);
int field_num = list_size(hdr->fields);
int found = http_get_header_field_num(hdr, header_name);
if (!(flags & HTTP_MERGE_FIELDS) || found == -1) {
if (http_add_header_field_len(hdr, buf, len1, pos1, len2 < 0 ? 0 : len2) != 0)
return http_error(HTTP_ERROR_TOO_MANY_HEADER_FIELDS);
} else {
field_num = found;
http_append_to_header_field(&hdr->fields[found], ", ", 2);
http_append_to_header_field(&hdr->fields[found], pos1, len2);
}
hdr->last_field_num = field_num;
return 0;
}
int http_parse_request(char *buf, http_req *req) {
char *ptr, *pos0 = buf, *pos1, *pos2;
long len;
unsigned long header_len = strstr(buf, "\r\n\r\n") - buf + 4;
if (header_len <= 0) {
print(ERR_STR "Unable to parse http header: End of header not found" CLR_STR);
return 5;
}
if (header_len <= 0)
return http_error(HTTP_ERROR_EOH_NOT_FOUND);
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
print(ERR_STR "Unable to parse http header: Header contains illegal characters" CLR_STR);
return 4;
}
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F)
return http_error(HTTP_ERROR_HEADER_MALFORMED);
}
ptr = buf;
while (header_len > (ptr - buf + 2)) {
pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
print(ERR_STR "Unable to parse http header: Invalid header format" CLR_STR);
return 1;
}
if (pos0 == NULL)
return http_error(HTTP_ERROR_HEADER_MALFORMED);
if (req->version[0] == 0) {
pos1 = memchr(ptr, ' ', rcv_len - (ptr - buf)) + 1;
if (pos1 == NULL) goto err_hdr_fmt;
pos1 = (char *) strchr(ptr, ' ') + 1;
if (pos1 == NULL)
return http_error(HTTP_ERROR_HEADER_MALFORMED);
if (pos1 - ptr - 1 >= sizeof(req->method)) {
print(ERR_STR "Unable to parse http header: Method name too long" CLR_STR);
return 2;
}
if (pos1 - ptr - 1 >= sizeof(req->method))
return http_error(HTTP_ERROR_HEADER_MALFORMED);
for (int i = 0; i < (pos1 - ptr - 1); i++) {
if (ptr[i] < 'A' || ptr[i] > 'Z') {
print(ERR_STR "Unable to parse http header: Invalid method" CLR_STR);
return 2;
}
if (ptr[i] < 'A' || ptr[i] > 'Z')
return http_error(HTTP_ERROR_HEADER_MALFORMED);
}
snprintf(req->method, sizeof(req->method), "%.*s", (int) (pos1 - ptr - 1), ptr);
pos2 = 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;
}
pos2 = (char *) strchr(pos1, ' ') + 1;
if (pos2 == NULL)
return http_error(HTTP_ERROR_HEADER_MALFORMED);
if (memcmp(pos2, "HTTP/", 5) != 0 || memcmp(pos2 + 8, "\r\n", 2) != 0) {
print(ERR_STR "Unable to parse http header: Invalid version" CLR_STR);
return 3;
}
if (memcmp(pos2, "HTTP/", 5) != 0 || memcmp(pos2 + 8, "\r\n", 2) != 0)
return http_error(HTTP_ERROR_INVALID_VERSION);
len = pos2 - pos1 - 1;
if (len >= 2048)
return http_error(HTTP_ERROR_URI_TOO_LONG);
req->uri = malloc(len + 1);
sprintf(req->uri, "%.*s", (int) len, pos1);
sprintf(req->version, "%.3s", pos2 + 5);
} else {
int ret = http_parse_header_field(&req->hdr, ptr, pos0);
if (ret != 0) return ret;
if (http_parse_header_field(&req->hdr, ptr, pos0, HTTP_MERGE_FIELDS) != 0)
return -1;
}
ptr = pos0 + 2;
}
if (pos0[2] == '\r' && pos0[3] == '\n') {
break;
}
return (int) header_len;
}
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 http_error(HTTP_ERROR_GENERAL);
}
int http_receive_request(sock *client, http_req *req) {
long rcv_len;
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;
http_init_hdr(&req->hdr);
rcv_len = sock_recv(client, buf, CLIENT_MAX_HEADER_SIZE - 1, MSG_PEEK);
if (rcv_len <= 0)
return -1;
buf[rcv_len] = 0;
long header_len = http_parse_request(buf, req);
if (header_len < 0)
return (int) -header_len;
if (sock_recv_x(client, buf, header_len, 0) == -1)
return -1;
return 0;
}
const char *http_get_header_field(const http_hdr *hdr, const char *field_name) {
int num = http_get_header_field_num(hdr, field_name);
return (num >= 0 && num < list_size(hdr->fields)) ? http_field_get_value(&hdr->fields[num]) : NULL;
}
int http_get_header_field_num(const http_hdr *hdr, const char *field_name) {
for (int i = 0; i < list_size(hdr->fields); i++) {
if (strcasecmp(field_name, http_field_get_name(&hdr->fields[i])) == 0)
return i;
}
return -1;
}
int http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
return http_add_header_field_len(hdr, field_name, strlen(field_name), field_value, strlen(field_value));
}
int http_add_header_field_len(http_hdr *hdr, const char *name, unsigned long name_len, const char *value, unsigned long value_len) {
http_field *f;
hdr->fields = list_append_ptr(hdr->fields, (void **) &f);
if (name_len < sizeof(f->normal.name) && value_len < sizeof(f->normal.value)) {
f->type = HTTP_FIELD_NORMAL;
memcpy(f->normal.name, name, name_len);
memcpy(f->normal.value, value, value_len);
f->normal.name[name_len] = 0;
f->normal.value[value_len] = 0;
http_to_camel_case(f->normal.name, HTTP_PRESERVE);
} else if (name_len < sizeof(f->ex_value.name)) {
f->type = HTTP_FIELD_EX_VALUE;
f->ex_value.value = malloc(value_len + 1);
memcpy(f->ex_value.name, name, name_len);
memcpy(f->ex_value.value, value, value_len);
f->ex_value.name[name_len] = 0;
f->ex_value.value[value_len] = 0;
http_to_camel_case(f->ex_value.name, HTTP_PRESERVE);
} else {
f->type = HTTP_FIELD_EX_NAME;
f->ex_name.name = malloc(name_len + 1);
f->ex_name.value = malloc(value_len + 1);
memcpy(f->ex_name.name, name, name_len);
memcpy(f->ex_name.value, value, value_len);
f->ex_name.name[name_len] = 0;
f->ex_name.value[value_len] = 0;
http_to_camel_case(f->ex_name.name, HTTP_PRESERVE);
}
return 0;
}
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;
int http_add_to_header_field(http_hdr *hdr, const char *field_name, const char *field_value) {
int field_num = http_get_header_field_num(hdr, field_name);
if (field_num == -1)
return http_add_header_field(hdr, field_name, field_value);
http_append_to_header_field(&hdr->fields[field_num], field_value, strlen(field_value));
return 0;
}
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_append_to_header_field(http_field *field, const char *value, unsigned long len) {
if (field->type == HTTP_FIELD_NORMAL) {
unsigned long total_len = strlen(field->normal.value) + len + 1;
if (total_len < sizeof(field->normal.value)) {
strncat(field->normal.value, value, len);
} else {
field->type = HTTP_FIELD_EX_VALUE;
char *new = malloc(total_len);
strcpy(new, field->normal.value);
strncat(new, value, len);
field->ex_value.value = new;
}
} else if (field->type == HTTP_FIELD_EX_VALUE) {
field->ex_value.value = realloc(field->ex_value.value, strlen(field->ex_value.value) + len + 1);
strncat(field->ex_value.value, value, len);
} else if (field->type == HTTP_FIELD_EX_NAME) {
field->ex_name.value = realloc(field->ex_name.value, strlen(field->ex_name.value) + len + 1);
strncat(field->ex_name.value, value, len);
}
}
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
char field_name_1[256], field_name_2[256];
strcpy(field_name_1, field_name);
http_to_camel_case(field_name_1, HTTP_LOWER);
int i = 0;
int diff = 1;
if (mode == HTTP_REMOVE_LAST) {
i = hdr->field_num - 1;
i = list_size(hdr->fields) - 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--;
for (; i < list_size(hdr->fields) && i >= 0; i += diff) {
if (strcasecmp(field_name, http_field_get_name(&hdr->fields[i])) == 0) {
http_free_field(&hdr->fields[i]);
list_remove(hdr->fields, i);
if (mode == HTTP_REMOVE_ALL) {
i -= diff;
} else {
@ -222,32 +362,41 @@ void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode) {
int http_send_response(sock *client, http_res *res) {
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]);
for (int i = 0; i < list_size(res->hdr.fields); i++) {
const http_field *f = &res->hdr.fields[i];
off += sprintf(buf + off, "%s: %s\r\n", http_field_get_name(f), http_field_get_value(f));
}
off += sprintf(buf + off, "\r\n");
if (sock_send(client, buf, off, 0) < 0) {
if (sock_send_x(client, buf, off, 0) != off)
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]);
for (int i = 0; i < list_size(req->hdr.fields); i++) {
const http_field *f = &req->hdr.fields[i];
off += sprintf(buf + off, "%s: %s\r\n", http_field_get_name(f), http_field_get_value(f));
}
off += sprintf(buf + off, "\r\n");
long ret = sock_send(server, buf, off, 0);
if (ret <= 0) {
if (sock_send_x(server, buf, off, 0) != off)
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++) {
int http_send_100_continue(sock *client) {
char buf[256];
char date_buf[64];
int size = sprintf(buf, "HTTP/1.1 100 Continue\r\nDate: %s\r\nServer: " SERVER_STR "\r\n\r\n",
http_get_date(date_buf, sizeof(date_buf)));
return sock_send_x(client, buf, size, 0) == -1 ? -1 : 0;
}
const http_status *http_get_status(status_code_t status_code) {
for (int i = 0; i < http_statuses_size; i++) {
if (http_statuses[i].code == status_code) {
return &http_statuses[i];
}
@ -255,74 +404,75 @@ const http_status *http_get_status(unsigned short status_code) {
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) {
const http_status_msg *http_get_error_msg(status_code_t status_code) {
for (int i = 0; i < http_status_messages_size; i++) {
if (http_status_messages[i].code == status_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;
const char *http_get_status_color(status_code_t status_code) {
if (status_code == 304) return HTTP_2XX_STR;
switch (status_code / 100) {
case 1: return HTTP_1XX_STR;
case 2: return HTTP_2XX_STR;
case 3: return HTTP_3XX_STR;
case 4: return HTTP_4XX_STR;
case 5: return HTTP_5XX_STR;
default: return "";
}
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);
char *http_format_date(time_t ts, char *buf, size_t size) {
struct tm time_info;
strftime(buf, size, "%a, %d %b %Y %H:%M:%S GMT", gmtime_r(&ts, &time_info));
return buf;
}
char *http_get_date(char *buf, size_t size) {
time_t rawtime;
time(&rawtime);
return http_format_date(rawtime, buf, size);
time_t raw_time;
time(&raw_time);
return http_format_date(raw_time, 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}
const http_doc_info *http_get_status_info(status_code_t status_code) {
static const http_doc_info info[] = {
{"info", HTTP_COLOR_INFO, "/.sesimos/res/icon-info.svg", http_info_doc},
{"success", HTTP_COLOR_SUCCESS, "/.sesimos/res/icon-success.svg", http_success_doc},
{"warning", HTTP_COLOR_WARNING, "/.sesimos/res/icon-warning.svg", http_warning_doc},
{"error", HTTP_COLOR_ERROR, "/.sesimos/res/icon-error.svg", http_error_doc}
};
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];
if (status_code == 304) return &info[1];
switch (status_code / 100) {
case 1: return &info[0];
case 2: return &info[1];
case 3: return &info[2];
case 4: // see case 5
case 5: return &info[3];
default: return NULL;
}
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");
const char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
const char *content_type = http_get_header_field(&res->hdr, "Content-Type");
const char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
if (mime_is_compressible(content_type) && content_encoding == NULL && accept_encoding != NULL) {
if (strstr(accept_encoding, "br") != NULL) {
if (strcontains(accept_encoding, "br")) {
return COMPRESS_BR;
} else if (strstr(accept_encoding, "gzip") != NULL) {
} else if (strcontains(accept_encoding, "gzip")) {
return COMPRESS_GZ;
}
}
return 0;
}
long http_get_keep_alive_timeout(http_hdr *hdr) {
const char *keep_alive = http_get_header_field(hdr, "Keep-Alive");
if (!keep_alive) return -1;
const char *timeout = strstr(keep_alive, "timeout=");
if (!timeout) return -1;
return strtol(timeout + 8, NULL, 10);
}

View File

@ -1,12 +1,13 @@
/**
* Necronda Web Server
* HTTP implementation (header file)
* src/lib/http.h
* Lorenz Stechauner, 2020-12-09
* sesimos - secure, simple, modern web server
* @brief HTTP implementation (header file)
* @file src/lib/http.h
* @author Lorenz Stechauner
* @date 2020-12-09
*/
#ifndef NECRONDA_SERVER_HTTP_H
#define NECRONDA_SERVER_HTTP_H
#ifndef SESIMOS_HTTP_H
#define SESIMOS_HTTP_H
#include "sock.h"
@ -18,6 +19,12 @@
#define HTTP_REMOVE_ALL 1
#define HTTP_REMOVE_LAST 2
#define HTTP_FIELD_NORMAL 0
#define HTTP_FIELD_EX_VALUE 1
#define HTTP_FIELD_EX_NAME 2
#define HTTP_MERGE_FIELDS 1
#define HTTP_1XX_STR "\x1B[1;32m"
#define HTTP_2XX_STR "\x1B[1;32m"
#define HTTP_3XX_STR "\x1B[1;33m"
@ -30,23 +37,40 @@
#define HTTP_COLOR_ERROR "#C00000"
#define CLIENT_MAX_HEADER_SIZE 8192
#define HTTP_INIT_HEADER_FIELD_NUM 16
#define HTTP_TYPE_INFORMATIONAL 1
#define HTTP_TYPE_SUCCESS 2
#define HTTP_TYPE_REDIRECTION 3
#define HTTP_TYPE_CLIENT_ERROR 4
#define HTTP_TYPE_SERVER_ERROR 5
#define HTTP_ERROR_GENERAL 1
#define HTTP_ERROR_SYSCALL 2
#define HTTP_ERROR_TOO_MANY_HEADER_FIELDS 3
#define HTTP_ERROR_EOH_NOT_FOUND 4
#define HTTP_ERROR_HEADER_MALFORMED 5
#define HTTP_ERROR_INVALID_VERSION 6
#define HTTP_ERROR_URI_TOO_LONG 7
#ifndef SERVER_STR
# define SERVER_STR "Necronda"
# define SERVER_STR "sesimos"
#endif
#ifndef SERVER_STR_HTML
# define SERVER_STR_HTML "Necronda&nbsp;web&nbsp;server"
# define SERVER_STR_HTML "sesimos&nbsp;web&nbsp;server"
#endif
typedef unsigned short status_code_t;
typedef struct {
unsigned short code;
char type[16];
status_code_t code:10;
unsigned char type:3;
char msg[64];
} http_status;
typedef struct {
unsigned short code;
status_code_t code:10;
const char *msg;
} http_status_msg;
@ -58,8 +82,26 @@ typedef struct {
} http_doc_info;
typedef struct {
char field_num;
char *fields[64][2];
char type;
union {
struct {
char name[32];
char value[32];
} normal;
struct {
char name[64 - sizeof(char *)];
char *value;
} ex_value;
struct {
char *name;
char *value;
} ex_name;
};
} http_field;
typedef struct {
int last_field_num;
http_field *fields;
} http_hdr;
typedef struct {
@ -80,8 +122,9 @@ typedef enum {
} http_error_origin;
typedef struct {
unsigned short status;
status_code_t status;
http_error_origin origin;
const char* ws_key;
} http_status_ctx;
extern const http_status http_statuses[];
@ -89,32 +132,41 @@ extern const http_status_msg http_status_messages[];
extern const int http_statuses_size;
extern const int http_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[];
extern const char http_error_doc[], http_warning_doc[], http_success_doc[], http_info_doc[];
const char *http_error_str(int err);
void http_to_camel_case(char *str, int mode);
const char *http_field_get_name(const http_field *field);
const char *http_field_get_value(const http_field *field);
int http_init_hdr(http_hdr *hdr);
void http_free_field(http_field *f);
void http_free_hdr(http_hdr *hdr);
void http_free_req(http_req *req);
void http_free_res(http_res *res);
int http_parse_request(char *buf, http_req *req);
int http_receive_request(sock *client, http_req *req);
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr) ;
int http_parse_header_field(http_hdr *hdr, const char *buf, const char *end_ptr, int flags);
char *http_get_header_field(const http_hdr *hdr, const char *field_name);
const char *http_get_header_field(const http_hdr *hdr, const char *field_name);
void http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
int http_get_header_field_num(const http_hdr *hdr, const char *field_name);
int http_add_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
int http_add_header_field_len(http_hdr *hdr, const char *name, unsigned long name_len, const char *value, unsigned long value_len);
int http_add_to_header_field(http_hdr *hdr, const char *field_name, const char *field_value);
void http_remove_header_field(http_hdr *hdr, const char *field_name, int mode);
@ -122,18 +174,22 @@ 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);
int http_send_100_continue(sock *client);
const http_status_msg *http_get_error_msg(const http_status *status);
const http_status *http_get_status(status_code_t status_code);
const char *http_get_status_color(const http_status *status);
const http_status_msg *http_get_error_msg(status_code_t status_code);
const char *http_get_status_color(status_code_t status_code);
char *http_format_date(time_t time, char *buf, size_t size);
char *http_get_date(char *buf, size_t size);
const http_doc_info *http_get_status_info(const http_status *status);
const http_doc_info *http_get_status_info(status_code_t status_code);
int http_get_compression(const http_req *req, const http_res *res);
#endif //NECRONDA_SERVER_HTTP_H
long http_get_keep_alive_timeout(http_hdr *hdr);
#endif //SESIMOS_HTTP_H

View File

@ -1,303 +1,212 @@
/**
* Necronda Web Server
* HTTP static implementation
* src/lib/http_static.c
* Lorenz Stechauner, 2021-05-03
* sesimos - secure, simple, modern web server
* @brief HTTP static implementation
* @file src/lib/http_static.c
* @author Lorenz Stechauner
* @date 2021-05-03
* @details https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
*/
#include "../necronda.h"
#include "http.h"
const http_status http_statuses[] = {
{100, "Informational", "Continue"},
{101, "Informational", "Switching Protocols"},
{100, HTTP_TYPE_INFORMATIONAL, "Continue"},
{101, HTTP_TYPE_INFORMATIONAL, "Switching Protocols"},
{102, HTTP_TYPE_INFORMATIONAL, "Processing"},
{103, HTTP_TYPE_INFORMATIONAL, "Early Hints"},
{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"},
{200, HTTP_TYPE_SUCCESS, "OK"},
{201, HTTP_TYPE_SUCCESS, "Created"},
{202, HTTP_TYPE_SUCCESS, "Accepted"},
{203, HTTP_TYPE_SUCCESS, "Non-Authoritative Information"},
{204, HTTP_TYPE_SUCCESS, "No Content"},
{205, HTTP_TYPE_SUCCESS, "Reset Content"},
{206, HTTP_TYPE_SUCCESS, "Partial Content"},
{207, HTTP_TYPE_SUCCESS, "Multi-Status"},
{208, HTTP_TYPE_SUCCESS, "Already Reported"},
{226, HTTP_TYPE_SUCCESS, "Instance Manipulation Used"},
{300, "Redirection", "Multiple Choices"},
{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"},
{300, HTTP_TYPE_REDIRECTION, "Multiple Choices"},
{301, HTTP_TYPE_REDIRECTION, "Moved Permanently"},
{302, HTTP_TYPE_REDIRECTION, "Found"},
{303, HTTP_TYPE_REDIRECTION, "See Other"},
{304, HTTP_TYPE_SUCCESS, "Not Modified"},
{305, HTTP_TYPE_REDIRECTION, "Use Proxy"},
{307, HTTP_TYPE_REDIRECTION, "Temporary Redirect"},
{308, HTTP_TYPE_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"},
{400, HTTP_TYPE_CLIENT_ERROR, "Bad Request"},
{401, HTTP_TYPE_CLIENT_ERROR, "Unauthorized"},
{402, HTTP_TYPE_CLIENT_ERROR, "Payment Required"},
{403, HTTP_TYPE_CLIENT_ERROR, "Forbidden"},
{404, HTTP_TYPE_CLIENT_ERROR, "Not Found"},
{405, HTTP_TYPE_CLIENT_ERROR, "Method Not Allowed"},
{406, HTTP_TYPE_CLIENT_ERROR, "Not Acceptable"},
{407, HTTP_TYPE_CLIENT_ERROR, "Proxy Authentication Required"},
{408, HTTP_TYPE_CLIENT_ERROR, "Request Timeout"},
{409, HTTP_TYPE_CLIENT_ERROR, "Conflict"},
{410, HTTP_TYPE_CLIENT_ERROR, "Gone"},
{411, HTTP_TYPE_CLIENT_ERROR, "Length Required"},
{412, HTTP_TYPE_CLIENT_ERROR, "Precondition Failed"},
{413, HTTP_TYPE_CLIENT_ERROR, "Request Entity Too Large"},
{414, HTTP_TYPE_CLIENT_ERROR, "Request-URI Too Long"},
{415, HTTP_TYPE_CLIENT_ERROR, "Unsupported Media Type"},
{416, HTTP_TYPE_CLIENT_ERROR, "Range Not Satisfiable"},
{417, HTTP_TYPE_CLIENT_ERROR, "Expectation Failed"},
{421, HTTP_TYPE_CLIENT_ERROR, "Misdirected Request"},
{422, HTTP_TYPE_CLIENT_ERROR, "Unprocessable Content"},
{423, HTTP_TYPE_CLIENT_ERROR, "Locked"},
{424, HTTP_TYPE_CLIENT_ERROR, "Failed Dependency"},
{425, HTTP_TYPE_CLIENT_ERROR, "Too Early"},
{426, HTTP_TYPE_CLIENT_ERROR, "Upgrade Required"},
{428, HTTP_TYPE_CLIENT_ERROR, "Precondition Required"},
{429, HTTP_TYPE_CLIENT_ERROR, "Too Many Requests"},
{431, HTTP_TYPE_CLIENT_ERROR, "Request Header Fields Too Large"},
{451, HTTP_TYPE_CLIENT_ERROR, "Unavailable For Legal Reasons"},
{500, "Server Error", "Internal Server Error"},
{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"},
{500, HTTP_TYPE_SERVER_ERROR, "Internal Server Error"},
{501, HTTP_TYPE_SERVER_ERROR, "Not Implemented"},
{502, HTTP_TYPE_SERVER_ERROR, "Bad Gateway"},
{503, HTTP_TYPE_SERVER_ERROR, "Service Unavailable"},
{504, HTTP_TYPE_SERVER_ERROR, "Gateway Timeout"},
{505, HTTP_TYPE_SERVER_ERROR, "HTTP Version Not Supported"},
{506, HTTP_TYPE_SERVER_ERROR, "Variant Also Negotiates"},
{507, HTTP_TYPE_SERVER_ERROR, "Insufficient Storage"},
{508, HTTP_TYPE_SERVER_ERROR, "Loop Detected"},
{511, HTTP_TYPE_SERVER_ERROR, "Network Authentication Required"},
};
const http_status_msg http_status_messages[] = {
{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."},
{100, "The client SHOULD continue with its request. The server MUST send a final response after the request "
"has been completed."},
{101, "The server understands and is willing to comply with the clients request, via the Upgrade message "
"header field, for a change in the application protocol being used on this connection."},
{102, "The server has a reasonable expectation that the request will take significant time to complete. The "
"server MUST send a final response after the request has been completed."},
{103, "The client can speculatively evaluate the header fields included in the response while waiting for the "
"final response. The server MUST send a final response after the request has been completed."},
{200, "The request has succeeded."},
{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."},
{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."},
{207, "The response provides status for multiple independent operations."},
{208, "The response is used to avoid enumerating the internal members of multiple bindings to the same "
"collection repeatedly."},
{226, "The server has fulfilled a GET request for the resource, and the response is a representation of the "
"result of one or more instance-manipulations applied to the current instance."},
{300, "The requested resource corresponds to any one of a set of representations, each with its own specific location, and agent-driven negotiation information is being provided so that the user (or user agent) can select a preferred representation and redirect its request to that location."},
{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."},
{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."},
{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."},
{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."},
{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."},
{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."},
{421, "The server is not able to produce a response. The client MAY retry the request over a different "
"connection."},
{422, "The server understands the content type of the request content, and the syntax of the request content "
"is correct, but the server was unable to process the contained information."},
{423, "The source or destination resource of a method is locked."},
{424, "The method could not be performed on the resource because the requested action depended on another "
"action and that action failed."},
{425, "The server is unwilling to risk processing a request that might be replayed."},
{426, "The server refuses to perform the request using the current protocol but might be willing to do so "
"after the client upgrades to a different protocol. The server MUST send an Upgrade header field to"
"indicate the required protocol(s)."},
{428, "The origin server requires the request to be conditional. By requiring requests to be conditional, the "
"server can assure that clients are working with the correct copies and thus avoiding a lost update."},
{429, "The client has sent too many requests in a given amount of time."},
{431, "The server is unwilling to process the request because its header fields are too large. The request MAY "
"be resubmitted after reducing the size of the request header fields."},
{451, "The server is denying access to the resource as a consequence of a legal demand."},
{500, "The server encountered an unexpected condition which prevented it from fulfilling the request."},
{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."}
{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."},
{506, "The server has an internal configuration error: the chosen variant resource is configured to engage in "
"transparent content negotiation itself, and is therefore not a proper end point in the negotiation "
"process."},
{507, "The method could not be performed on the resource because the server is unable to store the "
"representation needed to successfully complete the request. This condition is considered to be "
"temporary."},
{508, "The server terminated an operation because it encountered an infinite loop while processing the "
"request."},
{511, "The client needs to authenticate to gain network access. The response representation SHOULD contain a "
"link to a resource that allows the user to submit credentials."},
};
const char http_default_document[] =
"<!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 int http_statuses_size = sizeof(http_statuses) / sizeof(http_status);
const int http_status_messages_size = sizeof(http_status_messages) / sizeof(http_status_msg);
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_doc[] =
" <h1>%1$i</h1>\n"
" <h2>%2$s :&#xFEFF;(</h2>\n"
" <p>%3$s</p>\n"
" <p>%4$s</p>\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_warning_doc[] =
" <h1>%1$i</h1>\n"
" <h2>%2$s :&#xFEFF;)</h2>\n"
" <p>%3$s</p>\n"
" <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_success_doc[] =
" <h1>%1$i</h1>\n"
" <h2>%2$s :&#xFEFF;)</h2>\n"
" <p>%3$s</p>\n"
" <p>%4$s</p>\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);
const char http_info_doc[] =
" <h1>%1$i</h1>\n"
" <h2>%2$s :&#xFEFF;)</h2>\n"
" <p>%3$s</p>\n"
" <p>%4$s</p>\n";

View File

@ -1,12 +1,11 @@
/**
* Necronda Web Server
* FastCGI header file
* src/lib/include/fastcgi.h
* Lorenz Stechauner, 2021-05-03
* sesimos - secure, simple, modern web server
* @brief FastCGI header file
* @file src/lib/include/fastcgi.h
*/
#ifndef NECRONDA_SERVER_EXTERN_FASTCGI_H
#define NECRONDA_SERVER_EXTERN_FASTCGI_H
#ifndef SESIMOS_EXTERN_FASTCGI_H
#define SESIMOS_EXTERN_FASTCGI_H
/*
* Listening socket file number
@ -16,10 +15,8 @@
typedef struct {
unsigned char version;
unsigned char type;
unsigned char requestIdB1;
unsigned char requestIdB0;
unsigned char contentLengthB1;
unsigned char contentLengthB0;
unsigned short requestId;
unsigned short contentLength;
unsigned char paddingLength;
unsigned char reserved;
} FCGI_Header;
@ -57,8 +54,7 @@ typedef struct {
#define FCGI_NULL_REQUEST_ID 0
typedef struct {
unsigned char roleB1;
unsigned char roleB0;
unsigned short role;
unsigned char flags;
unsigned char reserved[5];
} FCGI_BeginRequestBody;
@ -81,10 +77,7 @@ typedef struct {
#define FCGI_FILTER 3
typedef struct {
unsigned char appStatusB3;
unsigned char appStatusB2;
unsigned char appStatusB1;
unsigned char appStatusB0;
unsigned int appStatus;
unsigned char protocolStatus;
unsigned char reserved[3];
} FCGI_EndRequestBody;
@ -119,4 +112,4 @@ typedef struct {
FCGI_UnknownTypeBody body;
} FCGI_UnknownTypeRecord;
#endif //NECRONDA_SERVER_EXTERN_FASTCGI_H
#endif //SESIMOS_EXTERN_FASTCGI_H

150
src/lib/list.c Normal file
View File

@ -0,0 +1,150 @@
#include "list.h"
#include <malloc.h>
#include <memory.h>
#include <errno.h>
#define FACTOR 4
#define meta(ptr) ((list_meta_t *) ((unsigned char *) (ptr) - sizeof(list_meta_t)))
#define data(ptr) ((unsigned char *) (ptr) + sizeof(list_meta_t))
typedef struct {
int init_size, elem_size, max_size, size;
} list_meta_t;
static void *list_resize(list_meta_t *list, int new_size) {
if (new_size <= 0) {
return NULL;
} else if (new_size == list->max_size) {
return list;
}
list_meta_t *new_ptr = realloc(list, sizeof(list_meta_t) + list->elem_size * new_size);
if (new_ptr == NULL)
return NULL;
new_ptr->max_size = new_size;
return new_ptr;
}
void *list_create(int elem_size, int init_elem_n) {
if (elem_size <= 0 || init_elem_n <= 0) {
errno = EINVAL;
return NULL;
}
void *list_ptr = malloc(sizeof(list_meta_t) + elem_size * init_elem_n);
if (list_ptr == NULL)
return NULL;
memset(list_ptr, 0, sizeof(list_meta_t) + elem_size * init_elem_n);
list_meta_t *list = list_ptr;
list->init_size = init_elem_n;
list->elem_size = elem_size;
list->max_size = init_elem_n;
list->size = 0;
return data(list_ptr);
}
int list_size(const void *list_ptr) {
return meta(list_ptr)->size;
}
int list_find(void *list_ptr, void *elem) {
list_meta_t *list = meta(list_ptr);
unsigned char *array = list_ptr;
for (int i = 0; i < list->size; i++) {
if (memcmp(array + i * list->elem_size, elem, list->elem_size) == 0) {
return i;
}
}
return -1;
}
int list_contains(void *list_ptr, void *elem) {
return list_find(list_ptr, elem) != -1;
}
void *list_insert(void *list_ptr, void *elem, int n) {
void *ptr = NULL;
list_ptr = list_insert_ptr(list_ptr, &ptr, n);
if (list_ptr != NULL && ptr != NULL) {
memcpy(ptr, elem, meta(list_ptr)->elem_size);
}
return list_ptr;
}
void *list_insert_ptr(void *list_ptr, void **elem, int n) {
list_meta_t *list = meta(list_ptr);
if (n < 0)
n = list->size + n + 1;
if (list->size >= list->max_size) {
if ((list = list_resize(list, list->max_size * FACTOR)) == NULL)
return NULL;
}
unsigned char *array = data(list);
if (n < list->size)
memmove(array + (n + 1) * list->elem_size, array + n * list->elem_size, (list->size - n) * list->elem_size);
*elem = array + n * list->elem_size;
memset(*elem, 0, list->elem_size);
list->size++;
return array;
}
void *list_append(void *list_ptr, void *elem) {
return list_insert(list_ptr, elem, -1);
}
void *list_append_ptr(void *list_ptr, void **elem) {
return list_insert_ptr(list_ptr, elem, -1);
}
void *list_remove(void *list_ptr, int n) {
list_meta_t *list = meta(list_ptr);
if (n < 0)
n = list->size + n;
unsigned char *array = list_ptr;
if (list->size > 1 && n < list->size)
memmove(array + n * list->elem_size, array + (n + 1) * list->elem_size, (list->size - n - 1) * list->elem_size);
list->size--;
memset(array + list->size * list->elem_size, 0, list->elem_size);
if (list->size <= list->max_size / FACTOR * 3 / 4 && list->max_size / FACTOR >= list->init_size) {
if ((list = list_resize(list, list->max_size / FACTOR)) == NULL) {
return NULL;
}
}
return data(list);
}
void *list_delete(void *list_ptr, void *elem) {
int idx = list_find(list_ptr, elem);
if (idx == -1) {
return list_ptr;
} else {
return list_remove(list_ptr, idx);
}
}
void *list_clear(void *list_ptr) {
list_meta_t *list = meta(list_ptr);
list->size = 0;
memset(list_ptr, 0, list->max_size * list->elem_size);
return data(list_resize(list, list->init_size));
}
void list_free(void *list_ptr) {
free(meta(list_ptr));
}

29
src/lib/list.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef SESIMOS_LIST_H
#define SESIMOS_LIST_H
void *list_create(int elem_size, int init_elem_n);
int list_size(const void *list_ptr);
int list_find(void *list_ptr, void *elem);
int list_contains(void *list_ptr, void *elem);
void *list_insert(void *list_ptr, void *elem, int n);
void *list_insert_ptr(void *list_ptr, void **elem, int n);
void *list_append(void *list_ptr, void *elem);
void *list_append_ptr(void *list_ptr, void **elem);
void *list_remove(void *list_ptr, int n);
void *list_delete(void *list_ptr, void *elem);
void *list_clear(void *list_ptr);
void list_free(void *list_ptr);
#endif //SESIMOS_LIST_H

167
src/lib/mpmc.c Normal file
View File

@ -0,0 +1,167 @@
#include "mpmc.h"
#include "../logger.h"
#include <errno.h>
#include <malloc.h>
#include <memory.h>
#include <pthread.h>
#include <signal.h>
static void *mpmc_worker(void *arg);
int mpmc_init(mpmc_t *ctx, int n_workers, int buf_size, void (*consumer)(void *obj), const char *name) {
ctx->alive = 1;
ctx->n_workers = n_workers;
ctx->size = buf_size, ctx->max_size = buf_size;
ctx->rd = 0, ctx->wr = 0;
ctx->buffer = NULL, ctx->workers = NULL;
ctx->consumer = consumer;
ctx->name = name;
if (sem_init(&ctx->free, 0, ctx->size) != 0 ||
sem_init(&ctx->used, 0, 0) != 0 ||
sem_init(&ctx->lck_rd, 0, 1) != 0 ||
sem_init(&ctx->lck_wr, 0, 1) != 0)
{
mpmc_destroy(ctx);
return -1;
}
if ((ctx->buffer = malloc(ctx->size * sizeof(void *))) == NULL ||
(ctx->workers = malloc(ctx->n_workers * sizeof(pthread_t))) == NULL)
{
mpmc_destroy(ctx);
return -1;
}
memset(ctx->buffer, 0, ctx->size * sizeof(void *));
memset(ctx->workers, -1, ctx->n_workers * sizeof(pthread_t));
for (int i = 0; i < ctx->n_workers; i++) {
int ret;
if ((ret = pthread_create(&ctx->workers[i], NULL, mpmc_worker, ctx)) != 0) {
mpmc_destroy(ctx);
errno = ret;
return -1;
}
}
return 0;
}
int mpmc_queue(mpmc_t *ctx, void *obj) {
// wait for buffer to be emptied
while (sem_wait(&ctx->free) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
return -1;
}
}
// lock wr field
while (sem_wait(&ctx->lck_wr) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
sem_post(&ctx->free);
return -1;
}
}
int p = ctx->wr;
ctx->wr = (ctx->wr + 1) % ctx->size;
// unlock wr field
sem_post(&ctx->lck_wr);
// fill buffer with object
ctx->buffer[p] = obj;
// inform worker
sem_post(&ctx->used);
return 0;
}
static void *mpmc_worker(void *arg) {
mpmc_t *ctx = arg;
int id;
for (id = 0; id < ctx->n_workers && ctx->workers[id] != pthread_self(); id++);
logger_set_name("%s/%i", ctx->name, id);
while (ctx->alive) {
// wait for buffer to be filled
if (sem_wait(&ctx->used) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
critical("Unable to lock semaphore");
errno = 0;
break;
}
}
// lock rd field
if (sem_wait(&ctx->lck_rd) != 0) {
if (errno == EINTR) {
errno = 0;
sem_post(&ctx->used);
continue;
} else {
critical("Unable to lock semaphore");
errno = 0;
sem_post(&ctx->used);
break;
}
}
int p = ctx->rd;
ctx->rd = (ctx->rd + 1) % ctx->size;
// unlock rd field
sem_post(&ctx->lck_rd);
// consume object
ctx->consumer(ctx->buffer[p]);
logger_set_prefix("");
// unlock slot in buffer
sem_post(&ctx->free);
}
return NULL;
}
void mpmc_stop(mpmc_t *ctx) {
ctx->alive = 0;
}
void mpmc_destroy(mpmc_t *ctx) {
int e = errno;
// stop threads, if running
mpmc_stop(ctx);
for (int i = 0; i < ctx->n_workers; i++) {
if (ctx->workers[i] == -1) break;
debug("Waiting for worker %s/%i to finish...", ctx->name, i);
pthread_kill(ctx->workers[i], SIGUSR1);
pthread_join(ctx->workers[i], NULL);
}
sem_destroy(&ctx->free);
sem_destroy(&ctx->used);
sem_destroy(&ctx->lck_rd);
sem_destroy(&ctx->lck_wr);
free(ctx->buffer);
free(ctx->workers);
// reset errno
errno = e;
}

27
src/lib/mpmc.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef SESIMOS_MPMC_H
#define SESIMOS_MPMC_H
#include <semaphore.h>
typedef struct {
unsigned char alive;
int n_workers;
int rd, wr;
sem_t free, used, lck_rd, lck_wr;
int size, max_size;
void **buffer;
pthread_t *workers;
void (*consumer)(void *obj);
const char* name;
} mpmc_t;
int mpmc_init(mpmc_t *ctx, int n_workers, int buf_size, void (*consumer)(void *obj), const char *name);
int mpmc_queue(mpmc_t *ctx, void *obj);
void mpmc_stop(mpmc_t *ctx);
void mpmc_destroy(mpmc_t *ctx);
#endif //SESIMOS_MPMC_H

694
src/lib/proxy.c Normal file
View File

@ -0,0 +1,694 @@
/**
* sesimos - secure, simple, modern web server
* @brief Reverse proxy
* @file src/lib/proxy.c
* @author Lorenz Stechauner
* @date 2021-01-07
*/
#include "../defs.h"
#include "../server.h"
#include "../logger.h"
#include "proxy.h"
#include "utils.h"
#include "config.h"
#include "error.h"
#include <openssl/ssl.h>
#include <string.h>
#include <errno.h>
#include <openssl/err.h>
#include <arpa/inet.h>
#include <semaphore.h>
static SSL_CTX *proxy_ctx = NULL;
static proxy_ctx_t *proxies = NULL;
static sem_t *available = NULL;
static sem_t lock;
static int num_proxy_hosts = -1;
int proxy_preload(void) {
int n = 0;
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
host_config_t *hc = &config.hosts[i];
if (hc->type == CONFIG_TYPE_UNSET) break;
if (hc->type != CONFIG_TYPE_REVERSE_PROXY) continue;
n++;
}
proxy_ctx = SSL_CTX_new(TLS_client_method());
if (proxy_ctx == NULL) {
return -1;
}
proxies = malloc(n * MAX_PROXY_CNX_PER_HOST * sizeof(proxy_ctx_t));
if (proxies == NULL) {
proxy_unload();
return -1;
}
memset(proxies, 0, n * MAX_PROXY_CNX_PER_HOST * sizeof(proxy_ctx_t));
available = malloc(n * sizeof(*available));
if (available == NULL) {
proxy_unload();
return -1;
}
for (int i = 0; i < n; i++) {
if (sem_init(&available[i], 0, MAX_PROXY_CNX_PER_HOST) != 0) {
proxy_unload();
return -1;
}
}
if (sem_init(&lock, 0, 1) != 0) {
proxy_unload();
return -1;
}
num_proxy_hosts = n;
return 0;
}
void proxy_unload(void) {
int e = errno;
SSL_CTX_free(proxy_ctx);
sem_destroy(&lock);
if (num_proxy_hosts != -1) {
for (int i = 0; i < num_proxy_hosts; i++) {
sem_destroy(&available[i]);
}
}
free(available);
free(proxies);
errno = e;
}
void proxy_close_all(void) {
proxy_ctx_t *ptr = proxies;
for (int i = 0; i < MAX_PROXY_CNX_PER_HOST * num_proxy_hosts; i++, ptr++) {
if (ptr->initialized) {
proxy_close(ptr);
logger_set_prefix("");
}
}
}
proxy_ctx_t *proxy_get_by_conf(host_config_t *conf) {
int n = 0;
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
host_config_t *hc = &config.hosts[i];
if (hc->type == CONFIG_TYPE_UNSET) break;
if (hc->type != CONFIG_TYPE_REVERSE_PROXY) continue;
if (hc == conf) break;
n++;
}
while (sem_wait(&available[n]) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
return NULL;
}
}
while (sem_wait(&lock) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
sem_post(&available[n]);
return NULL;
}
}
proxy_ctx_t *ptr = proxies + n * MAX_PROXY_CNX_PER_HOST;
for (int i = 0; i < MAX_PROXY_CNX_PER_HOST; i++, ptr++) {
if (!ptr->in_use) {
ptr->in_use = 1;
sem_post(&lock);
return ptr;
}
}
sem_post(&lock);
sem_post(&available[n]);
return NULL;
}
int proxy_unlock_ctx(proxy_ctx_t *ctx) {
int n = (int) ((ctx - proxies) / MAX_PROXY_CNX_PER_HOST);
if (ctx->close) proxy_close(ctx);
debug("Released proxy connection slot %i/%i", (ctx - proxies) % MAX_PROXY_CNX_PER_HOST, MAX_PROXY_CNX_PER_HOST);
ctx->in_use = 0;
ctx->client = NULL;
sem_post(&available[n]);
if (!ctx->close) {
return 1;
} else {
ctx->close = 0;
return 0;
}
}
int proxy_request_header(http_req *req, sock *sock) {
char buf1[256], buf2[256];
int p_len;
const char *via = http_get_header_field(&req->hdr, "Via");
sprintf(buf1, "HTTP/%s %s", req->version, SERVER_NAME);
if (via == NULL) {
http_add_header_field(&req->hdr, "Via", buf1);
} else {
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
if (p_len < 0 || p_len >= sizeof(buf2)) {
error("Header field 'Via' too long");
return -1;
}
http_remove_header_field(&req->hdr, "Via", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "Via", buf2);
}
int client_ipv6 = strchr(sock->addr, ':') != NULL;
int server_ipv6 = strchr(sock->s_addr, ':') != NULL;
p_len = snprintf(buf1, sizeof(buf1), "by=%s%s%s;for=%s%s%s;host=%s;proto=%s",
server_ipv6 ? "\"[" : "", sock->s_addr, server_ipv6 ? "]\"" : "",
client_ipv6 ? "\"[" : "", sock->addr, client_ipv6 ? "]\"" : "",
http_get_header_field(&req->hdr, "Host"), sock->enc ? "https" : "http");
if (p_len < 0 || p_len >= sizeof(buf1)) {
error("Appended part of header field 'Forwarded' too long");
return -1;
}
const char *forwarded = http_get_header_field(&req->hdr, "Forwarded");
if (forwarded == NULL) {
http_add_header_field(&req->hdr, "Forwarded", buf1);
} else {
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", forwarded, buf1);
if (p_len < 0 || p_len >= sizeof(buf2)) {
error("Header field 'Forwarded' too long");
return -1;
}
http_remove_header_field(&req->hdr, "Forwarded", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "Forwarded", buf2);
}
const char *xff = http_get_header_field(&req->hdr, "X-Forwarded-For");
if (xff == NULL) {
http_add_header_field(&req->hdr, "X-Forwarded-For", sock->addr);
} else {
sprintf(buf1, "%s, %s", xff, sock->addr);
http_remove_header_field(&req->hdr, "X-Forwarded-For", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "X-Forwarded-For", buf1);
}
const char *xfh = http_get_header_field(&req->hdr, "X-Forwarded-Host");
forwarded = http_get_header_field(&req->hdr, "Forwarded");
if (xfh == NULL) {
if (forwarded == NULL) {
http_add_header_field(&req->hdr, "X-Forwarded-Host", http_get_header_field(&req->hdr, "Host"));
} else {
char *ptr = strchr(forwarded, ',');
unsigned long len;
if (ptr != NULL) len = ptr - forwarded;
else len = strlen(forwarded);
ptr = strstr(forwarded, "host=");
if ((ptr - forwarded) < len) {
char *end = strchr(ptr, ';');
if (end == NULL) len -= (ptr - forwarded);
else len = (end - ptr);
len -= 5;
sprintf(buf1, "%.*s", (int) len, ptr + 5);
http_add_header_field(&req->hdr, "X-Forwarded-Host", buf1);
}
}
}
const char *xfp = http_get_header_field(&req->hdr, "X-Forwarded-Proto");
forwarded = http_get_header_field(&req->hdr, "Forwarded");
if (xfp == NULL) {
if (forwarded == NULL) {
http_add_header_field(&req->hdr, "X-Forwarded-Proto", sock->enc ? "https" : "http");
} else {
char *ptr = strchr(forwarded, ',');
unsigned long len;
if (ptr != NULL) len = ptr - forwarded;
else len = strlen(forwarded);
ptr = strstr(forwarded, "proto=");
if ((ptr - forwarded) < len) {
char *end = strchr(ptr, ';');
if (end == NULL) len -= (ptr - forwarded);
else len = (end - ptr);
len -= 6;
sprintf(buf1, "%.*s", (int) len, ptr + 6);
http_add_header_field(&req->hdr, "X-Forwarded-Proto", buf1);
}
}
}
return 0;
}
int proxy_response_header(http_req *req, http_res *res, host_config_t *conf) {
char buf1[256], buf2[256];
int p_len;
const char *via = http_get_header_field(&res->hdr, "Via");
p_len = snprintf(buf1, sizeof(buf1), "HTTP/%s %s", req->version, SERVER_NAME);
if (p_len < 0 || p_len >= sizeof(buf1)) {
error("Appended part of header field 'Via' too long");
return -1;
}
if (via == NULL) {
http_add_header_field(&res->hdr, "Via", buf1);
} else {
p_len = snprintf(buf2, sizeof(buf2), "%s, %s", via, buf1);
if (p_len < 0 || p_len >= sizeof(buf2)) {
error("Header field 'Via' too long");
return -1;
}
http_remove_header_field(&res->hdr, "Via", HTTP_REMOVE_ALL);
http_add_header_field(&res->hdr, "Via", buf2);
}
const char *location = http_get_header_field(&res->hdr, "Location");
if (location != NULL) {
char *hostnames[] = {conf->name, conf->proxy.hostname};
int found = 0;
for (int i = 0; i < sizeof(hostnames) / sizeof(hostnames[0]); i++) {
char *hostname = hostnames[i];
found = 1;
p_len = snprintf(buf1, sizeof(buf1), "http://%s/", hostname);
if (strstarts(location, buf1)) break;
p_len = snprintf(buf1, sizeof(buf1), "https://%s/", hostname);
if (strstarts(location, buf1)) break;
p_len = snprintf(buf1, sizeof(buf1), "http://%s:%i/", hostname, conf->proxy.port);
if (strstarts(location, buf1)) break;
p_len = snprintf(buf1, sizeof(buf1), "https://%s:%i/", hostname, conf->proxy.port);
if (strstarts(location, buf1)) break;
found = 0;
}
if (found) {
strcpy(buf1, location + p_len - 1);
http_remove_header_field(&res->hdr, "Location", HTTP_REMOVE_ALL);
http_add_header_field(&res->hdr, "Location", buf1);
}
}
return 0;
}
static int proxy_connect(proxy_ctx_t *proxy, host_config_t *conf, http_res *res, http_status_ctx *ctx, char *err_msg) {
char err_buf[256], addr_buf[1024];
info(BLUE_STR "Connecting to " BLD_STR "[%s]:%i" CLR_STR BLUE_STR "...", conf->proxy.hostname, conf->proxy.port);
int fd;
if ((fd = sock_connect(conf->proxy.hostname, conf->proxy.port, SERVER_SOCKET_TIMEOUT_INIT, addr_buf, sizeof(addr_buf))) == -1) {
if (errno == ETIMEDOUT || errno == EINPROGRESS || errno == EHOSTDOWN || errno == EHOSTUNREACH) {
res->status = http_get_status(504);
ctx->origin = SERVER_REQ;
} else if (errno == ECONNREFUSED) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
} else if (errno == ECONNABORTED || errno == ECONNRESET) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
} else {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
}
error("Unable to connect to [%s]:%i", addr_buf, conf->proxy.port);
sprintf(err_msg, "Unable to connect to server: %s.", error_str(errno, err_buf, sizeof(err_buf)));
return -1;
}
sock_init(&proxy->proxy, fd, 0);
if (sock_set_timeout(&proxy->proxy, SERVER_TIMEOUT) != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
error("Unable to set timeout for reverse proxy socket");
sprintf(err_msg, "Unable to set timeout for reverse proxy socket: %s", error_str(errno, err_buf, sizeof(err_buf)));
return -1;
}
if (conf->proxy.enc) {
proxy->proxy.ssl = SSL_new(proxy_ctx);
SSL_set_fd(proxy->proxy.ssl, proxy->proxy.socket);
SSL_set_connect_state(proxy->proxy.ssl);
int ret;
if ((ret = SSL_do_handshake(proxy->proxy.ssl)) != 1) {
sock_error(&proxy->proxy, (int) ret);
SSL_free(proxy->proxy.ssl);
proxy->proxy.ssl = NULL;
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
error("Unable to perform handshake");
sprintf(err_msg, "Unable to perform handshake: %s.", error_str(errno, err_buf, sizeof(err_buf)));
return -1;
}
proxy->proxy.enc = 1;
}
proxy->initialized = 1;
proxy->cnx_s = clock_micros();
proxy->host = conf->name;
proxy->http_timeout = 0;
info(BLUE_STR "Established new connection with " BLD_STR "[%s]:%i" CLR_STR BLUE_STR " (slot %i/%i)",
addr_buf, conf->proxy.port, (proxy - proxies) % MAX_PROXY_CNX_PER_HOST, MAX_PROXY_CNX_PER_HOST);
return 0;
}
int proxy_init(proxy_ctx_t **proxy_ptr, http_req *req, http_res *res, http_status_ctx *ctx, host_config_t *conf, sock *client, http_status *custom_status, char *err_msg) {
char buffer[CHUNK_SIZE], err_buf[256];
long ret;
*proxy_ptr = proxy_get_by_conf(conf);
proxy_ctx_t *proxy = *proxy_ptr;
proxy->client = NULL;
debug("Selected proxy connection slot %i/%i", (proxy - proxies) % MAX_PROXY_CNX_PER_HOST, MAX_PROXY_CNX_PER_HOST);
const char *connection = http_get_header_field(&req->hdr, "Connection");
if (strcontains(connection, "upgrade") || strcontains(connection, "Upgrade")) {
const char *upgrade = http_get_header_field(&req->hdr, "Upgrade");
const char *ws_version = http_get_header_field(&req->hdr, "Sec-WebSocket-Version");
if (streq(upgrade, "websocket") && streq(ws_version, "13")) {
ctx->ws_key = http_get_header_field(&req->hdr, "Sec-WebSocket-Key");
} else {
res->status = http_get_status(501);
ctx->origin = INTERNAL;
return -1;
}
} else {
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
http_add_header_field(&req->hdr, "Connection", "keep-alive");
}
ret = proxy_request_header(req, client);
if (ret != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
return -1;
}
for (int retry = 1, srv_error = 0, tries = 0;; tries++) {
errno = 0;
if (!retry)
return -1;
// honor server timeout with one second buffer
if (!proxy->initialized || srv_error ||
(proxy->http_timeout > 0 && (clock_micros() - proxy->proxy.ts_last_send) >= proxy->http_timeout) ||
sock_has_pending(&proxy->proxy, SOCK_DONTWAIT))
{
if (proxy->initialized)
proxy_close(proxy);
retry = 0;
srv_error = 0;
tries++;
if (proxy_connect(proxy, conf, res, ctx, err_msg) != 0)
continue;
}
ret = http_send_request(&proxy->proxy, req);
if (ret < 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
error("Unable to send request to server (1)");
sprintf(err_msg, "Unable to send request to server: %s.", error_str(errno, err_buf, sizeof(err_buf)));
retry = tries < 4;
srv_error = 1;
continue;
}
break;
}
const char *client_expect = http_get_header_field(&req->hdr, "Expect");
int expect_100_continue = (client_expect != NULL && strcasecmp(client_expect, "100-continue") == 0);
int ignore_content = 0;
if (expect_100_continue) {
http_res tmp_res = {
.version = "1.1",
.status = http_get_status(501),
};
if (http_init_hdr(&tmp_res.hdr) != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
error("Unable to initialize http header");
return -1;
}
ret = proxy_peek_response(proxy, &tmp_res, ctx, custom_status, err_msg);
http_free_hdr(&tmp_res.hdr);
if (ret < 0)
return (int) ret;
if (tmp_res.status->code == 100) {
if (sock_recv_x(&proxy->proxy, buffer, ret, 0) == -1) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
error("Unable to receive from server");
return -1;
}
info("%s -> %03i %s%s", http_get_status_color(tmp_res.status->code), tmp_res.status->code, tmp_res.status->msg, CLR_STR);
if (http_send_response(client, &tmp_res) != 0) {
res->status = http_get_status(400);
ctx->origin = CLIENT_RES;
error("Unable to send to client");
return -1;
}
} else {
ignore_content = 1;
}
}
if (!ignore_content) {
const char *content_length = http_get_header_field(&req->hdr, "Content-Length");
unsigned long content_len = content_length != NULL ? strtoul(content_length, NULL, 10) : 0;
const char *transfer_encoding = http_get_header_field(&req->hdr, "Transfer-Encoding");
ret = 0;
if (content_len > 0) {
ret = sock_splice(&proxy->proxy, client, buffer, sizeof(buffer), content_len);
} else if (strcontains(transfer_encoding, "chunked")) {
ret = sock_splice_chunked(&proxy->proxy, client, buffer, sizeof(buffer), SOCK_CHUNKED);
}
if (ret < 0 || (content_len != 0 && ret != content_len)) {
if (ret == -1 && errno != EPROTO) {
res->status = http_get_status(502);
ctx->origin = SERVER_REQ;
error("Unable to send request to server (2)");
sprintf(err_msg, "Unable to send request to server: %s.", error_str(errno, err_buf, sizeof(err_buf)));
return -1;
} else if (ret == -1) {
res->status = http_get_status(400);
ctx->origin = CLIENT_REQ;
error("Unable to receive request from client");
sprintf(err_msg, "Unable to receive request from client: %s.", error_str(errno, err_buf, sizeof(err_buf)));
return -1;
}
res->status = http_get_status(500);
ctx->origin = INTERNAL;
error("Unknown Error");
return -1;
}
}
if (sock_set_socket_timeout(&proxy->proxy, SERVER_SOCKET_TIMEOUT_RES) != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
error("Unable to set timeout for reverse proxy socket");
return -1;
}
while (1) {
ret = proxy_peek_response(proxy, res, ctx, custom_status, err_msg);
if (ret < 0) {
return (int) ret;
} else if (sock_recv_x(&proxy->proxy, buffer, ret, 0) == -1) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
error("Unable to receive from server");
return -1;
}
if (res->status->code == 100) {
info("%s -> %03i %s%s", http_get_status_color(res->status->code), res->status->code, res->status->msg, CLR_STR);
if (http_send_response(client, res) != 0) {
res->status = http_get_status(400);
ctx->origin = CLIENT_RES;
error("Unable to send to client");
return -1;
}
} else {
break;
}
}
long keep_alive_timeout = http_get_keep_alive_timeout(&res->hdr);
proxy->http_timeout = (keep_alive_timeout > 0) ? keep_alive_timeout * 1000000 : 0;
connection = http_get_header_field(&res->hdr, "Connection");
proxy->close = !streq(res->version, "1.1") || strcontains(connection, "close") || strcontains(connection, "Close");
ret = proxy_response_header(req, res, conf);
if (ret != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
return -1;
}
return 0;
}
int proxy_peek_response(proxy_ctx_t *proxy, http_res *res, http_status_ctx *ctx, http_status *custom_status, char *err_msg) {
char buffer[CHUNK_SIZE], err_buf[256];
long ret;
ret = sock_recv(&proxy->proxy, buffer, sizeof(buffer) - 1, MSG_PEEK);
if (ret <= 0) {
int e_sys = error_get_sys(), e_ssl = error_get_ssl();
if (e_sys == EAGAIN || e_sys == EINPROGRESS || e_ssl == SSL_ERROR_WANT_READ || e_ssl == SSL_ERROR_WANT_WRITE) {
res->status = http_get_status(504);
ctx->origin = SERVER_RES;
} else {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
}
error("Unable to receive response from server");
sprintf(err_msg, "Unable to receive response from server: %s.", error_str(errno, err_buf, sizeof(err_buf)));
return -1;
}
buffer[ret] = 0;
if (sock_set_socket_timeout(&proxy->proxy, SOCKET_TIMEOUT) != 0) {
res->status = http_get_status(500);
ctx->origin = INTERNAL;
error("Unable to set timeout for reverse proxy socket");
return -1;
}
char *buf = buffer;
unsigned short header_len = (unsigned short) (strstr(buffer, "\r\n\r\n") - buffer + 4);
if (header_len <= 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
error("Unable to parse header: End of header not found");
sprintf(err_msg, "Unable to parser header: End of header not found.");
return -2;
}
for (int i = 0; i < header_len; i++) {
if ((buf[i] >= 0x00 && buf[i] <= 0x1F && buf[i] != '\r' && buf[i] != '\n') || buf[i] == 0x7F) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
error("Unable to parse header: Header contains illegal characters");
sprintf(err_msg, "Unable to parse header: Header contains illegal characters.");
return -2;
}
}
char *ptr = buf;
while (header_len != (ptr - buf)) {
char *pos0 = strstr(ptr, "\r\n");
if (pos0 == NULL) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
error("Unable to parse header: Invalid header format");
sprintf(err_msg, "Unable to parse header: Invalid header format.");
return -2;
}
if (ptr == buf) {
if (!strstarts(ptr, "HTTP/")) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
error("Unable to parse header: Invalid header format");
sprintf(err_msg, "Unable to parse header: Invalid header format.");
return -2;
}
int status_code = (int) strtol(ptr + 9, NULL, 10);
res->status = http_get_status(status_code);
if (res->status == NULL && status_code >= 100 && status_code <= 999) {
custom_status->code = status_code;
custom_status->type = 0;
snprintf(custom_status->msg, sizeof(custom_status->msg), "%.*s",
(int) (strchr(ptr, '\r') - ptr - 13), ptr + 13);
res->status = custom_status;
} else if (res->status == NULL) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
error("Unable to parse header: Invalid or unknown status code");
sprintf(err_msg, "Unable to parse header: Invalid or unknown status code.");
return -2;
}
} else {
if (http_parse_header_field(&res->hdr, ptr, pos0, 0) != 0) {
res->status = http_get_status(502);
ctx->origin = SERVER_RES;
error("Unable to parse header");
sprintf(err_msg, "Unable to parse header.");
return -2;
}
}
if (pos0[2] == '\r' && pos0[3] == '\n') {
break;
}
ptr = pos0 + 2;
}
return header_len;
}
int proxy_send(proxy_ctx_t *proxy, sock *client, unsigned long len_to_send, int flags) {
char buffer[CHUNK_SIZE];
if (sock_splice(client, &proxy->proxy, buffer, sizeof(buffer), len_to_send) == -1)
return -1;
return 0;
}
int proxy_dump(proxy_ctx_t *proxy, char *buf, long len) {
long ret = sock_recv(&proxy->proxy, buf, len, 0);
if (ret == -1) return -1;
buf[ret] = 0;
return 0;
}
void proxy_close(proxy_ctx_t *ctx) {
client_ctx_t *cctx = ctx->client;
if (cctx) {
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, cctx->req_host, CLR_STR, cctx->log_prefix);
}
if (ctx->initialized) {
ctx->cnx_e = clock_micros();
char buf[32];
info(BLUE_STR "Closing proxy connection %i/%i (%s)",
(ctx - proxies) % MAX_PROXY_CNX_PER_HOST, MAX_PROXY_CNX_PER_HOST,
format_duration(ctx->cnx_e - ctx->cnx_s, buf));
}
sock_close(&ctx->proxy);
ctx->initialized = 0;
ctx->http_timeout = 0;
ctx->cnx_e = 0, ctx->cnx_s = 0;
ctx->client = NULL;
ctx->host = NULL;
errno = 0;
}

54
src/lib/proxy.h Normal file
View File

@ -0,0 +1,54 @@
/**
* sesimos - secure, simple, modern web server
* @brief Reverse proxy (header file)
* @file src/lib/proxy.h
* @author Lorenz Stechauner
* @date 2021-01-07
*/
#ifndef SESIMOS_PROXY_H
#define SESIMOS_PROXY_H
#define PROXY_CHUNKED 1
#ifndef SERVER_NAME
# define SERVER_NAME "reverse proxy"
#endif
#include "http.h"
#include "config.h"
typedef struct {
volatile unsigned char initialized:1, in_use:1, close:1;
sock proxy;
long cnx_s, cnx_e;
long http_timeout;
char *host;
void *client;
} proxy_ctx_t;
int proxy_preload(void);
void proxy_unload(void);
void proxy_close_all(void);
proxy_ctx_t *proxy_get_by_conf(host_config_t *conf);
int proxy_unlock_ctx(proxy_ctx_t *ctx);
int proxy_request_header(http_req *req, sock *sock);
int proxy_response_header(http_req *req, http_res *res, host_config_t *conf);
int proxy_init(proxy_ctx_t **proxy, http_req *req, http_res *res, http_status_ctx *ctx, host_config_t *conf, sock *client, http_status *custom_status, char *err_msg);
int proxy_peek_response(proxy_ctx_t *proxy, http_res *res, http_status_ctx *ctx, http_status *custom_status, char *err_msg);
int proxy_send(proxy_ctx_t *proxy, sock *client, unsigned long len_to_send, int flags);
int proxy_dump(proxy_ctx_t *proxy, char *buf, long len);
void proxy_close(proxy_ctx_t *ctx);
#endif //SESIMOS_PROXY_H

62
src/lib/res.h Normal file
View File

@ -0,0 +1,62 @@
/**
* sesimos - secure, simple, modern web server
* @brief HTTP resources (header file)
* @file src/lib/res.h
* @author Lorenz Stechauner
* @date 2022-12-31
*/
#ifndef SESIMOS_RES_H
#define SESIMOS_RES_H
#define http_default_doc _binary_bin_res_default_txt_start
#define http_default_doc_size ((unsigned int) (_binary_bin_res_default_txt_end - _binary_bin_res_default_txt_start) - 1)
#define http_proxy_doc _binary_bin_res_proxy_txt_start
#define http_proxy_doc_size ((unsigned int) (_binary_bin_res_proxy_txt_end - _binary_bin_res_proxy_txt_start) - 1)
#define http_style_doc _binary_bin_res_style_txt_start
#define http_style_doc_size ((unsigned int) (_binary_bin_res_style_txt_end - _binary_bin_res_style_txt_start) - 1)
#define http_icon_error _binary_bin_res_icon_error_txt_start
#define http_icon_error_size ((unsigned int) (_binary_bin_res_icon_error_txt_end - _binary_bin_res_icon_error_txt_start) - 1)
#define http_icon_info _binary_bin_res_icon_info_txt_start
#define http_icon_info_size ((unsigned int) (_binary_bin_res_icon_info_txt_end - _binary_bin_res_icon_info_txt_start) - 1)
#define http_icon_success _binary_bin_res_icon_success_txt_start
#define http_icon_success_size ((unsigned int) (_binary_bin_res_icon_success_txt_end - _binary_bin_res_icon_success_txt_start) - 1)
#define http_icon_warning _binary_bin_res_icon_warning_txt_start
#define http_icon_warning_size ((unsigned int) (_binary_bin_res_icon_warning_txt_end - _binary_bin_res_icon_warning_txt_start) - 1)
#define http_icon_globe _binary_bin_res_globe_txt_start
#define http_icon_globe_size ((unsigned int) (_binary_bin_res_globe_txt_end - _binary_bin_res_globe_txt_start) - 1)
typedef struct {
const char *name;
const char *type;
const char *content;
const unsigned int size;
} res_t;
extern const char _binary_bin_res_default_txt_start[];
extern const char _binary_bin_res_default_txt_end[];
extern const char _binary_bin_res_proxy_txt_start[];
extern const char _binary_bin_res_proxy_txt_end[];
extern const char _binary_bin_res_style_txt_start[];
extern const char _binary_bin_res_style_txt_end[];
extern const char _binary_bin_res_icon_error_txt_start[];
extern const char _binary_bin_res_icon_error_txt_end[];
extern const char _binary_bin_res_icon_info_txt_start[];
extern const char _binary_bin_res_icon_info_txt_end[];
extern const char _binary_bin_res_icon_success_txt_start[];
extern const char _binary_bin_res_icon_success_txt_end[];
extern const char _binary_bin_res_icon_warning_txt_start[];
extern const char _binary_bin_res_icon_warning_txt_end[];
extern const char _binary_bin_res_globe_txt_start[];
extern const char _binary_bin_res_globe_txt_end[];
#endif //SESIMOS_RES_H

View File

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

View File

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

View File

@ -1,33 +1,55 @@
/**
* Necronda Web Server
* Basic TCP and TLS socket
* src/lib/sock.c
* Lorenz Stechauner, 2021-01-07
* sesimos - secure, simple, modern web server
* @brief Basic TCP and TLS socket
* @file src/lib/sock.c
* @author Lorenz Stechauner
* @date 2021-01-07
*/
#include "sock.h"
#include <openssl/err.h>
#include "utils.h"
#include "error.h"
#include "../logger.h"
#include <errno.h>
#include <openssl/ssl.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <openssl/err.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <netdb.h>
int sock_enc_error(sock *s) {
return (int) s->enc ? SSL_get_error(s->ssl, (int) s->_last_ret) : 0;
static void sock_ssl_error(unsigned long err) {
if (err == SSL_ERROR_NONE) {
errno = 0;
} else if (err == SSL_ERROR_SYSCALL) {
// errno already set
} else if (err == SSL_ERROR_SSL) {
error_ssl_err(ERR_get_error());
} else {
error_ssl(err);
}
}
const char *sock_strerror(sock *s) {
if (s->_last_ret == 0) {
return "closed";
} else if (s->enc) {
if (s->_last_ret > 0) {
return NULL;
void sock_error(sock *s, int ret) {
sock_ssl_error(SSL_get_error(s->ssl, ret));
}
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;
int sock_gai_error(int ret) {
if (ret == 0) {
errno = 0;
} else if (ret == EAI_SYSTEM) {
// errno already set
} else {
error_gai(ret);
}
return -1;
}
const char *sock_error_str(unsigned long err) {
switch (err) {
case SSL_ERROR_ZERO_RETURN:
return "closed";
case SSL_ERROR_WANT_READ:
@ -40,79 +62,425 @@ const char *sock_strerror(sock *s) {
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;
case SSL_ERROR_WANT_ASYNC:
return "want async";
case SSL_ERROR_WANT_ASYNC_JOB:
return "want async job";
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
return "want client hello callback";
//case SSL_ERROR_WANT_RETRY_VERIFY:
// return "want retry verify";
default:
return "unknown error";
}
} else {
return strerror(s->_errno);
}
int sock_init(sock *s, int fd, int flags) {
if ((flags & SOCK_ENCRYPTED) && (flags & SOCK_PIPE)) {
errno = EINVAL;
return -1;
}
s->socket = fd;
s->enc = !!(flags & SOCK_ENCRYPTED);
s->pipe = !!(flags & SOCK_PIPE);
s->ts_start = clock_micros();
s->ts_last = s->ts_start;
s->ts_last_send = s->ts_last;
s->timeout_us = -1;
s->ssl = NULL;
s->addr = NULL;
s->s_addr = NULL;
return 0;
}
int sock_connect(const char *hostname, unsigned short port, double timeout_sec, char *addr_buf, size_t addr_buf_size) {
char buf[INET6_ADDRSTRLEN + 1];
int ret, fd, e = 0;
long timeout_micros = (long) (timeout_sec * 1000000L);
struct addrinfo *result, *rp,
hints = {
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
.ai_protocol = 0,
.ai_flags = 0,
};
if (addr_buf && addr_buf_size > 1)
addr_buf[0] = 0;
if ((ret = getaddrinfo(hostname, NULL, &hints, &result)) != 0)
return sock_gai_error(ret);
for (rp = result; rp != NULL; rp = rp->ai_next) {
switch (rp->ai_family) {
case AF_INET:
((struct sockaddr_in *) rp->ai_addr)->sin_port = htons(port);
inet_ntop(rp->ai_family, &((struct sockaddr_in *) rp->ai_addr)->sin_addr, buf, addr_buf_size);
break;
case AF_INET6:
((struct sockaddr_in6 *) rp->ai_addr)->sin6_port = htons(port);
inet_ntop(rp->ai_family, &((struct sockaddr_in6 *) rp->ai_addr)->sin6_addr, buf, addr_buf_size);
break;
}
debug("Trying [%s]:%i", buf, port);
if ((fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1) {
if (e == 0) {
e = errno;
} else if (e != errno) {
e = -1;
}
continue;
}
if (sock_set_socket_timeout_micros(fd, timeout_micros, timeout_micros) == -1) {
close(fd);
return -1;
}
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == -1) {
e = errno;
close(fd);
continue;
}
break;
}
freeaddrinfo(result);
if (addr_buf && addr_buf_size > 1 && addr_buf[0] == 0)
strncpy(addr_buf, buf, addr_buf_size);
errno = e;
return (e == 0) ? fd : -1;
}
int sock_reverse_lookup(const sock *s, char *host, size_t host_size) {
memset(host, 0, host_size);
int ret;
if ((ret = getnameinfo(&s->_addr.sock, sizeof(s->_addr), host, host_size, NULL, 0, 0)) != 0) {
if (ret == EAI_NONAME) {
return 0;
} else {
return sock_gai_error(ret);
}
}
return 0;
}
int sock_set_socket_timeout_micros(int fd, long recv_micros, long send_micros) {
struct timeval recv_to = {.tv_sec = recv_micros / 1000000, .tv_usec = recv_micros % 1000000},
send_to = {.tv_sec = send_micros / 1000000, .tv_usec = send_micros % 1000000};
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &recv_to, sizeof(recv_to)) != 0)
return -1;
if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &send_to, sizeof(send_to)) != 0)
return -1;
return 0;
}
int sock_set_socket_timeout(sock *s, double sec) {
return sock_set_socket_timeout_micros(s->socket, (long) (sec * 1000000L), (long) (sec * 1000000L));
}
int sock_set_timeout_micros(sock *s, long micros) {
if (micros < 0)
return -1;
s->timeout_us = micros;
return 0;
}
int sock_set_timeout(sock *s, double sec) {
return sock_set_timeout_micros(s, (long) (sec * 1000000));
}
long sock_send(sock *s, void *buf, unsigned long len, int flags) {
if (s->socket < 0) {
errno = ENOTCONN;
return -1;
}
long ret;
if (s->enc) {
ret = SSL_write(s->ssl, buf, (int) len);
s->_ssl_error = ERR_get_error();
if (ret <= 0) sock_error(s, (int) ret);
} else if (s->pipe) {
if (flags & ~MSG_MORE) {
errno = EINVAL;
return -1;
}
ret = write(s->socket, buf, len);
} else {
ret = send(s->socket, buf, len, flags);
}
s->_last_ret = ret;
s->_errno = errno;
return ret >= 0 ? ret : -1;
if (ret >= 0) {
s->ts_last = clock_micros();
s->ts_last_send = s->ts_last;
return ret;
} else {
return -1;
}
}
long sock_send_x(sock *s, void *buf, unsigned long len, int flags) {
for (long ret, sent = 0; sent < len; sent += ret) {
if ((ret = sock_send(s, (unsigned char *) buf + sent, len - sent, flags)) <= 0) {
if (errno == EINTR || errno == EAGAIN) {
errno = 0, ret = 0;
continue;
} else {
return -1;
}
}
}
return (long) len;
}
long sock_recv(sock *s, void *buf, unsigned long len, int flags) {
if (s->socket < 0) {
errno = ENOTCONN;
return -1;
}
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);
int (*func)(SSL *, void *, int) = (flags & MSG_PEEK) ? SSL_peek : SSL_read;
ret = func(s->ssl, buf, (int) len);
if (ret <= 0) sock_error(s, (int) ret);
} else if (s->pipe) {
if (flags & ~MSG_WAITALL) {
errno = EINVAL;
return -1;
}
s->_ssl_error = ERR_get_error();
ret = read(s->socket, buf, len);
} else {
ret = recv(s->socket, buf, len, flags);
}
s->_last_ret = ret;
s->_errno = errno;
return ret >= 0 ? ret : -1;
if (ret >= 0) {
s->ts_last = clock_micros();
return ret;
} else {
return -1;
}
}
long sock_recv_x(sock *s, void *buf, unsigned long len, int flags) {
for (long ret, rcv = 0; rcv < len; rcv += ret) {
if ((ret = sock_recv(s, (unsigned char *) buf + rcv, len - rcv, flags | MSG_WAITALL)) <= 0) {
if (errno == EINTR || errno == EAGAIN) {
errno = 0, ret = 0;
continue;
} else {
return -1;
}
}
}
return (long) len;
}
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len) {
long 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;
long send_len = 0;
if ((src->pipe || dst->pipe) && !src->enc && !dst->enc) {
for (long ret; send_len < len; send_len += ret) {
if ((ret = splice(src->socket, 0, dst->socket, 0, len, 0)) == -1) {
if (errno == EINTR || errno == EAGAIN) {
errno = 0, ret = 0;
continue;
} else {
return -1;
}
}
}
} else {
for (long ret, next_len; send_len < len; send_len += ret) {
next_len = (long) ((buf_len < (len - send_len)) ? buf_len : (len - send_len));
if ((ret = sock_recv(src, buf, next_len, MSG_WAITALL)) <= 0) {
if (errno == EINTR || errno == EAGAIN) {
errno = 0, ret = 0;
continue;
} else {
return -1;
}
}
if (sock_send_x(dst, buf, ret, send_len + ret < len ? MSG_MORE : 0) == -1)
return -1;
}
}
return send_len;
}
long sock_splice_all(sock *dst, sock *src, void *buf, unsigned long buf_len) {
long send_len = 0;
for (long ret;; send_len += ret) {
if ((ret = sock_recv(src, buf, buf_len, 0)) <= 0) {
if (errno == EINTR || errno == EAGAIN) {
errno = 0, ret = 0;
continue;
} else if (ret == 0) {
break;
} else {
return -1;
}
}
if (sock_send_x(dst, buf, ret, 0) == -1)
return -1;
}
return send_len;
}
long sock_splice_chunked(sock *dst, sock *src, void *buf, unsigned long buf_len, int flags) {
long ret;
unsigned long send_len = 0, next_len;
do {
if ((ret = sock_recv_chunk_header(src)) == -1)
return -1;
next_len = ret;
if (flags & SOCK_CHUNKED)
if (sock_send_chunk_header(dst, next_len) == -1)
return -1;
if ((ret = sock_splice(dst, src, buf, buf_len, next_len)) == -1)
return ret;
send_len += ret;
if (sock_recv_chunk_trailer(src) == -1)
return -1;
if (flags & SOCK_CHUNKED)
if (sock_send_chunk_trailer(dst) == -1)
return -1;
} while (!(flags & SOCK_SINGLE_CHUNK) && next_len != 0);
return (long) send_len;
}
int sock_close(sock *s) {
if ((int) s->enc && s->ssl != NULL) {
if (s->_last_ret >= 0) SSL_shutdown(s->ssl);
int e = errno;
if (s->ssl != NULL) {
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;
if (s->socket != -1) close(s->socket);
s->socket = -1;
s->enc = 0, s->pipe = 0;
errno = e;
return 0;
}
int sock_check(sock *s) {
char buf;
return recv(s->socket, &buf, 1, MSG_PEEK | MSG_DONTWAIT) == 1;
int sock_has_pending(sock *s, int flags) {
int e = errno;
long ret;
if (s->pipe) {
int arg;
ioctl(s->socket, FIONREAD, &arg);
ret = arg;
} else if (s->enc && (flags & SOCK_DONTWAIT)) {
ret = SSL_pending(s->ssl);
} else {
char buf[1];
ret = sock_recv(s, &buf, sizeof(buf), MSG_PEEK | ((flags & SOCK_DONTWAIT) ? MSG_DONTWAIT : 0));
}
errno = e;
return ret > 0;
}
long sock_recv_chunk_header(sock *s) {
if (s->pipe) {
uint64_t len;
if (sock_recv_x(s, &len, sizeof(len), 0) == -1)
return -1;
return (long) len;
}
long ret;
size_t len = 0;
char buf[20];
do {
if ((ret = sock_recv(s, buf, sizeof(buf) - 1, MSG_PEEK)) <= 0) {
if (errno == EINTR || errno == EAGAIN) {
errno = 0;
continue;
} else {
return -1;
}
} else if (ret < 2) {
continue;
}
buf[ret] = 0;
if ((ret = parse_chunk_header(buf, ret, &len)) == -1 && errno == EPROTO)
return -1;
} while (ret < 0);
if (sock_recv_x(s, buf, len, 0) == -1)
return -1;
return ret;
}
int sock_send_chunk_header(sock *s, unsigned long size) {
if (s->pipe) {
uint64_t len = size;
if (sock_send_x(s, &len, sizeof(len), 0) == -1)
return -1;
} else {
char buf[20];
if (sock_send_x(s, buf, sprintf(buf, "%lX\r\n", size), 0) == -1)
return -1;
}
return 0;
}
int sock_recv_chunk_trailer(sock *s) {
if (s->pipe) return 0;
char buf[2];
if (sock_recv_x(s, buf, sizeof(buf), MSG_PEEK) == -1)
return -1;
if (buf[0] != '\r' || buf[1] != '\n') {
errno = EPROTO;
return -1;
}
if (sock_recv_x(s, buf, sizeof(buf), 0) == -1)
return -1;
return 0;
}
int sock_send_chunk_trailer(sock *s) {
if (s->pipe) return 0;
if (sock_send_x(s, "\r\n", 2, 0) == -1)
return -1;
return 0;
}
int sock_send_last_chunk(sock *s) {
if (s->pipe) return sock_send_chunk_header(s, 0);
if (sock_send_x(s, "0\r\n\r\n", 5, 0) == -1)
return -1;
return 0;
}

View File

@ -1,40 +1,85 @@
/**
* Necronda Web Server
* Basic TCP and TLS socket (header file)
* src/lib/sock.h
* Lorenz Stechauner, 2021-01-07
* sesimos - secure, simple, modern web server
* @brief Basic TCP and TLS socket (header file)
* @file src/lib/sock.h
* @author Lorenz Stechauner
* @date 2021-01-07
*/
#ifndef NECRONDA_SERVER_SOCK_H
#define NECRONDA_SERVER_SOCK_H
#ifndef SESIMOS_SOCK_H
#define SESIMOS_SOCK_H
#include <openssl/crypto.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SOCK_CHUNKED 1
#define SOCK_SINGLE_CHUNK 2
#define SOCK_ENCRYPTED 1
#define SOCK_PIPE 2
#define SOCK_DONTWAIT 1
typedef struct {
unsigned int enc:1;
unsigned int enc:1, pipe:1;
int socket;
union {
struct sockaddr sock;
struct sockaddr_in6 ipv6;
} _addr;
char *addr, *s_addr;
SSL_CTX *ctx;
SSL *ssl;
char *buf;
unsigned long buf_len;
unsigned long buf_off;
long _last_ret;
int _errno;
unsigned long _ssl_error;
long ts_start, ts_last, ts_last_send, timeout_us;
} sock;
int sock_enc_error(sock *s);
void sock_error(sock *s, int ret);
const char *sock_strerror(sock *s);
const char *sock_error_str(unsigned long err);
int sock_init(sock *s, int fd, int flags);
int sock_connect(const char *hostname, unsigned short port, double timeout_sec, char *addr_buf, size_t addr_buf_size);
int sock_reverse_lookup(const sock *s, char *host, size_t host_size);
int sock_init_addr_str(const sock *s, char *c_addr, size_t c_addr_size, char *s_addr, size_t s_addr_size);
int sock_set_socket_timeout_micros(int fd, long recv_micros, long send_micros);
int sock_set_socket_timeout(sock *s, double sec);
int sock_set_timeout_micros(sock *s, long micros);
int sock_set_timeout(sock *s, double sec);
long sock_send(sock *s, void *buf, unsigned long len, int flags);
long sock_send_x(sock *s, void *buf, unsigned long len, int flags);
long sock_recv(sock *s, void *buf, unsigned long len, int flags);
long sock_recv_x(sock *s, void *buf, unsigned long len, int flags);
long sock_splice(sock *dst, sock *src, void *buf, unsigned long buf_len, unsigned long len);
long sock_splice_all(sock *dst, sock *src, void *buf, unsigned long buf_len);
long sock_splice_chunked(sock *dst, sock *src, void *buf, unsigned long buf_len, int flags);
int sock_close(sock *s);
int sock_check(sock *s);
int sock_has_pending(sock *s, int flags);
#endif //NECRONDA_SERVER_SOCK_H
long sock_recv_chunk_header(sock *s);
int sock_send_chunk_header(sock *s, unsigned long size);
int sock_recv_chunk_trailer(sock *s);
int sock_send_chunk_trailer(sock *s);
int sock_send_last_chunk(sock *s);
#endif //SESIMOS_SOCK_H

View File

@ -1,37 +1,47 @@
/**
* Necronda Web Server
* URI and path handlers
* src/lib/uri.c
* Lorenz Stechauner, 2020-12-13
* sesimos - secure, simple, modern web server
* @brief URI and path handlers
* @file src/lib/uri.c
* @author Lorenz Stechauner
* @date 2020-12-13
*/
#include "uri.h"
#include "utils.h"
#include <errno.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 e = errno;
struct stat stat_buf;
int ret = stat(path, &stat_buf);
errno = e;
return ret == 0 && S_ISDIR(stat_buf.st_mode) != 0;
}
int path_is_file(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode) == 0;
int e = errno;
struct stat stat_buf;
int ret = stat(path, &stat_buf);
errno = e;
return ret == 0 && S_ISDIR(stat_buf.st_mode) == 0;
}
int path_exists(const char *path) {
struct stat statbuf;
return stat(path, &statbuf) == 0;
int e = errno;
struct stat stat_buf;
int ret = stat(path, &stat_buf);
errno = e;
return ret == 0;
}
int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mode) {
char buf0[1024];
char buf1[1024];
char buf2[1024];
char buf3[1024];
char buf4[1024];
char buf0[1024], buf1[1024], buf2[1024], buf3[1024], buf4[1024];
int p_len;
uri->webroot = NULL;
uri->req_path = NULL;
uri->path = NULL;
@ -42,9 +52,10 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
uri->meta = NULL;
uri->is_static = 1;
uri->is_dir = 0;
if (uri_str[0] != '/') {
if (uri_str[0] != '/')
return 1;
}
uri->webroot = malloc(strlen(webroot) + 1);
strcpy(uri->webroot, webroot);
@ -62,12 +73,10 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
long size = (long) strlen(uri_str) + 1;
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) {
if (query != NULL) query[-1] = '?';
if (strcontains(uri->req_path, "/../") || strcontains(uri->req_path, "/./"))
return 2;
}
size = (long) strlen(uri->req_path) + 1;
uri->path = malloc(size);
@ -83,9 +92,8 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
last = ch;
}
if (dir_mode == URI_DIR_MODE_NO_VALIDATION) {
if (dir_mode == URI_DIR_MODE_NO_VALIDATION)
return 0;
}
if (uri->path[strlen(uri->path) - 1] == '/') {
uri->path[strlen(uri->path) - 1] = 0;
@ -94,26 +102,20 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
strcpy(uri->pathinfo, "");
}
if (!path_exists(uri->webroot)) {
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);
p_len = snprintf(buf1, sizeof(buf1), "%s.xhtml", buf0);
if (p_len < 0 || p_len >= sizeof(buf1)) return -1;
p_len = snprintf(buf2, sizeof(buf2), "%s.php", buf0);
p_len = snprintf(buf2, sizeof(buf2), "%s.html", buf0);
if (p_len < 0 || p_len >= sizeof(buf2)) return -1;
p_len = snprintf(buf3, sizeof(buf3), "%s.html", buf0);
p_len = snprintf(buf3, sizeof(buf3), "%s.php", buf0);
if (p_len < 0 || p_len >= sizeof(buf3)) return -1;
if (strlen(uri->path) <= 1 ||
path_exists(buf0) ||
path_is_file(buf1) ||
path_is_file(buf2) ||
path_is_file(buf3)) {
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:
@ -132,40 +134,44 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
uri->filename = malloc(strlen(buf0) + 1);
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) {
if (strends(uri->path, ".xhtml")) {
uri->path[len - 6] = 0;
} else if (strends(uri->path, ".html")) {
uri->path[len - 5] = 0;
} else if (strends(uri->path, ".php")) {
uri->path[len - 4] = 0;
uri->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);
uri->is_static = 0;
} 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)) {
sprintf(buf1, "%s%s" "index.xhtml", uri->webroot, uri->path);
sprintf(buf2, "%s%s" "index.html", uri->webroot, uri->path);
sprintf(buf3, "%s%s" "index.php", uri->webroot, uri->path);
if (path_is_file(buf3) && uri->pathinfo[0] != 0) {
uri->filename = malloc(strlen(buf3) + 1);
strcpy(uri->filename, buf3);
uri->is_static = 0;
} else if (path_is_file(buf1)) {
uri->filename = malloc(strlen(buf1) + 1);
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);
uri->is_static = 0;
} else {
if (dir_mode == URI_DIR_MODE_FORBIDDEN) {
uri->is_static = 1;
@ -182,20 +188,17 @@ int uri_init(http_uri *uri, const char *webroot, const char *uri_str, int dir_mo
}
}
if (strcmp(uri->path + strlen(uri->path) - 5, "index") == 0) {
if (strends(uri->path, "/index"))
uri->path[strlen(uri->path) - 5] = 0;
}
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);
if (streq(uri->pathinfo, "index.php") || streq(uri->pathinfo, "index.html") || streq(uri->pathinfo, "index.xhtml"))
uri->pathinfo[0] = 0;
sprintf(buf4, "%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(buf4) + 1);
strcpy(uri->uri, buf4);
return 0;
}

View File

@ -1,12 +1,13 @@
/**
* Necronda Web Server
* URI and path handlers (header file)
* src/lib/uri.h
* Lorenz Stechauner, 2020-12-13
* sesimos - secure, simple, modern web server
* @brief URI and path handlers (header file)
* @file src/lib/uri.h
* @author Lorenz Stechauner
* @date 2020-12-13
*/
#ifndef NECRONDA_SERVER_URI_H
#define NECRONDA_SERVER_URI_H
#ifndef SESIMOS_URI_H
#define SESIMOS_URI_H
#include <sys/stat.h>
@ -15,14 +16,18 @@
#define URI_DIR_MODE_LIST 2
#define URI_DIR_MODE_INFO 3
#define URI_ETAG_SIZE 65 // SHA256 size (hex)
#define URI_TYPE_SIZE 64
#define URI_CHARSET_SIZE 16
typedef struct {
char etag[64];
char type[24];
char charset[16];
char etag[URI_ETAG_SIZE];
char type[URI_TYPE_SIZE];
char charset[URI_CHARSET_SIZE];
char filename_comp_gz[256];
char filename_comp_br[256];
struct stat stat;
} meta_data;
long mtime;
} metadata_t;
typedef struct {
char *webroot; // "/srv/www/www.test.org"
@ -32,9 +37,8 @@ typedef struct {
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;
metadata_t *meta;
unsigned int is_static:1, is_dir:1;
} http_uri;
@ -44,4 +48,4 @@ int uri_init_cache(http_uri *uri);
void uri_free(http_uri *uri);
#endif //NECRONDA_SERVER_URI_H
#endif //SESIMOS_URI_H

View File

@ -1,80 +1,94 @@
/**
* Necronda Web Server
* Utilities
* src/lib/utils.c
* Lorenz Stechauner, 2020-12-03
* sesimos - secure, simple, modern web server
* @brief Utilities
* @file src/lib/utils.c
* @author Lorenz Stechauner
* @date 2020-12-03
*/
#include "utils.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
static const char base64_encode_table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const int base64_mod_table[3] = {0, 2, 1};
char *log_prefix;
char *format_duration(unsigned long micros, char *buf) {
if (micros < 10000) {
sprintf(buf, "%.1f ms", (double) micros / 1000);
} else if (micros < 1000000) {
sprintf(buf, "%li ms", micros / 1000);
} else if (micros < 60000000) {
} else if (micros < 1000000 - 1000) {
sprintf(buf, "%.0f ms", (double) micros / 1000);
} else if (micros < 60000000 - 1000000) {
sprintf(buf, "%.1f s", (double) micros / 1000000);
} else if (micros < 6000000000) {
sprintf(buf, "%.1f min", (double) micros / 1000000 / 60);
sprintf(buf, "%li:%02li min", micros / 1000000 / 60, micros / 1000000 % 60);
} else {
sprintf(buf, "%li min", micros / 1000000 / 60);
sprintf(buf, "%.0f min", (double) 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_component(const void *in, size_t size_in, char *out, size_t size_out) {
int size = 0;
// Encode control characters
for (int i = 0; i < size_in; i++) {
unsigned char ch = ((unsigned char *) in)[i];
if (ch == ' ') {
ch = '+';
} else if (
ch <= 0x20 || ch >= 0x7F ||
!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') ||
ch == '-' || ch == '_' || ch == '.' || ch == '!' || ch == '~' || ch == '*' || ch == '\'' ||
ch == '(' || ch == ')')
) {
size += 3;
if (size < size_out) sprintf(out + size - 3, "%%%02X", ch);
ch = 0;
}
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;
if (ch != 0) {
size++;
if (size < size_out) out[size - 1] = (char) ch;
}
ch = str[i];
if (ch > 0x7F || ch == ' ') {
if ((ptr - enc + 2) >= *size) {
return -1;
}
sprintf(ptr, "%%%02X", ch);
ptr += 2;
// Set terminating null byte
if (size_out > 0) out[size < size_out ? size : size_out - 1] = 0;
// Return theoretical size
return size;
}
int url_encode(const void *in, size_t size_in, char *out, size_t size_out) {
int size = 0;
// Encode control characters
for (int i = 0; i < size_in; i++) {
unsigned char ch = ((unsigned char *) in)[i];
if (ch <= 0x20 || ch >= 0x7F) {
size += 3;
if (size < size_out) sprintf(out + size - 3, "%%%02X", ch);
} else {
ptr[0] = (char) ch;
size++;
if (size < size_out) out[size - 1] = (char) ch;
}
}
*size = ptr - enc;
return 0;
// Set terminating null byte
if (size_out > 0) out[size < size_out ? size : size_out - 1] = 0;
// Return theoretical size
return size;
}
int url_decode(const char *str, char *dec, long *size) {
@ -103,43 +117,285 @@ int url_decode(const char *str, char *dec, long *size) {
return 0;
}
int mime_is_compressible(const char *type) {
int mime_is_compressible(const char *restrict 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;
mime_is_text(type) ||
streq(type_parsed, "application/vnd.ms-fontobject") ||
streq(type_parsed, "application/x-font-ttf") ||
streq(type_parsed, "font/eot") ||
streq(type_parsed, "font/opentype") ||
streq(type_parsed, "image/bmp") ||
streq(type_parsed, "image/gif") ||
streq(type_parsed, "image/vnd.microsoft.icon") ||
streq(type_parsed, "image/vnd.microsoft.iconbinary") ||
streq(type_parsed, "image/x-icon");
}
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));
int mime_is_text(const char *restrict 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
strstarts(type_parsed, "text/") ||
strstarts(type_parsed, "message/") ||
strends(type_parsed, "+xml") ||
strends(type_parsed, "+json") ||
streq(type_parsed, "application/javascript") ||
streq(type_parsed, "application/json") ||
streq(type_parsed, "application/xml") ||
streq(type_parsed, "application/sql") ||
streq(type_parsed, "application/x-www-form-urlencoded") ||
streq(type_parsed, "application/x-tex") ||
streq(type_parsed, "application/x-httpd-php") ||
streq(type_parsed, "application/x-latex") ||
streq(type_parsed, "application/x-javascript");
}
int strcpy_rem_webroot(char *dst, const char *src, const char *webroot) {
strcpy(dst, src);
if (webroot == NULL)
return 0;
char *pos;
const unsigned long webroot_len = strlen(webroot);
if (webroot_len == 0)
return 0;
while ((pos = strstr(dst, webroot)) != NULL) {
strcpy(pos, pos + webroot_len);
}
return 0;
}
int str_trim(char **start, char **end) {
if (start == NULL || end == NULL || *start == NULL || *end == NULL)
return -1;
(*end)--;
while (*start[0] == ' ' || *start[0] == '\t' || *start[0] == '\r' || *start[0] == '\n') (*start)++;
while (*end[0] == ' ' || *end[0] == '\t' || *end[0] == '\r' || *end[0] == '\n') (*end)--;
(*end)++;
return 0;
}
int str_trim_lws(char **start, char **end) {
if (start == NULL || end == NULL || *start == NULL || *end == NULL)
return -1;
(*end)--;
while (*start[0] == ' ' || *start[0] == '\t') (*start)++;
while (*end[0] == ' ' || *end[0] == '\t') (*end)--;
(*end)++;
return 0;
}
int streq(const char *restrict str1, const char *restrict str2) {
return str1 != NULL && str2 != NULL && strcmp(str1, str2) == 0;
}
int strcontains(const char *restrict haystack, const char *restrict needle) {
return haystack != NULL && needle != NULL && strstr(haystack, needle) != NULL;
}
int strstarts(const char *restrict str, const char *restrict prefix) {
if (str == NULL || prefix == NULL) return 0;
unsigned long l1 = strlen(str), l2 = strlen(prefix);
return l2 <= l1 && strncmp(str, prefix, l2) == 0;
}
int strends(const char *restrict str, const char *restrict suffix) {
if (str == NULL || suffix == NULL) return 0;
unsigned long l1 = strlen(str), l2 = strlen(suffix);
return l2 <= l1 && strcmp(str + l1 - l2, suffix) == 0;
}
int base64_encode(void *data, unsigned long data_len, char *output, unsigned long *output_len) {
unsigned long out_len = 4 * ((data_len + 2) / 3);
if (output_len != NULL) *output_len = out_len;
for (int i = 0, j = 0; i < data_len;) {
unsigned int octet_a = (i < data_len) ? ((unsigned char *) data)[i++] : 0;
unsigned int octet_b = (i < data_len) ? ((unsigned char *) data)[i++] : 0;
unsigned int octet_c = (i < data_len) ? ((unsigned char *) data)[i++] : 0;
unsigned int triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
output[j++] = base64_encode_table[(triple >> 3 * 6) & 0x3F];
output[j++] = base64_encode_table[(triple >> 2 * 6) & 0x3F];
output[j++] = base64_encode_table[(triple >> 1 * 6) & 0x3F];
output[j++] = base64_encode_table[(triple >> 0 * 6) & 0x3F];
}
for (int i = 0; i < base64_mod_table[data_len % 3]; i++)
output[out_len - 1 - i] = '=';
output[out_len] = 0;
return 0;
}
long clock_micros(void) {
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
return time.tv_sec * 1000000 + time.tv_nsec / 1000;
}
long clock_cpu(void) {
struct timespec time;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time);
return time.tv_sec * 1000000000 + time.tv_nsec;
}
long stat_mtime(const char *filename) {
struct stat stat_buf;
stat(filename, &stat_buf);
return stat_buf.st_mtime;
}
int rm_rf(const char *path) {
struct stat stat_buf;
if (lstat(path, &stat_buf) != 0)
return (errno == ENOENT) ? 0 : -1;
if (S_ISREG(stat_buf.st_mode)) {
// regular file
return unlink(path);
} else if (S_ISLNK(stat_buf.st_mode)) {
// link
return unlink(path);
} else if (S_ISDIR(stat_buf.st_mode)) {
// directory
char buf[FILENAME_MAX];
DIR *dir;
// open directory
if ((dir = opendir(path)) == NULL)
return -1;
// read directory
for (struct dirent *ent; (ent = readdir(dir)) != NULL;) {
if (streq(ent->d_name, ".") || streq(ent->d_name, ".."))
continue;
snprintf(buf, sizeof(buf), "%s/%s", path, ent->d_name);
if (rm_rf(buf) != 0) {
closedir(dir);
return -1;
}
}
// close and remove directory
closedir(dir);
return rmdir(path);
} else {
// other - not supported
errno = ENOTSUP;
return -1;
}
}
long fsize(FILE *file) {
long cur_pos, len;
if ((cur_pos = ftell(file)) == -1)
return -1;
if (fseek(file, 0, SEEK_END) != 0)
return -1;
if ((len = ftell(file)) == -1)
return -1;
if (fseek(file, cur_pos, SEEK_SET) != 0)
return -1;
return len;
}
long flines(FILE *file) {
long cur_pos, lines = 0;
if ((cur_pos = ftell(file)) == -1)
return -1;
if (fseek(file, 0, SEEK_SET) != 0)
return -1;
for (int ch; (ch = fgetc(file)) != EOF;) {
if (ch == '\n') lines++;
}
if (fseek(file, cur_pos, SEEK_SET) != 0)
return -1;
return lines + 1;
}
long file_get_line_pos(FILE *file, long line_num) {
if (line_num < 1) {
errno = EINVAL;
return -1;
}
long cur_pos;
if ((cur_pos = ftell(file)) == -1)
return -1;
if (fseek(file, 0, SEEK_SET) != 0)
return -1;
long lines = 0, pos = 0;
for (int ch; lines < line_num - 1 && (ch = fgetc(file)) != EOF; pos++) {
if (ch == '\n') lines++;
}
if (fseek(file, cur_pos, SEEK_SET) != 0)
return -1;
return pos;
}
int fseekl(FILE *file, long line_num) {
if (line_num < 1) {
errno = EINVAL;
return -1;
}
if (fseek(file, 0, SEEK_SET) != 0)
return -1;
long lines = 0;
for (int ch; lines < line_num - 1 && (ch = fgetc(file)) != EOF;) {
if (ch == '\n') lines++;
}
return 0;
}
long ftelll(FILE *file) {
long cur_pos;
if ((cur_pos = ftell(file)) == -1)
return -1;
if (fseek(file, 0, SEEK_SET) != 0)
return -1;
long lines = 0, pos = 0;
for (int ch; pos < cur_pos && (ch = fgetc(file)) != EOF; pos++) {
if (ch == '\n') lines++;
}
if (fseek(file, cur_pos, SEEK_SET) != 0)
return -1;
return lines + 1;
}
long parse_chunk_header(const char *buf, size_t len, size_t *ret_len) {
for (int i = 0; i < len; i++) {
char ch = buf[i];
if (ch == '\r') {
continue;
} else if (ch == '\n' && i > 1 && buf[i - 1] == '\r') {
if (ret_len != NULL) *ret_len = i + 1;
return strtol(buf, NULL, 16);
} else if (!((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))) {
errno = EPROTO;
return -1;
}
}
return -1;
}

View File

@ -1,12 +1,13 @@
/**
* Necronda Web Server
* Utilities (header file)
* src/lib/utils.h
* Lorenz Stechauner, 2020-12-03
* sesimos - secure, simple, modern web server
* @brief Utilities (header file)
* @file src/lib/utils.h
* @author Lorenz Stechauner
* @date 2020-12-03
*/
#ifndef NECRONDA_SERVER_UTILS_H
#define NECRONDA_SERVER_UTILS_H
#ifndef SESIMOS_UTILS_H
#define SESIMOS_UTILS_H
#include <stdio.h>
@ -18,28 +19,52 @@
#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_component(const void *in, size_t size_in, char *out, size_t size_out);
int url_encode(const char *str, char *enc, long *size);
int url_encode(const void *in, size_t size_in, char *out, size_t size_out);
int url_decode(const char *str, char *dec, long *size);
int mime_is_compressible(const char *type);
int mime_is_compressible(const char *restrict type);
int strcpy_rem_webroot(char *dst, const char *str, long len, const char *webroot);
int mime_is_text(const char *restrict type);
#endif //NECRONDA_SERVER_UTILS_H
int strcpy_rem_webroot(char *dst, const char *str, const char *webroot);
int str_trim(char **start, char **end);
int str_trim_lws(char **start, char **end);
int streq(const char *restrict str1, const char *restrict str2);
int strcontains(const char *restrict haystack, const char *restrict needle);
int strstarts(const char *restrict str, const char *restrict prefix);
int strends(const char *restrict str, const char *restrict suffix);
int base64_encode(void *data, unsigned long data_len, char *output, unsigned long *output_len);
long clock_micros(void);
long clock_cpu(void);
long stat_mtime(const char *filename);
int rm_rf(const char *path);
long fsize(FILE *file);
long flines(FILE *file);
long file_get_line_pos(FILE *file, long line_num);
int fseekl(FILE *file, long line_num);
long ftelll(FILE *file);
long parse_chunk_header(const char *buf, size_t len, size_t *ret_len);
#endif //SESIMOS_UTILS_H

130
src/lib/websocket.c Normal file
View File

@ -0,0 +1,130 @@
/**
* sesimos - secure, simple, modern web server
* @brief WebSocket reverse proxy
* @file src/lib/websocket.c
* @author Lorenz Stechauner
* @date 2022-08-16
*/
#include "../logger.h"
#include "websocket.h"
#include "utils.h"
#include <string.h>
#include <openssl/sha.h>
static const char ws_key_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
int ws_calc_accept_key(const char *key, char *accept_key) {
if (key == NULL || accept_key == NULL)
return -1;
char input[256] = "";
unsigned char output[SHA_DIGEST_LENGTH];
strcat(input, key);
strcat(input, ws_key_uuid);
if (SHA1((unsigned char *) input, strlen(input), output) == NULL) {
return -2;
}
base64_encode(output, sizeof(output), accept_key, NULL);
return 0;
}
int ws_recv_frame_header(sock *s, ws_frame *frame) {
unsigned char buf[12];
if (sock_recv_x(s, buf, 2, 0) == -1) {
error("Unable to receive from socket");
return -1;
}
unsigned short bits = (buf[0] << 8) | buf[1];
frame->f_fin = (bits >> 15) & 1;
frame->f_rsv1 = (bits >> 14) & 1;
frame->f_rsv2 = (bits >> 13) & 1;
frame->f_rsv3 = (bits >> 12) & 1;
frame->opcode = (bits >> 8) & 0xF;
frame->f_mask = (bits >> 7) & 1;
unsigned short len = (bits & 0x7F);
int remaining = frame->f_mask ? 4 : 0;
if (len == 126) {
remaining += 2;
} else if (len == 127) {
remaining += 8;
}
if (sock_recv_x(s, buf, remaining, 0) == -1) {
error("Unable to receive from socket");
return -1;
}
if (len == 126) {
frame->len = (((unsigned long) buf[0]) << 8) | ((unsigned long) buf[1]);
} else if (len == 127) {
frame->len =
(((unsigned long) buf[0]) << 56) |
(((unsigned long) buf[1]) << 48) |
(((unsigned long) buf[2]) << 40) |
(((unsigned long) buf[3]) << 32) |
(((unsigned long) buf[4]) << 24) |
(((unsigned long) buf[5]) << 16) |
(((unsigned long) buf[6]) << 8) |
(((unsigned long) buf[7]) << 0);
} else {
frame->len = len;
}
if (frame->f_mask) memcpy(frame->masking_key, buf + (remaining - 4), 4);
return 0;
}
int ws_send_frame_header(sock *s, ws_frame *frame) {
unsigned char buf[14], *ptr = buf;
unsigned short len;
if (frame->len > 0x7FFF) {
len = 127;
} else if (frame->len > 125) {
len = 126;
} else {
len = frame->len;
}
unsigned short bits =
(frame->f_fin << 15) |
(frame->f_rsv1 << 14) |
(frame->f_rsv2 << 13) |
(frame->f_rsv3 << 12) |
(frame->opcode << 8) |
(frame->f_mask << 7) |
len;
ptr++[0] = bits >> 8;
ptr++[0] = bits & 0xFF;
if (len >= 126) {
for (int i = (len == 126 ? 2 : 8) - 1; i >= 0; i--)
ptr++[0] = (unsigned char) ((frame->len >> (i * 8)) & 0xFF);
}
if (frame->f_mask) {
memcpy(ptr, frame->masking_key, 4);
ptr += 4;
}
long ret = sock_send_x(s, buf, ptr - buf, frame->len != 0 ? MSG_MORE : 0);
if (ret < 0) {
error("Unable to send to socket");
return -1;
} else if (ret != ptr - buf) {
error("Unable to send to socket");
return -2;
}
return 0;
}

33
src/lib/websocket.h Normal file
View File

@ -0,0 +1,33 @@
/**
* sesimos - secure, simple, modern web server
* @brief WebSocket reverse proxy (header file)
* @file src/lib/websocket.h
* @author Lorenz Stechauner
* @date 2022-08-16
*/
#ifndef SESIMOS_WEBSOCKET_H
#define SESIMOS_WEBSOCKET_H
#include "sock.h"
#define WS_TIMEOUT 3600
typedef struct {
unsigned char f_fin:1;
unsigned char f_rsv1:1;
unsigned char f_rsv2:1;
unsigned char f_rsv3:1;
unsigned char opcode:4;
unsigned char f_mask:1;
unsigned long len;
char masking_key[4];
} ws_frame;
int ws_calc_accept_key(const char *key, char *accept_key);
int ws_recv_frame_header(sock *s, ws_frame *frame);
int ws_send_frame_header(sock *s, ws_frame *frame);
#endif //SESIMOS_WEBSOCKET_H

305
src/logger.c Normal file
View File

@ -0,0 +1,305 @@
/**
* sesimos - secure, simple, modern web server
* @brief Logger
* @file src/logger.h
* @author Lorenz Stechauner
* @date 2022-12-10
*/
#include "logger.h"
#include "lib/utils.h"
#include "lib/error.h"
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <signal.h>
#include <malloc.h>
#define LOG_MAX_MSG_SIZE 2048
#define LOG_BUF_SIZE 256
#define LOG_NAME_LEN 12
#define LOG_PREFIX_LEN 256
#define LOG_PREFIX "[%8s][%-8s][%-6s]"
#define LOG_TIME_BUF_SIZE 9
#define LOG_TIME_FMT "%H:%M:%S"
typedef struct {
log_lvl_t lvl;
long time;
char name[LOG_NAME_LEN];
char prefix[LOG_PREFIX_LEN];
char txt[LOG_MAX_MSG_SIZE];
} log_msg_t;
typedef struct {
int rd;
int wr;
log_msg_t msgs[LOG_BUF_SIZE];
} buf_t;
static pthread_t thread;
static volatile sig_atomic_t alive = 0;
static sem_t sem_buf, sem_buf_free, sem_buf_used;
static buf_t buffer;
static pthread_key_t key_name = -1, key_prefix = -1;
static char global_name[LOG_NAME_LEN] = "", global_prefix[LOG_PREFIX_LEN] = "";
static const char *level_keywords[] = {
"EMERG",
"ALERT",
"CRIT",
"ERROR",
"WARN",
"NOTICE",
"INFO",
"DEBUG"
};
static const char *timestr(time_t ts, char *buf) {
struct tm time_info;
strftime(buf, LOG_TIME_BUF_SIZE, LOG_TIME_FMT, localtime_r(&ts, &time_info));
return buf;
}
static void err(const char *restrict msg) {
char err_buf[64], time_buf[LOG_TIME_BUF_SIZE];
fprintf(stderr, ERR_STR LOG_PREFIX " %s: %s" CLR_STR "\n", timestr(time(NULL), time_buf), "logger",
level_keywords[LOG_CRITICAL], msg, error_str(errno, err_buf, sizeof(err_buf)));
}
void logmsgf(log_lvl_t level, const char *restrict format, ...) {
char buf[256], err_buf[256], time_buf[LOG_TIME_BUF_SIZE];
va_list args;
va_start(args, format);
const char *color = (level <= LOG_ERROR) ? ERR_STR : ((level <= LOG_WARNING) ? WRN_STR : "");
if (errno != 0) {
snprintf(buf, sizeof(buf), "%s%s: %s" CLR_STR, color, format, error_str(errno, err_buf, sizeof(err_buf)));
} else {
snprintf(buf, sizeof(buf), "%s%s" CLR_STR, color, format);
}
void *name = pthread_getspecific(key_name);
if (name == NULL && global_name[0] != 0) name = global_name;
void *prefix = pthread_getspecific(key_prefix);
if (prefix == NULL && global_prefix[0] != 0) prefix = global_prefix;
if (!alive) {
// no logger thread running
// simply write to stdout without synchronization
printf("%s" LOG_PREFIX "%s%s ", color,
timestr(time(NULL), time_buf),
(name != NULL) ? (char *) name : "",
level_keywords[level], CLR_STR,
(prefix != NULL) ? (char *) prefix : "");
vprintf(buf, args);
printf("\n");
} else {
// wait for free slot in buffer
while (sem_wait(&sem_buf_free) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
err("Unable to lock semaphore");
errno = 0;
}
// cleanup
va_end(args);
return;
}
// try to lock buffer
while (sem_wait(&sem_buf) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
err("Unable to lock semaphore");
errno = 0;
}
// cleanup
sem_post(&sem_buf_free);
va_end(args);
return;
}
// write message to buffer
log_msg_t *msg = &buffer.msgs[buffer.rd];
buffer.rd = (buffer.rd + 1) % LOG_BUF_SIZE;
vsnprintf(msg->txt, sizeof(msg->txt), buf, args);
msg->lvl = level;
msg->time = time(NULL);
if (name != NULL) {
snprintf(msg->name, sizeof(msg->name), "%s", (char *) name);
} else {
msg->name[0] = 0;
}
if (prefix != NULL) {
snprintf(msg->prefix, sizeof(msg->prefix), "%s", (char *) prefix);
} else {
msg->prefix[0] = 0;
}
// unlock buffer
sem_post(&sem_buf);
// unlock slot in buffer for logger
sem_post(&sem_buf_used);
}
// cleanup
va_end(args);
}
static void logger_destroy(void) {
sem_destroy(&sem_buf);
sem_destroy(&sem_buf_free);
sem_destroy(&sem_buf_used);
}
static int logger_remaining(void) {
int val = 0;
sem_getvalue(&sem_buf_used, &val);
return val;
}
void logger_set_name(const char *restrict format, ...) {
va_list args;
void *ptr;
if (key_name == -1) {
// not initialized
va_start(args, format);
vsnprintf(global_name, sizeof(global_name), format, args);
ptr = global_name;
} else {
int ret;
if ((ptr = pthread_getspecific(key_name)) == NULL) {
ptr = malloc(LOG_NAME_LEN);
if ((ret = pthread_setspecific(key_name, ptr)) != 0) {
errno = ret;
err("Unable to set thread specific values");
return;
}
}
va_start(args, format);
vsnprintf(ptr, LOG_NAME_LEN, format, args);
}
// set thread name
// warning: max length is 16 (incl. terminating null byte)
pthread_setname_np(pthread_self(), ptr);
// cleanup
va_end(args);
}
void logger_set_prefix(const char *restrict format, ...) {
va_list args;
if (key_prefix == -1) {
// not initialized
va_start(args, format);
vsnprintf(global_prefix, sizeof(global_prefix), format, args);
} else {
int ret;
void *ptr = pthread_getspecific(key_prefix);
if (!ptr) {
ptr = malloc(LOG_PREFIX_LEN);
if ((ret = pthread_setspecific(key_prefix, ptr)) != 0) {
errno = ret;
err("Unable to set thread specific values");
return;
}
}
va_start(args, format);
vsnprintf(ptr, LOG_PREFIX_LEN, format, args);
}
// cleanup
va_end(args);
}
static void *logger_thread(void *arg) {
char time_buf[LOG_TIME_BUF_SIZE];
logger_set_name("logger");
alive = 1;
while (alive || logger_remaining() > 0) {
// wait for buffer to be filled
if (sem_wait(&sem_buf_used) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
err("Unable to lock semaphore");
errno = 0;
break;
}
}
log_msg_t *msg = &buffer.msgs[buffer.wr];
buffer.wr = (buffer.wr + 1) % LOG_BUF_SIZE;
printf("%s" LOG_PREFIX "%s%s %s\n",
(msg->lvl <= LOG_ERROR) ? ERR_STR : ((msg->lvl <= LOG_WARNING) ? WRN_STR : ""),
(timestr(msg->time, time_buf)),
(msg->name[0] != 0) ? (char *) msg->name : "", level_keywords[msg->lvl], CLR_STR,
(msg->prefix[0] != 0) ? (char *) msg->prefix : "", msg->txt);
// unlock slot in buffer
sem_post(&sem_buf_free);
}
logger_destroy();
return NULL;
}
int logger_init(void) {
int ret;
// try to initialize all three semaphores
if (sem_init(&sem_buf, 0, 1) != 0 || sem_init(&sem_buf_free, 0, LOG_BUF_SIZE) != 0 || sem_init(&sem_buf_used, 0, 0) != 0) {
err("Unable to initialize semaphore");
logger_destroy();
return -1;
}
// initialize read/write heads
buffer.rd = 0;
buffer.wr = 0;
// initialize thread specific values (keys)
if ((ret = pthread_key_create(&key_name, free)) != 0 || (ret = pthread_key_create(&key_prefix, free)) != 0) {
errno = ret;
err("Unable to initialize thread specific values");
logger_destroy();
return -1;
}
pthread_create(&thread, NULL, logger_thread, NULL);
return 0;
}
void logger_stop(void) {
alive = 0;
pthread_kill(thread, SIGUSR1);
}
int logger_join(void) {
return pthread_join(thread, NULL);
}

42
src/logger.h Normal file
View File

@ -0,0 +1,42 @@
/**
* sesimos - secure, simple, modern web server
* @brief Logger (header file)
* @file src/logger.h
* @author Lorenz Stechauner
* @date 2022-12-10
*/
#ifndef SESIMOS_LOGGER_H
#define SESIMOS_LOGGER_H
#define LOG_DEBUG 7
#define LOG_INFO 6
#define LOG_NOTICE 5
#define LOG_WARNING 4
#define LOG_ERROR 3
#define LOG_CRITICAL 2
#define LOG_ALERT 1
typedef unsigned char log_lvl_t;
#define debug(...) logmsgf(LOG_DEBUG, __VA_ARGS__)
#define info(...) logmsgf(LOG_INFO, __VA_ARGS__)
#define notice(...) logmsgf(LOG_NOTICE, __VA_ARGS__)
#define warning(...) logmsgf(LOG_WARNING, __VA_ARGS__)
#define error(...) logmsgf(LOG_ERROR, __VA_ARGS__)
#define critical(...) logmsgf(LOG_CRITICAL, __VA_ARGS__)
#define alert(...) logmsgf(LOG_ALERT, __VA_ARGS__)
void logmsgf(log_lvl_t level, const char *restrict format, ...);
void logger_set_name(const char *restrict format, ...);
void logger_set_prefix(const char *restrict format, ...);
int logger_init(void);
void logger_stop(void);
int logger_join(void);
#endif //SESIMOS_LOGGER_H

View File

@ -1,419 +0,0 @@
/**
* Necronda Web Server
* Main executable
* src/necronda-server.c
* Lorenz Stechauner, 2020-12-03
*/
#define _POSIX_C_SOURCE 199309L
#include "necronda.h"
#include "necronda-server.h"
#include "client.c"
#include "lib/cache.h"
#include "lib/config.h"
#include "lib/sock.h"
#include "lib/rev_proxy.h"
#include "lib/geoip.h"
#include <stdio.h>
#include <sys/socket.h>
#include <signal.h>
#include <unistd.h>
#include <sys/select.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <wait.h>
#include <sys/types.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/conf.h>
#include <dirent.h>
int active = 1;
const char *config_file;
int sockets[NUM_SOCKETS];
pid_t children[MAX_CHILDREN];
MMDB_s mmdbs[MAX_MMDB];
SSL_CTX *contexts[CONFIG_MAX_CERT_CONFIG];
void openssl_init() {
SSL_library_init();
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
}
static int ssl_servername_cb(SSL *ssl, int *ad, void *arg) {
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (servername != NULL) {
const host_config *conf = get_host_config(servername);
if (conf != NULL) SSL_set_SSL_CTX(ssl, contexts[conf->cert]);
}
return SSL_TLSEXT_ERR_OK;
}
void destroy() {
fprintf(stderr, "\n" ERR_STR "Terminating forcefully!" CLR_STR "\n");
int status = 0;
int ret;
int kills = 0;
for (int i = 0; i < MAX_CHILDREN; i++) {
if (children[i] != 0) {
ret = waitpid(children[i], &status, WNOHANG);
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
}
} else {
kill(children[i], SIGKILL);
kills++;
}
}
}
if (kills > 0) {
fprintf(stderr, ERR_STR "Killed %i child process(es)" CLR_STR "\n", kills);
}
cache_unload();
config_unload();
exit(2);
}
void terminate() {
fprintf(stderr, "\nTerminating gracefully...\n");
active = 0;
signal(SIGINT, destroy);
signal(SIGTERM, destroy);
for (int i = 0; i < NUM_SOCKETS; i++) {
shutdown(sockets[i], SHUT_RDWR);
close(sockets[i]);
}
int status = 0;
int wait_num = 0;
int ret;
for (int i = 0; i < MAX_CHILDREN; i++) {
if (children[i] != 0) {
ret = waitpid(children[i], &status, WNOHANG);
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
}
} else {
kill(children[i], SIGTERM);
wait_num++;
}
}
}
if (wait_num > 0) {
fprintf(stderr, "Waiting for %i child process(es)...\n", wait_num);
}
for (int i = 0; i < MAX_CHILDREN; i++) {
if (children[i] != 0) {
ret = waitpid(children[i], &status, 0);
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
}
}
}
}
if (wait_num > 0) {
// Wait another 50 ms to let child processes write to stdout/stderr
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
struct timespec ts = {.tv_sec = 0, .tv_nsec = 50000000};
nanosleep(&ts, &ts);
fprintf(stderr, "\nGoodbye\n");
} else {
fprintf(stderr, "Goodbye\n");
}
cache_unload();
config_unload();
exit(0);
}
int main(int argc, const char *argv[]) {
const int YES = 1;
fd_set socket_fds, read_socket_fds;
int max_socket_fd = 0;
int ready_sockets_num;
long client_num = 0;
char buf[1024];
int ret;
int client_fd;
sock client;
struct sockaddr_in6 client_addr;
unsigned int client_addr_len = sizeof(client_addr);
memset(sockets, 0, sizeof(sockets));
memset(children, 0, sizeof(children));
memset(mmdbs, 0, sizeof(mmdbs));
struct timeval timeout;
const struct sockaddr_in6 addresses[2] = {
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(80)},
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(443)}
};
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0) {
fprintf(stderr, ERR_STR "Unable to set stdout to line-buffered mode: %s" CLR_STR, strerror(errno));
return 1;
}
printf("Necronda Web Server " NECRONDA_VERSION "\n");
ret = config_init();
if (ret != 0) {
return 1;
}
config_file = NULL;
for (int i = 1; i < argc; i++) {
const char *arg = argv[i];
if (strcmp(arg, "-h") == 0 || strcmp(arg, "--help") == 0) {
printf("Usage: necronda-server [-h] [-c <CONFIG-FILE>]\n"
"\n"
"Options:\n"
" -c, --config <CONFIG-FILE> path to the config file. If not provided, default will be used\n"
" -h, --help print this dialogue\n");
config_unload();
return 0;
} else if (strcmp(arg, "-c") == 0 || strcmp(arg, "--config") == 0) {
if (i == argc - 1) {
fprintf(stderr, ERR_STR "Unable to parse argument %s, usage: --config <CONFIG-FILE>" CLR_STR "\n", arg);
config_unload();
return 1;
}
config_file = argv[++i];
} else {
fprintf(stderr, ERR_STR "Unable to parse argument '%s'" CLR_STR "\n", arg);
config_unload();
return 1;
}
}
ret = config_load(config_file == NULL ? DEFAULT_CONFIG_FILE : config_file);
if (ret != 0) {
config_unload();
return 1;
}
sockets[0] = socket(AF_INET6, SOCK_STREAM, 0);
if (sockets[0] < 0) goto socket_err;
sockets[1] = socket(AF_INET6, SOCK_STREAM, 0);
if (sockets[1] < 0) {
socket_err:
fprintf(stderr, ERR_STR "Unable to create socket: %s" CLR_STR "\n", strerror(errno));
config_unload();
return 1;
}
for (int i = 0; i < NUM_SOCKETS; i++) {
if (setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(YES)) < 0) {
fprintf(stderr, ERR_STR "Unable to set options for socket %i: %s" CLR_STR "\n", i, strerror(errno));
config_unload();
return 1;
}
}
if (bind(sockets[0], (struct sockaddr *) &addresses[0], sizeof(addresses[0])) < 0) goto bind_err;
if (bind(sockets[1], (struct sockaddr *) &addresses[1], sizeof(addresses[1])) < 0) {
bind_err:
fprintf(stderr, ERR_STR "Unable to bind socket to address: %s" CLR_STR "\n", strerror(errno));
config_unload();
return 1;
}
signal(SIGINT, terminate);
signal(SIGTERM, terminate);
if (geoip_dir[0] != 0) {
DIR *geoip = opendir(geoip_dir);
if (geoip == NULL) {
fprintf(stderr, ERR_STR "Unable to open GeoIP dir: %s" CLR_STR "\n", strerror(errno));
config_unload();
return 1;
}
struct dirent *dir;
int i = 0;
while ((dir = readdir(geoip)) != NULL) {
if (strcmp(dir->d_name + strlen(dir->d_name) - 5, ".mmdb") != 0) continue;
if (i >= MAX_MMDB) {
fprintf(stderr, ERR_STR "Too many .mmdb files" CLR_STR "\n");
config_unload();
return 1;
}
sprintf(buf, "%s/%s", geoip_dir, dir->d_name);
ret = MMDB_open(buf, 0, &mmdbs[i]);
if (ret != MMDB_SUCCESS) {
fprintf(stderr, ERR_STR "Unable to open .mmdb file: %s" CLR_STR "\n", MMDB_strerror(ret));
config_unload();
return 1;
}
i++;
}
if (i == 0) {
fprintf(stderr, ERR_STR "No .mmdb files found in %s" CLR_STR "\n", geoip_dir);
config_unload();
return 1;
}
closedir(geoip);
}
ret = cache_init();
if (ret < 0) {
config_unload();
return 1;
} else if (ret != 0) {
children[0] = ret; // pid
} else {
return 0;
}
openssl_init();
client.buf = NULL;
client.buf_len = 0;
client.buf_off = 0;
for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) {
const cert_config *conf = &config->certs[i];
if (conf->name[0] == 0) break;
contexts[i] = SSL_CTX_new(TLS_server_method());
SSL_CTX *ctx = contexts[i];
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
SSL_CTX_set_ecdh_auto(ctx, 1);
SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
if (SSL_CTX_use_certificate_chain_file(ctx, conf->full_chain) != 1) {
fprintf(stderr, ERR_STR "Unable to load certificate chain file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), conf->full_chain);
config_unload();
return 1;
}
if (SSL_CTX_use_PrivateKey_file(ctx, conf->priv_key, SSL_FILETYPE_PEM) != 1) {
fprintf(stderr, ERR_STR "Unable to load private key file: %s: %s" CLR_STR "\n",
ERR_reason_error_string(ERR_get_error()), conf->priv_key);
config_unload();
return 1;
}
}
client.ctx = contexts[0];
rev_proxy_preload();
for (int i = 0; i < NUM_SOCKETS; i++) {
if (listen(sockets[i], LISTEN_BACKLOG) < 0) {
fprintf(stderr, ERR_STR "Unable to listen on socket %i: %s" CLR_STR "\n", i, strerror(errno));
config_unload();
return 1;
}
}
FD_ZERO(&socket_fds);
for (int i = 0; i < NUM_SOCKETS; i++) {
FD_SET(sockets[i], &socket_fds);
if (sockets[i] > max_socket_fd) {
max_socket_fd = sockets[i];
}
}
fprintf(stderr, "Ready to accept connections\n");
while (active) {
timeout.tv_sec = 1;
timeout.tv_usec = 0;
read_socket_fds = socket_fds;
ready_sockets_num = select(max_socket_fd + 1, &read_socket_fds, NULL, NULL, &timeout);
if (ready_sockets_num < 0) {
fprintf(stderr, ERR_STR "Unable to select sockets: %s" CLR_STR "\n", strerror(errno));
terminate();
return 1;
}
for (int i = 0; i < NUM_SOCKETS; i++) {
if (FD_ISSET(sockets[i], &read_socket_fds)) {
client_fd = accept(sockets[i], (struct sockaddr *) &client_addr, &client_addr_len);
if (client_fd < 0) {
fprintf(stderr, ERR_STR "Unable to accept connection: %s" CLR_STR "\n", strerror(errno));
continue;
}
pid_t pid = fork();
if (pid == 0) {
// child
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
client.socket = client_fd;
client.enc = i == 1;
return client_handler(&client, client_num, &client_addr);
} else if (pid > 0) {
// parent
client_num++;
close(client_fd);
for (int j = 0; j < MAX_CHILDREN; j++) {
if (children[j] == 0) {
children[j] = pid;
break;
}
}
} else {
fprintf(stderr, ERR_STR "Unable to create child process: %s" CLR_STR "\n", strerror(errno));
}
}
}
// TODO outsource in thread
int status = 0;
for (int i = 0; i < MAX_CHILDREN; i++) {
if (children[i] != 0) {
ret = waitpid(children[i], &status, WNOHANG);
if (ret < 0) {
fprintf(stderr, ERR_STR "Unable to wait for child process (PID %i): %s" CLR_STR "\n",
children[i], strerror(errno));
} else if (ret == children[i]) {
children[i] = 0;
if (status != 0) {
fprintf(stderr, ERR_STR "Child process with PID %i terminated with exit code %i" CLR_STR "\n",
ret, status);
}
}
}
}
}
return 0;
}

View File

@ -1,34 +0,0 @@
/**
* Necronda Web Server
* Main executable (header file)
* src/necronda-server.h
* Lorenz Stechauner, 2020-12-03
*/
#ifndef NECRONDA_SERVER_NECRONDA_SERVER_H
#define NECRONDA_SERVER_NECRONDA_SERVER_H
#include <sys/time.h>
#include <maxminddb.h>
#define NUM_SOCKETS 2
#define MAX_CHILDREN 1024
#define MAX_MMDB 3
#define LISTEN_BACKLOG 16
#define REQ_PER_CONNECTION 200
#define CLIENT_TIMEOUT 3600
#define SERVER_TIMEOUT_INIT 4
#define SERVER_TIMEOUT 3600
#define CHUNK_SIZE 8192
extern int sockets[NUM_SOCKETS];
extern pid_t children[MAX_CHILDREN];
extern MMDB_s mmdbs[MAX_MMDB];
extern int server_keep_alive;
extern char *log_client_prefix, *log_conn_prefix, *log_req_prefix, *client_geoip;
extern char *client_addr_str, *client_addr_str_ptr, *server_addr_str, *server_addr_str_ptr, *client_host_str;
extern struct timeval client_timeout;
#endif //NECRONDA_SERVER_NECRONDA_SERVER_H

View File

@ -1,23 +0,0 @@
/**
* Necronda Web Server
* Definitions
* src/necronda.h
* Lorenz Stechauner, 2021-05-04
*/
#ifndef NECRONDA_SERVER_NECRONDA_H
#define NECRONDA_SERVER_NECRONDA_H
#define NECRONDA_VERSION "4.5"
#define SERVER_STR "Necronda/" NECRONDA_VERSION
#define SERVER_STR_HTML "Necronda&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

439
src/server.c Normal file
View File

@ -0,0 +1,439 @@
/**
* Sesimos - secure, simple, modern web server
* @brief Main executable
* @file src/server.c
* @author Lorenz Stechauner
* @date 2020-12-03
*/
#include "defs.h"
#include "server.h"
#include "logger.h"
#include "async.h"
#include "cache_handler.h"
#include "lib/config.h"
#include "lib/proxy.h"
#include "lib/geoip.h"
#include "workers.h"
#include "worker/func.h"
#include "lib/list.h"
#include "lib/utils.h"
#include <stdio.h>
#include <getopt.h>
#include <sys/socket.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/conf.h>
#include <semaphore.h>
const char *config_file;
static int sockets[NUM_SOCKETS];
static SSL_CTX *contexts[CONFIG_MAX_CERT_CONFIG];
static client_ctx_t **clients;
static sem_t sem_clients_lock;
static void clean(void) {
notice("Cleaning sesimos cache and metadata files...");
// remove legacy files
// /.../server/, /.../server/cache
if (rm_rf("/var/sesimos/server") != 0) {
error("Unable to remove /var/sesimos/server/");
} else if (!errno) {
notice("Successfully removed /var/sesimos/server/");
}
errno = 0;
// remove cache and metadata files
char buf[512];
for (int i = 0; i < CONFIG_MAX_HOST_CONFIG; i++) {
host_config_t *hc = &config.hosts[i];
if (hc->type == CONFIG_TYPE_UNSET) break;
if (hc->type != CONFIG_TYPE_LOCAL) continue;
snprintf(buf, sizeof(buf), "%s/.sesimos", hc->local.webroot);
if (rm_rf(buf) != 0) {
error("Unable to remove %s/", buf);
} else if (!errno) {
notice("Successfully removed %s/", buf);
}
errno = 0;
}
notice("Cleaned all sesimos cache and metadata files!");
}
static int ssl_servername_cb(SSL *ssl, int *ad, void *arg) {
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (servername != NULL) {
const host_config_t *conf = get_host_config(servername);
if (conf != NULL) SSL_set_SSL_CTX(ssl, contexts[conf->cert]);
}
return SSL_TLSEXT_ERR_OK;
}
void server_free_client(client_ctx_t *ctx) {
// try to lock clients list
while (sem_wait(&sem_clients_lock) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
critical("Unable to lock clients list");
return;
}
}
// delete from list
clients = list_delete(clients, &ctx);
if (clients == NULL) {
critical("Unable to delete context from list");
return;
}
// unlock clients list
sem_post(&sem_clients_lock);
// free data
free(ctx);
}
static void ssl_free() {
for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) {
const cert_config_t *conf = &config.certs[i];
if (conf->name[0] == 0) break;
SSL_CTX_free(contexts[i]);
}
}
static void accept_cb(void *arg) {
int i = (int) (((int *) arg) - sockets);
int fd = sockets[i];
client_ctx_t *client_ctx = malloc(sizeof(client_ctx_t));
if (client_ctx == NULL) {
critical("Unable to allocate memory for client context");
errno = 0;
return;
}
sock *client = &client_ctx->socket;
client->ctx = contexts[0];
socklen_t addr_len = sizeof(client->_addr);
int client_fd = accept(fd, &client->_addr.sock, &addr_len);
if (client_fd < 0) {
critical("Unable to accept connection");
free(client_ctx);
return;
}
sock_init(client, client_fd, (i == 1) ? SOCK_ENCRYPTED : 0);
client_ctx->cnx_s = client->ts_start;
client_ctx->cnx_e = -1, client_ctx->req_s = -1, client_ctx->req_e = -1, client_ctx->res_ts = -1;
// try to lock clients list
while (sem_wait(&sem_clients_lock) != 0) {
if (errno == EINTR) {
errno = 0;
continue;
} else {
critical("Unable to lock clients list");
return;
}
}
// append to list
clients = list_append(clients, &client_ctx);
if (clients == NULL) {
critical("Unable to add client context to list");
free(client_ctx);
errno = 0;
return;
}
// unlock clients list
sem_post(&sem_clients_lock);
tcp_accept(client_ctx);
}
static void accept_err_cb(void *arg) {
int i = (int) (((int *) arg) - sockets);
int fd = sockets[i];
// TODO accept error callback
}
static void terminate_forcefully(int sig) {
fprintf(stderr, "\n");
notice("Terminating forcefully!");
geoip_free();
notice("Goodbye");
exit(2);
}
static void terminate_gracefully(int sig) {
if (sig != 0) fprintf(stderr, "\n");
notice("Terminating gracefully...");
struct sigaction act = {0};
act.sa_handler = terminate_forcefully;
sigaction(SIGINT, &act, NULL);
sigaction(SIGTERM, &act, NULL);
debug("Closing listening sockets...");
for (int i = 0; i < NUM_SOCKETS; i++) {
close(sockets[i]);
}
debug("Stopping workers...");
cache_stop();
workers_stop();
debug("Destroying workers...");
workers_destroy();
logger_set_prefix("");
debug("Closing proxy connections...");
proxy_close_all();
debug("Closing client connections...");
while (list_size(clients) > 0)
tcp_close(clients[0]);
logger_set_prefix("");
debug("Stopping async loop...");
async_stop();
}
static void nothing(int sig) {}
int main(int argc, char *const argv[]) {
const int YES = 1;
int ret;
int mode = 0;
memset(sockets, 0, sizeof(sockets));
const struct sockaddr_in6 addresses[2] = {
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(80)},
{.sin6_family = AF_INET6, .sin6_addr = IN6ADDR_ANY_INIT, .sin6_port = htons(443)}
};
logger_set_name("server");
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0 || setvbuf(stderr, NULL, _IOLBF, 0) != 0) {
critical("Unable to set stdout/stderr to line-buffered mode");
return 1;
}
printf("sesimos web server " SERVER_VERSION "\n");
static const struct option long_opts[] = {
{"help", no_argument, 0, 'h'},
{"clean", no_argument, 0, 'C'},
{"config", required_argument, 0, 'c'},
{ 0, 0, 0, 0 }
};
config_file = NULL;
for (int c, opt_idx; (c = getopt_long(argc, argv, "hCc:", long_opts, &opt_idx)) != -1;) {
switch (c) {
case 'h':
fprintf(stderr,
"Usage: sesimos [-h] [-c <CONFIG FILE>]\n"
"\n"
"Options:\n"
" -c, --config <CONFIG-FILE> path to the config file. If not provided, default will be used\n"
" -C, --clean clear cached files and other metadata\n"
" -h, --help print this dialogue\n");
return 0;
case 'c':
config_file = optarg;
break;
case 'C':
mode = 1;
break;
case '?':
default:
critical("Unable to parse arguments");
return 1;
}
}
if (optind != argc) {
critical("No positional arguments expected");
return 1;
}
if (config_load(config_file == NULL ? DEFAULT_CONFIG_FILE : config_file) != 0)
return 1;
if (mode == 1) {
clean();
return 0;
}
if ((sockets[0] = socket(AF_INET6, SOCK_STREAM, 0)) == -1 ||
(sockets[1] = socket(AF_INET6, SOCK_STREAM, 0)) == -1)
{
critical("Unable to create socket");
return 1;
}
for (int i = 0; i < NUM_SOCKETS; i++) {
if (setsockopt(sockets[i], SOL_SOCKET, SO_REUSEADDR, &YES, sizeof(YES)) < 0) {
critical("Unable to set options for socket %i", i);
return 1;
}
}
if (bind(sockets[0], (struct sockaddr *) &addresses[0], sizeof(addresses[0])) == -1 ||
bind(sockets[1], (struct sockaddr *) &addresses[1], sizeof(addresses[1])) == -1)
{
critical("Unable to bind socket to address");
return 1;
}
struct sigaction act = {0};
act.sa_handler = terminate_gracefully;
sigaction(SIGINT, &act, NULL);
sigaction(SIGTERM, &act, NULL);
act.sa_handler = nothing;
sigaction(SIGUSR1, &act, NULL);
sigaction(SIGPIPE, &act, NULL);
if ((ret = geoip_init(config.geoip_dir)) != 0) {
if (ret == -1) {
critical("Unable to initialize geoip");
}
return 1;
}
for (int i = 0; i < CONFIG_MAX_CERT_CONFIG; i++) {
const cert_config_t *conf = &config.certs[i];
if (conf->name[0] == 0) break;
contexts[i] = SSL_CTX_new(TLS_server_method());
SSL_CTX *ctx = contexts[i];
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_AUTO_RETRY);
SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
SSL_CTX_set_ecdh_auto(ctx, 1);
SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
if (SSL_CTX_use_certificate_chain_file(ctx, conf->full_chain) != 1) {
critical("Unable to load certificate chain file: %s: %s", ERR_reason_error_string(ERR_get_error()), conf->full_chain);
geoip_free();
return 1;
}
if (SSL_CTX_use_PrivateKey_file(ctx, conf->priv_key, SSL_FILETYPE_PEM) != 1) {
critical("Unable to load private key file: %s: %s", ERR_reason_error_string(ERR_get_error()), conf->priv_key);
geoip_free();
return 1;
}
}
clients = list_create(sizeof(client_ctx_t *), 64);
if (clients == NULL) {
critical("Unable to initialize client list");
ssl_free();
return 1;
}
if (sem_init(&sem_clients_lock, 0, 1) != 0) {
critical("Unable to create clients lock semaphore");
ssl_free();
list_free(clients);
return 1;
}
if (async_init() != 0) {
critical("Unable to initialize async thread");
ssl_free();
geoip_free();
list_free(clients);
sem_destroy(&sem_clients_lock);
return 1;
}
if (proxy_preload() != 0) {
critical("Unable to initialize proxy");
ssl_free();
geoip_free();
list_free(clients);
sem_destroy(&sem_clients_lock);
async_free();
return 1;
}
for (int i = 0; i < NUM_SOCKETS; i++) {
if (listen(sockets[i], LISTEN_BACKLOG) < 0) {
critical("Unable to listen on socket %i", i);
ssl_free();
geoip_free();
list_free(clients);
sem_destroy(&sem_clients_lock);
async_free();
proxy_unload();
return 1;
}
}
logger_init();
if ((ret = cache_init()) != 0) {
if (ret == -1) critical("Unable to initialize cache");
ssl_free();
geoip_free();
list_free(clients);
sem_destroy(&sem_clients_lock);
async_free();
proxy_unload();
logger_stop();
logger_join();
return 1;
}
logger_set_name("main");
workers_init();
for (int i = 0; i < NUM_SOCKETS; i++) {
async_fd(sockets[i], ASYNC_WAIT_READ, ASYNC_KEEP, &sockets[i], accept_cb, accept_err_cb, accept_err_cb);
}
notice("Ready to accept connections");
int error = 0;
async_thread();
if (errno != 0) {
errno = 0;
error = 2;
terminate_gracefully(0);
}
notice("Goodbye!");
// cleanup
ssl_free();
list_free(clients);
sem_destroy(&sem_clients_lock);
geoip_free();
proxy_unload();
cache_join();
async_free();
logger_stop();
logger_join();
return error;
}

29
src/server.h Normal file
View File

@ -0,0 +1,29 @@
/**
* sesimos - secure, simple, modern web server
* @brief Main executable (header file)
* @file src/server.h
* @author Lorenz Stechauner
* @date 2020-12-03
*/
#ifndef SESIMOS_SERVER_H
#define SESIMOS_SERVER_H
#include "worker/func.h"
#define NUM_SOCKETS 2
#define LISTEN_BACKLOG 16
#define REQ_PER_CONNECTION 200
#define SOCKET_TIMEOUT 1
#define CLIENT_TIMEOUT 3600
#define SERVER_SOCKET_TIMEOUT_INIT 5
#define SERVER_SOCKET_TIMEOUT_RES 60
#define SERVER_TIMEOUT 3600
#define CNX_HANDLER_WORKERS 8
#define REQ_HANDLER_WORKERS 16
void server_free_client(client_ctx_t *ctx);
#endif //SESIMOS_SERVER_H

View File

@ -0,0 +1,50 @@
/**
* sesimos - secure, simple, modern web server
* @brief FastCGI frame handler
* @file src/worker/fcgi_frame_handler.c
* @author Lorenz Stechauner
* @date 2023-01-22
*/
#include "func.h"
#include "../logger.h"
#include "../workers.h"
#include <errno.h>
void chunk_handler_func(chunk_ctx_t *ctx) {
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
char buf[CHUNK_SIZE];
long sent = sock_splice_chunked(&ctx->client->socket, ctx->socket, buf, sizeof(buf), ctx->flags | SOCK_SINGLE_CHUNK);
if (sent < 0) {
// error
error("Unable to splice chunk");
errno = 0;
ctx->err_cb(ctx);
} else if (sent == 0) {
// last chunk
ctx->client->chunks_transferred = 1;
ctx->next_cb(ctx);
} else {
// next chunk
handle_chunk(ctx);
return;
}
free(ctx);
}
int handle_chunks(client_ctx_t *ctx, sock *socket, int flags, void (*next_cb)(chunk_ctx_t *), void (*err_cb)(chunk_ctx_t *)) {
chunk_ctx_t *a = malloc(sizeof(chunk_ctx_t));
a->client = ctx;
a->socket = socket;
a->flags = flags;
a->next_cb = (void (*)(void *)) next_cb;
a->err_cb = (void (*)(void *)) err_cb;
handle_chunk(a);
return 0;
}

View File

@ -0,0 +1,76 @@
/**
* sesimos - secure, simple, modern web server
* @brief FastCGI frame handler
* @file src/worker/fastcgi_frame_handler.c
* @author Lorenz Stechauner
* @date 2023-01-22
*/
#include "func.h"
#include "../lib/fastcgi.h"
#include "../logger.h"
#include "../workers.h"
#include <errno.h>
#include <memory.h>
#include <unistd.h>
void fastcgi_frame_handler_func(fastcgi_ctx_t *ctx) {
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
switch (fastcgi_recv_frame(&ctx->cnx)) {
case FCGI_STDOUT:
case FCGI_STDERR:
fastcgi_handle_frame(ctx);
break;
case -1:
error("Unable to receive FastCGI frame");
ctx->client->s_keep_alive = 0;
fastcgi_close(ctx);
break;
default:
// end of request received
write(ctx->cnx.fd_out, "\0\0\0\0\0\0\0\0\r\n", 10);
fastcgi_close(ctx);
}
}
int fastcgi_handle_connection(client_ctx_t *ctx, fastcgi_cnx_t **cnx) {
sock_set_timeout(&(*cnx)->socket, FASTCGI_TIMEOUT);
sock_set_socket_timeout(&(*cnx)->socket, FASTCGI_SOCKET_TIMEOUT);
fastcgi_ctx_t *a = malloc(sizeof(fastcgi_ctx_t));
a->closed = 0;
a->client = ctx;
memcpy(&a->cnx, *cnx, sizeof(fastcgi_cnx_t));
ctx->fcgi_ctx = a;
fastcgi_handle_frame(a);
*cnx = &a->cnx;
return 0;
}
void fastcgi_close(fastcgi_ctx_t *ctx) {
ctx->closed++;
if (ctx->closed != 2)
return;
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
fastcgi_php_error(&ctx->cnx, NULL);
if (ctx->cnx.app_status != 0)
error("FastCGI app terminated with exit code %i", ctx->cnx.app_status);
debug("Closing FastCGI connection");
fastcgi_close_cnx(&ctx->cnx);
ctx->client->fcgi_ctx = NULL;
free(ctx);
errno = 0;
}
void fastcgi_close_error(fastcgi_ctx_t *ctx) {
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
fastcgi_close_cnx(&ctx->cnx);
}

View File

@ -0,0 +1,200 @@
/**
* sesimos - secure, simple, modern web server
* @brief FastCGI handler
* @file src/worker/fastcgi_handler.c
* @author Lorenz Stechauner
* @date 2022-12-28
*/
#include "func.h"
#include "../logger.h"
#include "../lib/utils.h"
#include "../workers.h"
#include "../lib/fastcgi.h"
#include <string.h>
#include <errno.h>
#include <unistd.h>
static int fastcgi_handler_1(client_ctx_t *ctx, fastcgi_cnx_t **fcgi_cnx);
static int fastcgi_handler_2(client_ctx_t *ctx, fastcgi_cnx_t *fcgi_cnx);
void fastcgi_handler_func(client_ctx_t *ctx) {
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->req_host, CLR_STR, ctx->log_prefix);
if (!ctx->chunks_transferred) {
fastcgi_cnx_t *fcgi_cnx = NULL;
int ret = fastcgi_handler_1(ctx, &fcgi_cnx);
respond(ctx);
if (ret == 0) {
fastcgi_handler_2(ctx, fcgi_cnx);
return;
} else if (ctx->fcgi_ctx != NULL) {
fastcgi_close(ctx->fcgi_ctx);
}
}
request_complete(ctx);
handle_request(ctx);
}
static int fastcgi_handler_1(client_ctx_t *ctx, fastcgi_cnx_t **fcgi_cnx) {
http_res *res = &ctx->res;
http_req *req = &ctx->req;
http_uri *uri = &ctx->uri;
sock *client = &ctx->socket;
char *err_msg = ctx->err_msg;
char buf[1024];
int mode, ret;
if (strends(uri->filename, ".php")) {
mode = FASTCGI_BACKEND_PHP;
} else {
res->status = http_get_status(500);
error("Invalid FastCGI extension: %s", uri->filename);
return 3;
}
fastcgi_cnx_t fcgi_cnx_buf;
sock_init(&fcgi_cnx_buf.socket, -1, 0);
fcgi_cnx_buf.req_id = 0;
fcgi_cnx_buf.r_addr = ctx->socket.addr;
fcgi_cnx_buf.r_host = (ctx->host[0] != 0) ? ctx->host : NULL;
struct stat statbuf;
stat(uri->filename, &statbuf);
char *last_modified = http_format_date(statbuf.st_mtime, buf, sizeof(buf));
http_add_header_field(&res->hdr, "Last-Modified", last_modified);
res->status = http_get_status(200);
if (fastcgi_init(&fcgi_cnx_buf, mode, ctx->req_num, client, req, uri) != 0) {
fastcgi_close_cnx(&fcgi_cnx_buf);
res->status = http_get_status(503);
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
return 3;
}
(*fcgi_cnx) = &fcgi_cnx_buf;
fastcgi_handle_connection(ctx, fcgi_cnx);
int expect_100_continue = 0;
const char *client_expect = http_get_header_field(&req->hdr, "Expect");
if (client_expect != NULL && strcasecmp(client_expect, "100-continue") == 0) {
expect_100_continue = 1;
} else if (client_expect != NULL) {
fastcgi_close_cnx((&fcgi_cnx_buf));
res->status = http_get_status(417);
return 3;
}
const char *client_content_length = http_get_header_field(&req->hdr, "Content-Length");
const char *client_transfer_encoding = http_get_header_field(&req->hdr, "Transfer-Encoding");
if (client_content_length != NULL) {
if (expect_100_continue) {
info(HTTP_1XX_STR "100 Continue" CLR_STR);
http_send_100_continue(client);
}
unsigned long client_content_len = strtoul(client_content_length, NULL, 10);
ret = fastcgi_receive(*fcgi_cnx, client, client_content_len);
} else if (strcontains(client_transfer_encoding, "chunked")) {
if (expect_100_continue) {
info(HTTP_1XX_STR "100 Continue" CLR_STR);
http_send_100_continue(client);
}
ret = fastcgi_receive_chunked(*fcgi_cnx, client);
} else if (expect_100_continue) {
fastcgi_close_cnx((&fcgi_cnx_buf));
res->status = http_get_status(417);
return 3;
} else {
ret = 0;
}
if (ret != 0) {
if (ret < 0) {
return -1;
} else {
sprintf(err_msg, "Unable to communicate with FastCGI socket.");
}
res->status = http_get_status(502);
return 2;
}
fastcgi_close_stdin(*fcgi_cnx);
if ((ret = fastcgi_header(*fcgi_cnx, res, err_msg)) != 0) {
if (ret == -1) res->status = http_get_status(502);
return ret;
}
const char *status_hdr = http_get_header_field(&res->hdr, "Status");
if (status_hdr != NULL) {
int status_code = (int) strtoul(status_hdr, NULL, 10);
res->status = http_get_status(status_code);
http_remove_header_field(&res->hdr, "Status", HTTP_REMOVE_ALL);
if (res->status == NULL && status_code >= 100 && status_code <= 999) {
ctx->custom_status.code = status_code;
ctx->custom_status.type = 0;
strcpy(ctx->custom_status.msg, status_hdr + 4);
res->status = &ctx->custom_status;
} else if (res->status == NULL) {
res->status = http_get_status(500);
sprintf(err_msg, "The status_hdr code was set to an invalid or unknown value.");
return 2;
}
}
const char *content_length_f = http_get_header_field(&res->hdr, "Content-Length");
ctx->content_length = (content_length_f == NULL) ? -1 : strtol(content_length_f, NULL, 10);
const char *content_type = http_get_header_field(&res->hdr, "Content-Type");
const char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
if (content_encoding == NULL &&
content_type != NULL &&
strstarts(content_type, "text/html") &&
ctx->content_length != -1 &&
ctx->content_length <= sizeof(ctx->msg_content) - 1)
{
fastcgi_dump(*fcgi_cnx, ctx->msg_content, sizeof(ctx->msg_content));
return 1;
}
ctx->use_fastcgi = 1;
ctx->content_length = -1;
if (http_get_header_field(&res->hdr, "Content-Length") == NULL) {
http_add_header_field(&res->hdr, "Transfer-Encoding", "chunked");
}
return 0;
}
static void fastcgi_next_cb(chunk_ctx_t *ctx) {
if (ctx->client->fcgi_ctx) {
fastcgi_close(ctx->client->fcgi_ctx);
ctx->client->fcgi_ctx = NULL;
}
fastcgi_handle(ctx->client);
}
static void fastcgi_error_cb(chunk_ctx_t *ctx) {
if (ctx->client->chunks_transferred)
return;
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->client->req_host, CLR_STR, ctx->client->log_prefix);
// FIXME segfault on error_cb
warning("Closing connection due to FastCGI error");
if(ctx->client->fcgi_ctx) {
fastcgi_close_error(ctx->client->fcgi_ctx);
ctx->client->fcgi_ctx = NULL;
}
tcp_close(ctx->client);
errno = 0;
}
static int fastcgi_handler_2(client_ctx_t *ctx, fastcgi_cnx_t *fcgi_cnx) {
int chunked = strcontains(http_get_header_field(&ctx->res.hdr, "Transfer-Encoding"), "chunked");
handle_chunks(ctx, &fcgi_cnx->out, chunked ? SOCK_CHUNKED : 0, fastcgi_next_cb, fastcgi_error_cb);
return 1;
}

100
src/worker/func.h Normal file
View File

@ -0,0 +1,100 @@
/**
* sesimos - secure, simple, modern web server
* @brief Worker function header file
* @file src/worker/func.h
* @author Lorenz Stechauner
* @date 2022-12-29
*/
#ifndef SESIMOS_FUNC_H
#define SESIMOS_FUNC_H
#include "../defs.h"
#include "../lib/sock.h"
#include "../lib/http.h"
#include "../lib/uri.h"
#include "../lib/config.h"
#include "../lib/proxy.h"
#include "../lib/fastcgi.h"
typedef struct {
sock socket;
int req_num;
unsigned char s_keep_alive:1, c_keep_alive:1, use_fastcgi:4, use_proxy:2, ws_close:2, chunks_transferred:1;
char cc[3], host[256];
char req_host[256], err_msg[256];
char log_prefix[128];
char _c_addr[INET6_ADDRSTRLEN + 1], _s_addr[INET6_ADDRSTRLEN + 1];
long cnx_s, cnx_e, req_s, res_ts, req_e;
http_req req;
http_res res;
http_uri uri;
http_status_ctx status;
http_status custom_status;
host_config_t *conf;
FILE *file;
long content_length;
char *msg_buf, *msg_buf_ptr, msg_content[1024];
proxy_ctx_t *proxy;
void *fcgi_ctx;
} client_ctx_t;
typedef struct {
client_ctx_t *client;
sock *socket;
void *other;
} ws_ctx_t;
typedef struct {
unsigned char closed:4;
client_ctx_t *client;
fastcgi_cnx_t cnx;
} fastcgi_ctx_t;
typedef struct {
client_ctx_t *client;
sock *socket;
int flags;
void (*next_cb)(void *);
void (*err_cb)(void *);
} chunk_ctx_t;
void tcp_acceptor_func(client_ctx_t *ctx);
void request_handler_func(client_ctx_t *ctx);
void local_handler_func(client_ctx_t *ctx);
void fastcgi_handler_func(client_ctx_t *ctx);
void fastcgi_frame_handler_func(fastcgi_ctx_t *ctx);
void proxy_handler_func(client_ctx_t *ctx);
void proxy_peer_handler_func(proxy_ctx_t *ctx);
void ws_frame_handler_func(ws_ctx_t *ctx);
void chunk_handler_func(chunk_ctx_t *ctx);
int respond(client_ctx_t *ctx);
void request_complete(client_ctx_t *ctx);
void timeout_request(client_ctx_t *ctx);
void tcp_close(client_ctx_t *ctx);
int ws_handle_connection(client_ctx_t *ctx);
void ws_close(ws_ctx_t *ctx);
int handle_chunks(client_ctx_t *ctx, sock *socket, int flags, void (*next_cb)(chunk_ctx_t *), void (*err_cb)(chunk_ctx_t *));
int fastcgi_handle_connection(client_ctx_t *ctx, fastcgi_cnx_t **cnx);
void fastcgi_close(fastcgi_ctx_t *ctx);
void fastcgi_close_error(fastcgi_ctx_t *ctx);
#endif //SESIMOS_FUNC_H

257
src/worker/local_handler.c Normal file
View File

@ -0,0 +1,257 @@
/**
* sesimos - secure, simple, modern web server
* @brief Local filesystem handler
* @file src/worker/local_handler.c
* @author Lorenz Stechauner
* @date 2022-12-29
*/
#include "func.h"
#include "../logger.h"
#include "../lib/utils.h"
#include "../lib/compress.h"
#include "../workers.h"
#include "../lib/list.h"
#include <string.h>
#include <errno.h>
static int local_handler(client_ctx_t *ctx);
void local_handler_func(client_ctx_t *ctx) {
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->req_host, CLR_STR, ctx->log_prefix);
switch (local_handler(ctx)) {
case 0:
respond(ctx);
request_complete(ctx);
handle_request(ctx);
break;
case 1:
fastcgi_handle(ctx);
break;
default:
tcp_close(ctx);
break;
}
}
static int range_handler(client_ctx_t *ctx) {
char buf[64];
long num0, num1, num2;
char *ptr;
int mode;
const char *range = http_get_header_field(&ctx->req.hdr, "Range");
if (strcontains(range, ","))
return -1;
ctx->file = fopen(ctx->uri.filename, "rb");
if (strstarts(range, "bytes=")) {
mode = 0;
range += 6;
num0 = fsize(ctx->file), num1 = 0, num2 = num0 - 1;
sprintf(buf, "bytes */%li", num0);
} else if (strstarts(range, "lines=") && mime_is_text(ctx->uri.meta->type)) {
mode = 1;
range += 6;
num0 = flines(ctx->file), num1 = 1, num2 = num0;
sprintf(buf, "lines */%li", num0);
} else {
return -1;
}
http_add_header_field(&ctx->res.hdr, "Content-Range", buf);
if ((ptr = strchr(range, '-')) == NULL)
return -1;
if (num0 == 0) {
ctx->content_length = 0;
return 0;
}
if (ptr[1] != 0) num2 = (long) strtoul(ptr + 1, NULL, 10);
if (ptr != range) {
num1 = (long) strtoul(range, NULL, 10);
} else {
if (mode == 0) {
num1 = num0 - num2 - 1;
num2 = num0 - 1;
} else if (mode == 1) {
num1 = num0 - num2 + 1;
num2 = num0;
}
}
if ((mode == 0 && (num1 >= num0 || num2 >= num0 || num1 > num2 || num1 < 0 || num2 < 0)) ||
(mode == 1 && (num1 > num0 || num2 > num0 || num1 > num2 || num1 <= 0 || num2 <= 0)))
{
return -1;
}
sprintf(buf, "%s %li-%li/%li", (mode == 0) ? "bytes" : "lines", num1, num2, num0);
http_remove_header_field(&ctx->res.hdr, "Content-Range", HTTP_REMOVE_ALL);
http_add_header_field(&ctx->res.hdr, "Content-Range", buf);
if (mode == 0) {
fseek(ctx->file, num1, SEEK_SET);
ctx->content_length = num2 - num1 + 1;
} else if (mode == 1) {
fseekl(ctx->file, num1);
ctx->content_length = file_get_line_pos(ctx->file, num2 + 1) - file_get_line_pos(ctx->file, num1);
}
return 0;
}
static int local_handler(client_ctx_t *ctx) {
http_res *res = &ctx->res;
http_req *req = &ctx->req;
http_uri *uri = &ctx->uri;
char *err_msg = ctx->err_msg;
char buf1[1024], buf2[1024];
int accept_if_modified_since = 0;
if (streq(req->method, "TRACE")) {
res->status = http_get_status(200);
http_add_header_field(&res->hdr, "Content-Type", "message/http");
ctx->msg_buf_ptr = malloc(4096);
ctx->msg_buf = ctx->msg_buf_ptr;
ctx->content_length = snprintf(ctx->msg_buf, 4096 - ctx->content_length, "%s %s HTTP/%s\r\n", req->method, req->uri, req->version);
for (int i = 0; i < list_size(req->hdr.fields); i++) {
const http_field *f = &req->hdr.fields[i];
ctx->content_length += snprintf(ctx->msg_buf + ctx->content_length, 4096 - ctx->content_length, "%s: %s\r\n", http_field_get_name(f), http_field_get_value(f));
}
return 0;
}
if (strstarts(uri->req_path, "/.well-known/")) {
http_add_header_field(&res->hdr, "Access-Control-Allow-Origin", "*");
}
if ((!strstarts(uri->req_path, "/.well-known/") && strcontains(uri->path, "/.")) || strends(uri->filename, ".inc") || strends(uri->filename, ".inc.php")) {
res->status = http_get_status(403);
sprintf(err_msg, "Parts of this URI are hidden.");
return 0;
} else if (uri->filename == NULL && (int) uri->is_static && (int) uri->is_dir && strlen(uri->pathinfo) == 0) {
res->status = http_get_status(403);
sprintf(err_msg, "It is not allowed to list the contents of this directory.");
return 0;
} else if (uri->filename == NULL && (int) !uri->is_static && (int) uri->is_dir && strlen(uri->pathinfo) == 0) {
// TODO list directory contents
res->status = http_get_status(501);
sprintf(err_msg, "Listing contents of an directory is currently not implemented.");
return 0;
} else if (uri->filename == NULL || (strlen(uri->pathinfo) > 0 && (int) uri->is_static)) {
res->status = http_get_status(404);
return 0;
} else if (strlen(uri->pathinfo) != 0 && ctx->conf->local.dir_mode != URI_DIR_MODE_INFO) {
res->status = http_get_status(404);
return 0;
}
if (!uri->is_static) {
return 1;
}
const char *client_expect = http_get_header_field(&req->hdr, "Expect");
if (client_expect != NULL && strcasecmp(client_expect, "100-continue") != 0) {
res->status = http_get_status(417);
return 0;
}
res->status = http_get_status(200);
cache_init_uri(ctx->conf->cache, uri);
http_add_header_field(&res->hdr, "Accept-Ranges", mime_is_text(uri->meta->type) ? "bytes, lines" : "bytes");
if (!streq(req->method, "GET") && !streq(req->method, "HEAD")) {
res->status = http_get_status(405);
return 0;
}
if (http_get_header_field(&req->hdr, "Content-Length") != NULL || http_get_header_field(&req->hdr, "Transfer-Encoding") != NULL) {
res->status = http_get_status(400);
sprintf(err_msg, "A GET request must not contain a payload");
return 0;
}
const char *last_modified = http_format_date(uri->meta->mtime, buf1, sizeof(buf1));
http_add_header_field(&res->hdr, "Last-Modified", last_modified);
sprintf(buf2, "%s; charset=%s", uri->meta->type, uri->meta->charset);
http_add_header_field(&res->hdr, "Content-Type", buf2);
const char *accept_encoding = http_get_header_field(&req->hdr, "Accept-Encoding");
int enc = 0;
if (accept_encoding != NULL) {
if (uri->meta->filename_comp_br[0] != 0 && strcontains(accept_encoding, "br")) {
ctx->file = fopen(uri->meta->filename_comp_br, "rb");
if (ctx->file == NULL) {
cache_mark_dirty(ctx->conf->cache, uri->filename);
errno = 0;
} else {
http_add_header_field(&res->hdr, "Content-Encoding", "br");
enc = COMPRESS_BR;
}
} else if (uri->meta->filename_comp_gz[0] != 0 && strcontains(accept_encoding, "gzip")) {
ctx->file = fopen(uri->meta->filename_comp_gz, "rb");
if (ctx->file == NULL) {
cache_mark_dirty(ctx->conf->cache, uri->filename);
errno = 0;
} else {
http_add_header_field(&res->hdr, "Content-Encoding", "gzip");
enc = COMPRESS_GZ;
}
}
if (enc != 0) {
http_add_header_field(&res->hdr, "Vary", "Accept-Encoding");
}
}
if (uri->meta->etag[0] != 0) {
strcpy(buf1, uri->meta->etag);
if (enc) {
strcat(buf1, "-");
strcat(buf1, (enc & COMPRESS_BR) ? "br" : (enc & COMPRESS_GZ) ? "gzip" : "");
}
http_add_header_field(&res->hdr, "ETag", buf1);
}
http_add_header_field(&res->hdr, "Cache-Control", mime_is_text(uri->meta->type) ? "public, max-age=3600" : "public, max-age=86400");
const char *if_modified_since = http_get_header_field(&req->hdr, "If-Modified-Since");
const char *if_none_match = http_get_header_field(&req->hdr, "If-None-Match");
if ((if_none_match != NULL && !strcontains(if_none_match, uri->meta->etag)) ||
(accept_if_modified_since && streq(if_modified_since, last_modified)))
{
res->status = http_get_status(304);
return 0;
}
if (http_get_header_field(&req->hdr, "Range") != NULL) {
if (range_handler(ctx) == 0) {
res->status = http_get_status(206);
} else {
if (ctx->file) {
fclose(ctx->file);
ctx->file = NULL;
}
http_remove_header_field(&res->hdr, "Content-Type", HTTP_REMOVE_ALL);
http_remove_header_field(&res->hdr, "Last-Modified", HTTP_REMOVE_ALL);
http_remove_header_field(&res->hdr, "ETag", HTTP_REMOVE_ALL);
http_remove_header_field(&res->hdr, "Cache-Control", HTTP_REMOVE_ALL);
res->status = http_get_status(416);
}
return 0;
}
if (ctx->file == NULL) ctx->file = fopen(uri->filename, "rb");
ctx->content_length = fsize(ctx->file);
return 0;
}

157
src/worker/proxy_handler.c Normal file
View File

@ -0,0 +1,157 @@
/**
* sesimos - secure, simple, modern web server
* @brief Proxy handler
* @file src/worker/proxy_handler.c
* @author Lorenz Stechauner
* @date 2022-12-29
*/
#include "func.h"
#include "../logger.h"
#include "../lib/utils.h"
#include "../lib/proxy.h"
#include "../lib/websocket.h"
#include "../workers.h"
#include <string.h>
static int proxy_handler_1(client_ctx_t *ctx);
static int proxy_handler_2(client_ctx_t *ctx);
void proxy_handler_func(client_ctx_t *ctx) {
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->req_host, CLR_STR, ctx->log_prefix);
// TODO handle 1xx responses
int ret = proxy_handler_1(ctx);
respond(ctx);
if (ret == 1) {
// error status code
if (proxy_unlock_ctx(ctx->proxy) == 1)
proxy_peer_handle(ctx->proxy);
} else if (ctx->use_proxy == 0) {
// proxy not used
proxy_close(ctx->proxy);
proxy_unlock_ctx(ctx->proxy);
} else if (ctx->use_proxy == 1) {
// proxy is used
if (proxy_handler_2(ctx) == 1) {
// chunked
return;
}
if (proxy_unlock_ctx(ctx->proxy) == 1)
proxy_peer_handle(ctx->proxy);
} else if (ctx->use_proxy == 2) {
// WebSocket
ws_handle_connection(ctx);
return;
}
ctx->proxy = NULL;
request_complete(ctx);
handle_request(ctx);
}
static int proxy_handler_1(client_ctx_t *ctx) {
http_res *res = &ctx->res;
http_status_ctx *status = &ctx->status;
char buf[1024];
info("Reverse proxy for " BLD_STR "[%s]:%i" CLR_STR, ctx->conf->proxy.hostname, ctx->conf->proxy.port);
http_remove_header_field(&res->hdr, "Date", HTTP_REMOVE_ALL);
http_remove_header_field(&res->hdr, "Server", HTTP_REMOVE_ALL);
ctx->use_proxy = proxy_init(&ctx->proxy, &ctx->req, res, status, ctx->conf, &ctx->socket, &ctx->custom_status, ctx->err_msg) == 0;
ctx->proxy->client = ctx;
if (ctx->use_proxy == 0)
return 0;
if (res->status->code == 101) {
const char *connection = http_get_header_field(&res->hdr, "Connection");
const char *upgrade = http_get_header_field(&res->hdr, "Upgrade");
if (connection != NULL && upgrade != NULL &&
(strcontains(connection, "upgrade") || strcontains(connection, "Upgrade")) &&
streq(upgrade, "websocket"))
{
const char *ws_accept = http_get_header_field(&res->hdr, "Sec-WebSocket-Accept");
if (ws_calc_accept_key(status->ws_key, buf) == 0) {
ctx->use_proxy = streq(buf, ws_accept) ? 2 : 1;
}
} else {
status->status = 101;
status->origin = INTERNAL;
res->status = http_get_status(501);
}
}
// Let 300 be formatted by origin server
if (ctx->use_proxy && res->status->code >= 301 && res->status->code < 600) {
const char *content_type = http_get_header_field(&res->hdr, "Content-Type");
const char *content_length_f = http_get_header_field(&res->hdr, "Content-Length");
const char *content_encoding = http_get_header_field(&res->hdr, "Content-Encoding");
const char *transfer_encoding = http_get_header_field(&res->hdr, "Transfer-Encoding");
if (transfer_encoding == NULL && content_encoding == NULL && (
content_length_f == NULL ||
streq(content_length_f, "0") ||
(content_length_f != NULL && strstarts(content_type, "text/html"))))
{
long content_len = (!streq(ctx->req.method, "HEAD") && content_length_f != NULL) ? strtol(content_length_f, NULL, 10) : 0;
if (content_len < sizeof(ctx->msg_content)) {
if (status->status != 101) {
status->status = res->status->code;
status->origin = res->status->code >= 400 ? SERVER : NONE;
}
ctx->use_proxy = 0;
if (content_len > 0)
proxy_dump(ctx->proxy, ctx->msg_content, content_len);
return 1;
}
}
}
return streq(ctx->req.method, "HEAD") ? 1 : 0;
}
static void proxy_chunk_next_cb(chunk_ctx_t *ctx) {
if (proxy_unlock_ctx(ctx->client->proxy) == 1)
proxy_peer_handle(ctx->client->proxy);
ctx->client->proxy = NULL;
request_complete(ctx->client);
handle_request(ctx->client);
}
static void proxy_chunk_err_cb(chunk_ctx_t *ctx) {
ctx->client->c_keep_alive = 0;
proxy_close(ctx->client->proxy);
proxy_unlock_ctx(ctx->client->proxy);
ctx->client->proxy = NULL;
request_complete(ctx->client);
handle_request(ctx->client);
}
static int proxy_handler_2(client_ctx_t *ctx) {
const char *transfer_encoding = http_get_header_field(&ctx->res.hdr, "Transfer-Encoding");
int chunked = strcontains(transfer_encoding, "chunked");
const char *content_len = http_get_header_field(&ctx->res.hdr, "Content-Length");
unsigned long len_to_send = (content_len != NULL) ? strtol(content_len, NULL, 10) : 0;
if (chunked) {
handle_chunks(ctx, &ctx->proxy->proxy, SOCK_CHUNKED, proxy_chunk_next_cb, proxy_chunk_err_cb);
return 1;
}
int ret;
if ((ret = proxy_send(ctx->proxy, &ctx->socket, len_to_send, 0)) == -1) {
ctx->c_keep_alive = 0;
}
return ret;
}

View File

@ -0,0 +1,17 @@
/**
* sesimos - secure, simple, modern web server
* @brief Proxy peer handler
* @file src/worker/proxy_peer_handler.c
* @author Lorenz Stechauner
* @date 2023-07-07
*/
#include "func.h"
#include "../logger.h"
#include "../lib/utils.h"
void proxy_peer_handler_func(proxy_ctx_t *ctx) {
if (!ctx->initialized || ctx->in_use) return;
logger_set_prefix("[%s%*s%s]", BLD_STR, ADDRSTRLEN, ctx->host, CLR_STR);
proxy_close(ctx);
}

View File

@ -0,0 +1,397 @@
/**
* sesimos - secure, simple, modern web server
* @brief Client request handler
* @file src/worker/request_handler.c
* @author Lorenz Stechauner
* @date 2022-12-28
*/
#include "../defs.h"
#include "func.h"
#include "../workers.h"
#include "../lib/mpmc.h"
#include "../logger.h"
#include "../lib/utils.h"
#include "../server.h"
#include "../lib/res.h"
#include "../lib/error.h"
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>
static int request_handler(client_ctx_t *ctx);
void request_handler_func(client_ctx_t *ctx) {
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->socket.s_addr, ctx->log_prefix);
switch (request_handler(ctx)) {
case 0:
respond(ctx);
request_complete(ctx);
handle_request(ctx);
break;
case 1:
local_handle(ctx);
break;
case 2:
proxy_handle(ctx);
break;
default:
tcp_close(ctx);
break;
}
}
static void init_ctx(client_ctx_t *ctx) {
ctx->conf = NULL;
ctx->file = NULL;
ctx->proxy = NULL;
ctx->use_fastcgi = 0;
ctx->chunks_transferred = 0;
ctx->fcgi_ctx = NULL;
ctx->use_proxy = 0;
ctx->ws_close = 0;
ctx->proxy = NULL;
ctx->msg_content[0] = 0;
ctx->msg_buf = NULL;
ctx->msg_buf_ptr = NULL;
ctx->req_host[0] = 0;
ctx->err_msg[0] = 0;
ctx->req_s = ctx->socket.ts_last;
memset(&ctx->uri, 0, sizeof(ctx->uri));
memset(&ctx->req, 0, sizeof(ctx->req));
memset(&ctx->res, 0, sizeof(ctx->res));
ctx->res.status = http_get_status(501);
http_init_hdr(&ctx->res.hdr);
sprintf(ctx->res.version, "1.1");
ctx->status.status = 0;
ctx->status.origin = NONE;
ctx->status.ws_key = NULL;
}
static int request_handler(client_ctx_t *ctx) {
sock *client = &ctx->socket;
char *err_msg = ctx->err_msg;
http_res *res = &ctx->res;
long ret;
char buf0[1024], buf1[1024];
ctx->req_s = clock_micros();
init_ctx(ctx);
http_add_header_field(&res->hdr, "Date", http_get_date(buf0, sizeof(buf0)));
http_add_header_field(&res->hdr, "Server", SERVER_STR);
/*if (ret <= 0) {
if (errno != 0) return 0;
ctx->c_keep_alive = 0;
res->status = http_get_status(408);
return 0;
}*/
http_req *req = &ctx->req;
ret = http_receive_request(client, req);
if (ret != 0) {
ctx->c_keep_alive = 0;
error("Unable to receive http header");
sprintf(err_msg, "Unable to receive http header: %s.", error_str(errno, buf0, sizeof(buf0)));
int err = error_get_http();
res->status = http_get_status(err == HTTP_ERROR_URI_TOO_LONG ? 414 : (err == HTTP_ERROR_TOO_MANY_HEADER_FIELDS ? 431 : 400));
errno = 0;
return 0;
}
const char *hdr_connection = http_get_header_field(&req->hdr, "Connection");
ctx->c_keep_alive = (strcontains(hdr_connection, "keep-alive") || strcontains(hdr_connection, "Keep-Alive"));
const char *host_ptr = http_get_header_field(&req->hdr, "Host");
if (host_ptr != NULL && strlen(host_ptr) > 255) {
ctx->req_host[0] = 0;
res->status = http_get_status(400);
sprintf(err_msg, "Host header field is too long.");
return 0;
} else if (host_ptr == NULL || strchr(host_ptr, '/') != NULL) {
if (strchr(ctx->socket.addr, ':') == NULL) {
strcpy(ctx->req_host, ctx->socket.addr);
} else {
sprintf(ctx->req_host, "[%s]", ctx->socket.addr);
}
res->status = http_get_status(400);
sprintf(err_msg, "The client provided no or an invalid Host header field.");
return 0;
} else {
strcpy(ctx->req_host, host_ptr);
}
logger_set_prefix("[%s%*s%s]%s", BLD_STR, ADDRSTRLEN, ctx->req_host, CLR_STR, ctx->log_prefix);
info(BLD_STR "%s %s", req->method, req->uri);
if (strstarts(req->uri, "/.sesimos/res/")) {
if (!streq(req->method, "GET") && !streq(req->method, "HEAD")) {
res->status = http_get_status(405);
http_add_header_field(&res->hdr, "Allow", "GET, HEAD");
return 0;
}
const res_t resources[] = {
{"style.css", "text/css; charset=UTF-8", http_style_doc, http_style_doc_size},
{"icon-error.svg", "image/svg+xml; charset=UTF-8", http_icon_error, http_icon_error_size},
{"icon-info.svg", "image/svg+xml; charset=UTF-8", http_icon_info, http_icon_info_size},
{"icon-success.svg", "image/svg+xml; charset=UTF-8", http_icon_success, http_icon_success_size},
{"icon-warning.svg", "image/svg+xml; charset=UTF-8", http_icon_warning, http_icon_warning_size},
{"globe.svg", "image/svg+xml; charset=UTF-8", http_icon_globe, http_icon_globe_size},
};
res->status = http_get_status(404);
for (int i = 0; i < sizeof(resources) / sizeof(res_t); i++) {
const res_t *r = &resources[i];
if (streq(req->uri + 14, r->name)) {
res->status = http_get_status(203);
http_add_header_field(&res->hdr, "Content-Type", r->type);
http_add_header_field(&res->hdr, "Cache-Control", "public, max-age=86400");
ctx->msg_buf = (char *) r->content;
ctx->content_length = r->size;
break;
}
}
return 0;
}
ctx->conf = get_host_config(ctx->req_host);
if (ctx->conf == NULL) {
res->status = http_get_status(421);
strcpy(ctx->err_msg, "The requested host name is not configured on the server.");
return 0;
}
http_uri *uri = &ctx->uri;
unsigned char dir_mode = (ctx->conf->type == CONFIG_TYPE_LOCAL ? ctx->conf->local.dir_mode : URI_DIR_MODE_NO_VALIDATION);
ret = uri_init(uri, ctx->conf->local.webroot, req->uri, dir_mode);
if (ret != 0) {
if (ret == 1) {
sprintf(err_msg, "Invalid URI: has to start with slash.");
res->status = http_get_status(400);
} else if (ret == 2) {
sprintf(err_msg, "Invalid URI: contains relative path change (/../).");
res->status = http_get_status(400);
} else if (ret == 3) {
sprintf(err_msg, "The specified webroot directory does not exist.");
res->status = http_get_status(404);
} else {
res->status = http_get_status(500);
}
return 0;
}
if (ctx->conf->type == CONFIG_TYPE_LOCAL && streq(req->method, "TRACE")) {
return 1;
} else if (dir_mode != URI_DIR_MODE_NO_VALIDATION) {
ssize_t size = sizeof(buf0);
url_decode(req->uri, buf0, &size);
int change_proto = (!client->enc && !strstarts(uri->uri, "/.well-known/"));
if (!streq(uri->uri, buf0) || change_proto) {
res->status = http_get_status(308);
size = url_encode(uri->uri, strlen(uri->uri), buf0, sizeof(buf0));
if (change_proto) {
int p_len = snprintf(buf1, sizeof(buf1), "https://%s%s", ctx->req_host, buf0);
if (p_len < 0 || p_len >= sizeof(buf1)) {
res->status = http_get_status(500);
error("Header field 'Location' too long");
return 0;
}
http_add_header_field(&res->hdr, "Location", buf1);
} else {
http_add_header_field(&res->hdr, "Location", buf0);
}
return 0;
}
} else if (!client->enc) {
res->status = http_get_status(308);
sprintf(buf0, "https://%s%s", ctx->req_host, req->uri);
http_add_header_field(&res->hdr, "Location", buf0);
return 0;
}
if (ctx->conf->type == CONFIG_TYPE_LOCAL) {
return 1;
} else if (ctx->conf->type == CONFIG_TYPE_REVERSE_PROXY) {
return 2;
} else {
error("Unknown host type: %i", ctx->conf->type);
res->status = http_get_status(501);
}
return 0;
}
int respond(client_ctx_t *ctx) {
http_req *req = &ctx->req;
http_res *res = &ctx->res;
sock *client = &ctx->socket;
http_status_ctx *status = &ctx->status;
char *err_msg = ctx->err_msg;
long ret = 0;
char buf0[1024];
char msg_pre_buf_1[4096], msg_pre_buf_2[4096];
char buffer[CHUNK_SIZE];
if (!ctx->use_proxy) {
if (ctx->conf != NULL && ctx->conf->type == CONFIG_TYPE_LOCAL && ctx->uri.is_static && res->status->code == 405) {
http_add_header_field(&res->hdr, "Allow", "GET, HEAD, TRACE");
}
if (http_get_header_field(&res->hdr, "Accept-Ranges") == NULL) {
http_add_header_field(&res->hdr, "Accept-Ranges", "none");
}
if (!ctx->use_fastcgi && ctx->file == NULL && ctx->msg_buf == NULL) {
http_remove_header_field(&res->hdr, "Date", HTTP_REMOVE_ALL);
http_remove_header_field(&res->hdr, "Server", HTTP_REMOVE_ALL);
http_remove_header_field(&res->hdr, "Cache-Control", HTTP_REMOVE_ALL);
http_remove_header_field(&res->hdr, "Content-Type", HTTP_REMOVE_ALL);
http_remove_header_field(&res->hdr, "Content-Encoding", HTTP_REMOVE_ALL);
http_add_header_field(&res->hdr, "Date", http_get_date(buf0, sizeof(buf0)));
http_add_header_field(&res->hdr, "Server", SERVER_STR);
http_add_header_field(&res->hdr, "Cache-Control", "no-cache");
http_add_header_field(&res->hdr, "Content-Type", "text/html; charset=UTF-8");
// TODO list Locations on 3xx Redirects
const http_doc_info *http_info = http_get_status_info(res->status->code);
const http_status_msg *http_msg = http_get_error_msg(res->status->code);
if (ctx->msg_content[0] == 0) {
if (res->status->code >= 300 && res->status->code < 400) {
const char *location = http_get_header_field(&res->hdr, "Location");
if (location != NULL) {
snprintf(ctx->msg_content, sizeof(ctx->msg_content), " <ul>\n <li><a href=\"%s\">%s</a></li>\n </ul>\n", location, location);
}
}
} else if (strstarts(ctx->msg_content, "<!DOCTYPE html>") || strstarts(ctx->msg_content, "<html")) {
ctx->msg_content[0] = 0;
// TODO let relevant information pass?
}
char *proxy_doc = "";
if (ctx->conf != NULL && ctx->conf->type == CONFIG_TYPE_REVERSE_PROXY) {
const http_status *status_hdr = http_get_status(status->status);
char stat_str[8];
sprintf(stat_str, "%03i", status->status);
snprintf(msg_pre_buf_2, sizeof(msg_pre_buf_2), http_proxy_doc,
" success",
(status->origin == CLIENT_REQ) ? " error" : " success",
(status->origin == INTERNAL) ? " error" : " success",
(status->origin == SERVER_REQ) ? " error" : (status->status == 0 ? "" : " success"),
(status->origin == CLIENT_RES) ? " error" : " success",
(status->origin == SERVER) ? " error" : (status->status == 0 ? "" : " success"),
(status->origin == SERVER_RES) ? " error" : (status->status == 0 ? "" : " success"),
(status->origin == INTERNAL) ? " error" : " success",
(status->origin == INTERNAL || status->origin == SERVER) ? " error" : " success",
res->status->code,
res->status->msg,
(status->status == 0) ? "???" : stat_str,
(status_hdr != NULL) ? status_hdr->msg : "",
ctx->req_host, SERVER_NAME);
proxy_doc = msg_pre_buf_2;
}
ctx->msg_buf_ptr = malloc(4096);
ctx->msg_buf = ctx->msg_buf_ptr;
snprintf(msg_pre_buf_1, sizeof(msg_pre_buf_1), http_info->doc,
res->status->code, res->status->msg, http_msg != NULL ? http_msg->msg : "", err_msg);
ctx->content_length = snprintf(ctx->msg_buf, 4096, http_default_doc, res->status->code, res->status->msg,
msg_pre_buf_1, http_info->mode, http_info->icon, http_info->color,
ctx->req_host, proxy_doc, ctx->msg_content, SERVER_STR_HTML);
}
if (ctx->content_length >= 0) {
sprintf(buf0, "%li", ctx->content_length);
http_remove_header_field(&res->hdr, "Content-Length", HTTP_REMOVE_ALL);
http_add_header_field(&res->hdr, "Content-Length", buf0);
} else if (http_get_header_field(&res->hdr, "Transfer-Encoding") == NULL) {
ctx->s_keep_alive = 0;
}
}
if (ctx->use_proxy != 2) {
http_remove_header_field(&res->hdr, "Connection", HTTP_REMOVE_ALL);
http_remove_header_field(&res->hdr, "Keep-Alive", HTTP_REMOVE_ALL);
if (ctx->s_keep_alive && ctx->c_keep_alive) {
http_add_header_field(&res->hdr, "Connection", "keep-alive");
sprintf(buf0, "timeout=%i, max=%i", CLIENT_TIMEOUT, REQ_PER_CONNECTION);
http_add_header_field(&res->hdr, "Keep-Alive", buf0);
} else {
http_add_header_field(&res->hdr, "Connection", "close");
}
}
http_send_response(client, res);
ctx->res_ts = clock_micros();
const char *location = http_get_header_field(&res->hdr, "Location");
info("%s%s%03i %s%s%s (%s)%s", http_get_status_color(res->status->code), ctx->use_proxy ? "-> " : "", res->status->code,
res->status->msg, location != NULL ? " -> " : "", location != NULL ? location : "",
format_duration(ctx->res_ts - ctx->req_s, buf0), CLR_STR);
// TODO access/error log file
if (ctx->use_proxy) {
// reverse proxy
return 3;
} else if (!streq(req->method, "HEAD")) {
// default response
if (ctx->msg_buf != NULL) {
ret = sock_send_x(client, ctx->msg_buf, ctx->content_length, 0);
if (ret <= 0) {
error("Unable to send");
}
} else if (ctx->file != NULL) {
unsigned long len, snd_len = 0;
while (snd_len < ctx->content_length) {
len = fread(buffer, 1, CHUNK_SIZE, ctx->file);
if (snd_len + len > ctx->content_length) {
len = ctx->content_length - snd_len;
}
ret = sock_send_x(client, buffer, len, feof(ctx->file) ? 0 : MSG_MORE);
if (ret <= 0) {
error("Unable to send");
break;
}
snd_len += ret;
}
} else if (ctx->use_fastcgi) {
// FastCGI
return 2;
}
if (ret < 0) ctx->c_keep_alive = 0;
}
return 0;
}
void request_complete(client_ctx_t *ctx) {
char buf[32];
ctx->req_e = clock_micros();
info("Transfer complete: %s", format_duration(ctx->req_e - ctx->req_s, buf));
if (ctx->file) fclose(ctx->file);
free(ctx->msg_buf_ptr);
uri_free(&ctx->uri);
http_free_req(&ctx->req);
http_free_res(&ctx->res);
}
void timeout_request(client_ctx_t *ctx) {
init_ctx(ctx);
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->socket.s_addr, ctx->log_prefix);
ctx->s_keep_alive = 0;
ctx->res.status = http_get_status(408);
respond(ctx);
request_complete(ctx);
tcp_close(ctx);
}

109
src/worker/tcp_acceptor.c Normal file
View File

@ -0,0 +1,109 @@
/**
* sesimos - secure, simple, modern web server
* @brief TCP acceptor
* @file src/worker/tcp_acceptor.c
* @author Lorenz Stechauner
* @date 2022-12-28
*/
#include "func.h"
#include "../logger.h"
#include "../lib/utils.h"
#include "../lib/geoip.h"
#include "../workers.h"
#include "../server.h"
#include "../lib/error.h"
#include <string.h>
#include <errno.h>
#include <openssl/ssl.h>
static int tcp_acceptor(client_ctx_t *ctx);
void tcp_acceptor_func(client_ctx_t *ctx) {
if (tcp_acceptor(ctx) == 0) {
handle_request(ctx);
} else {
tcp_close(ctx);
}
}
static int tcp_acceptor(client_ctx_t *ctx) {
struct sockaddr_in6 server_addr;
memset(ctx->_c_addr, 0, sizeof(ctx->_c_addr));
memset(ctx->_s_addr, 0, sizeof(ctx->_s_addr));
inet_ntop(ctx->socket._addr.ipv6.sin6_family, &ctx->socket._addr.ipv6.sin6_addr, ctx->_c_addr, sizeof(ctx->_c_addr));
if (strstarts(ctx->_c_addr, "::ffff:")) {
ctx->socket.addr = ctx->_c_addr + 7;
} else {
ctx->socket.addr = ctx->_c_addr;
}
socklen_t len = sizeof(server_addr);
getsockname(ctx->socket.socket, (struct sockaddr *) &server_addr, &len);
inet_ntop(server_addr.sin6_family, (void *) &server_addr.sin6_addr, ctx->_s_addr, sizeof(ctx->_s_addr));
if (strstarts(ctx->_s_addr, "::ffff:")) {
ctx->socket.s_addr = ctx->_s_addr + 7;
} else {
ctx->socket.s_addr = ctx->_s_addr;
}
sprintf(ctx->log_prefix, "[%s%4i%s]%s[%*s][%5i]%s", (int) ctx->socket.enc ? HTTPS_STR : HTTP_STR,
ntohs(server_addr.sin6_port), CLR_STR, /*color_table[0]*/ "", ADDRSTRLEN, ctx->socket.addr,
ntohs(ctx->socket._addr.ipv6.sin6_port), CLR_STR);
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->socket.s_addr, ctx->log_prefix);
sock *client = &ctx->socket;
ctx->cnx_s = clock_micros();
sock_reverse_lookup(&ctx->socket, ctx->host, sizeof(ctx->host));
ctx->cc[0] = 0;
geoip_lookup_country(&client->_addr.sock, ctx->cc);
info("Connection accepted from %s %s%s%s[%s]", ctx->socket.addr, ctx->host[0] != 0 ? "(" : "",
ctx->host[0] != 0 ? ctx->host : "", ctx->host[0] != 0 ? ") " : "",
ctx->cc[0] != 0 ? ctx->cc : "N/A");
if (sock_set_socket_timeout(client, SOCKET_TIMEOUT) != 0 || sock_set_timeout(client, CLIENT_TIMEOUT) != 0) {
error("Unable to set timeout for socket");
return -1;
}
if (client->enc) {
client->ssl = SSL_new(client->ctx);
SSL_set_fd(client->ssl, client->socket);
SSL_set_accept_state(client->ssl);
int ret;
if ((ret = SSL_accept(client->ssl)) != 1) {
sock_error(client, ret);
info("Unable to perform handshake");
return -1;
}
client->ts_last = clock_micros();
client->ts_last_send = client->ts_last;
}
ctx->req_num = 0;
ctx->s_keep_alive = 1;
ctx->c_keep_alive = 1;
ctx->chunks_transferred = 0;
return 0;
}
void tcp_close(client_ctx_t *ctx) {
errno = 0;
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->socket.s_addr, ctx->log_prefix);
sock_close(&ctx->socket);
ctx->cnx_e = clock_micros();
char buf[32];
info("Connection closed (%s)", format_duration(ctx->cnx_e - ctx->cnx_s, buf));
server_free_client(ctx);
}

View File

@ -0,0 +1,113 @@
/**
* sesimos - secure, simple, modern web server
* @brief WebSocket frame handler
* @file src/worker/ws_frame_handler.c
* @author Lorenz Stechauner
* @date 2022-12-30
*/
#include "../defs.h"
#include "func.h"
#include "../logger.h"
#include "../lib/websocket.h"
#include "../workers.h"
#include "../lib/utils.h"
#include <errno.h>
#include <string.h>
static int ws_frame_handler(ws_ctx_t *ctx);
void ws_frame_handler_func(ws_ctx_t *ctx) {
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
if (ws_frame_handler(ctx) == 0) {
if (ctx->client->ws_close == 3) {
ws_close(ctx);
} else {
ws_handle_frame(ctx);
}
} else {
ws_close(ctx);
}
}
int ws_handle_connection(client_ctx_t *ctx) {
info("Upgrading to WebSocket connection");
// copy proxy connection details
proxy_ctx_t *proxy = malloc(sizeof(proxy_ctx_t));
memcpy(proxy, ctx->proxy, sizeof(proxy_ctx_t));
// free proxy connection slot
ctx->proxy->initialized = 0;
proxy_unlock_ctx(ctx->proxy);
ctx->proxy = proxy;
sock_set_timeout(&ctx->socket, WS_TIMEOUT);
sock_set_timeout(&proxy->proxy, WS_TIMEOUT);
ws_ctx_t *a = malloc(sizeof(ws_ctx_t));
ws_ctx_t *b = malloc(sizeof(ws_ctx_t));
a->other = b, b->other = a;
a->client = ctx, b->client = ctx;
a->socket = &ctx->socket, b->socket = &proxy->proxy;
ws_handle_frame(a);
ws_handle_frame(b);
return 0;
}
static int ws_frame_handler(ws_ctx_t *ctx) {
ws_frame frame;
char buf[CHUNK_SIZE];
sock *socket = ctx->socket;
sock *other = (ctx->socket == &ctx->client->socket) ? &ctx->client->proxy->proxy : &ctx->client->socket;
if (ws_recv_frame_header(socket, &frame) != 0)
return -1;
debug("WebSocket: Peer %s, Opcode=0x%X, Len=%li", (ctx->socket == &ctx->client->socket) ? "client" : "server", frame.opcode, frame.len);
if (frame.opcode == 0x8) {
ctx->client->ws_close |= (ctx->socket == &ctx->client->socket) ? 1 : 2;
}
if (ws_send_frame_header(other, &frame) != 0)
return -1;
if (frame.len > 0) {
long ret = sock_splice(other, socket, buf, sizeof(buf), frame.len);
if (ret < 0) {
error("Unable to forward data in WebSocket");
return -1;
} else if (ret != frame.len) {
error("Unable to forward correct number of bytes in WebSocket");
return -1;
}
}
return 0;
}
void ws_close(ws_ctx_t *ctx) {
ws_ctx_t *other = ctx->other;
if (other) {
proxy_ctx_t *proxy = ctx->client->proxy;
other->other = NULL;
logger_set_prefix("[%*s]%s", ADDRSTRLEN, ctx->client->socket.s_addr, ctx->client->log_prefix);
proxy->cnx_e = clock_micros();
char buf[32];
info("Closing WebSocket connection (%s)", format_duration(proxy->cnx_e - proxy->cnx_s, buf));
sock_close(&proxy->proxy);
free(ctx->client->proxy);
tcp_close(ctx->client);
}
free(ctx);
errno = 0;
}

129
src/workers.c Normal file
View File

@ -0,0 +1,129 @@
/**
* sesimos - secure, simple, modern web server
* @brief Worker interface
* @file src/workers.c
* @author Lorenz Stechauner
* @date 2022-12-29
*/
#include "workers.h"
#include "lib/mpmc.h"
#include "worker/func.h"
#include "async.h"
static mpmc_t tcp_acceptor_ctx, request_handler_ctx, local_handler_ctx, fastcgi_handler_ctx, proxy_handler_ctx,
proxy_peer_handler_ctx, ws_frame_handler_ctx, chunk_handler_ctx, fastcgi_frame_handler_ctx;
int workers_init(void) {
mpmc_init(&tcp_acceptor_ctx, 8, 64, (void (*)(void *)) tcp_acceptor_func, "tcp");
mpmc_init(&request_handler_ctx, 8, 64, (void (*)(void *)) request_handler_func, "req");
mpmc_init(&local_handler_ctx, 8, 64, (void (*)(void *)) local_handler_func, "local");
mpmc_init(&fastcgi_handler_ctx, 8, 64, (void (*)(void *)) fastcgi_handler_func, "fcgi");
mpmc_init(&proxy_handler_ctx, 8, 64, (void (*)(void *)) proxy_handler_func, "proxy");
mpmc_init(&proxy_peer_handler_ctx, 1, 8, (void (*)(void *)) proxy_peer_handler_func, "prxy_p");
mpmc_init(&ws_frame_handler_ctx, 8, 64, (void (*)(void *)) ws_frame_handler_func, "ws");
mpmc_init(&chunk_handler_ctx, 8, 64, (void (*)(void *)) chunk_handler_func, "chunk");
mpmc_init(&fastcgi_frame_handler_ctx, 8, 64, (void (*)(void *)) fastcgi_frame_handler_func, "fcgi_f");
return -1;
}
void workers_stop(void) {
mpmc_stop(&tcp_acceptor_ctx);
mpmc_stop(&local_handler_ctx);
mpmc_stop(&fastcgi_handler_ctx);
mpmc_stop(&proxy_handler_ctx);
mpmc_stop(&proxy_peer_handler_ctx);
mpmc_stop(&request_handler_ctx);
mpmc_stop(&ws_frame_handler_ctx);
mpmc_stop(&chunk_handler_ctx);
mpmc_stop(&fastcgi_frame_handler_ctx);
}
void workers_destroy(void) {
mpmc_destroy(&tcp_acceptor_ctx);
mpmc_destroy(&local_handler_ctx);
mpmc_destroy(&fastcgi_handler_ctx);
mpmc_destroy(&proxy_handler_ctx);
mpmc_destroy(&proxy_peer_handler_ctx);
mpmc_destroy(&request_handler_ctx);
mpmc_destroy(&ws_frame_handler_ctx);
mpmc_destroy(&chunk_handler_ctx);
mpmc_destroy(&fastcgi_frame_handler_ctx);
}
int tcp_accept(client_ctx_t *ctx) {
return mpmc_queue(&tcp_acceptor_ctx, ctx);
}
static int handle_request_cb(client_ctx_t *ctx) {
return mpmc_queue(&request_handler_ctx, ctx);
}
int handle_request(client_ctx_t *ctx) {
if (ctx->c_keep_alive && ctx->s_keep_alive) {
return async(&ctx->socket, ASYNC_WAIT_READ, 0, ctx,
(void (*)(void *)) handle_request_cb,
(void (*)(void *)) timeout_request,
(void (*)(void *)) tcp_close);
} else {
tcp_close(ctx);
return 0;
}
}
int local_handle(client_ctx_t *ctx) {
return mpmc_queue(&local_handler_ctx, ctx);
}
int fastcgi_handle(client_ctx_t *ctx) {
return mpmc_queue(&fastcgi_handler_ctx, ctx);
}
static int fastcgi_handle_frame_cb(fastcgi_ctx_t *ctx) {
return mpmc_queue(&fastcgi_frame_handler_ctx, ctx);
}
int fastcgi_handle_frame(fastcgi_ctx_t *ctx) {
return async(&ctx->cnx.socket, ASYNC_WAIT_READ, 0, ctx,
(void (*)(void *)) fastcgi_handle_frame_cb,
(void (*)(void *)) fastcgi_close,
(void (*)(void *)) fastcgi_close);
}
int proxy_handle(client_ctx_t *ctx) {
return mpmc_queue(&proxy_handler_ctx, ctx);
}
static int proxy_peer_handle_cb(proxy_ctx_t *ctx) {
return mpmc_queue(&proxy_peer_handler_ctx, ctx);
}
int proxy_peer_handle(proxy_ctx_t *ctx) {
return async(&ctx->proxy, ASYNC_WAIT_READ, ASYNC_IGNORE_PENDING, ctx,
(void (*)(void *)) proxy_peer_handle_cb,
(void (*)(void *)) proxy_peer_handle_cb,
(void (*)(void *)) proxy_peer_handle_cb);
}
static int ws_handle_frame_cb(ws_ctx_t *ctx) {
return mpmc_queue(&ws_frame_handler_ctx, ctx);
}
int ws_handle_frame(ws_ctx_t *ctx) {
return async(ctx->socket, ASYNC_WAIT_READ, 0, ctx,
(void (*)(void *)) ws_handle_frame_cb,
(void (*)(void *)) ws_close,
(void (*)(void *)) ws_close);
}
static int handle_chunk_cb(chunk_ctx_t *ctx) {
return mpmc_queue(&chunk_handler_ctx, ctx);
}
int handle_chunk(chunk_ctx_t *ctx) {
return async(ctx->socket, ASYNC_WAIT_READ, 0, ctx,
(void (*)(void *)) handle_chunk_cb,
(void (*)(void *)) ctx->err_cb,
(void (*)(void *)) ctx->err_cb);
}

38
src/workers.h Normal file
View File

@ -0,0 +1,38 @@
/**
* sesimos - secure, simple, modern web server
* @brief Worker interface (header file)
* @file src/workers.h
* @author Lorenz Stechauner
* @date 2022-12-29
*/
#ifndef SESIMOS_WORKERS_H
#define SESIMOS_WORKERS_H
#include "worker/func.h"
int workers_init(void);
void workers_stop(void);
void workers_destroy(void);
int tcp_accept(client_ctx_t *ctx);
int handle_request(client_ctx_t *ctx);
int local_handle(client_ctx_t *ctx);
int fastcgi_handle(client_ctx_t *ctx);
int fastcgi_handle_frame(fastcgi_ctx_t *ctx);
int proxy_handle(client_ctx_t *ctx);
int proxy_peer_handle(proxy_ctx_t *ctx);
int ws_handle_frame(ws_ctx_t *ctx);
int handle_chunk(chunk_ctx_t *ctx);
#endif //SESIMOS_WORKERS_H

28
test/mock_socket.c Normal file
View File

@ -0,0 +1,28 @@
#include <stdio.h>
#include <errno.h>
#include "mock_socket.h"
int mock_socket_send_mode;
static int sockets[256] = {0};
static int n_sockets = 0;
int mock_socket(int domain, int type, int protocol) {
printf("SOCKET\n");
return (n_sockets++) + 100;
}
ssize_t mock_send(int fd, const void *buf, size_t n, int flags) {
printf("SEND\n");
if (mock_socket_send_mode == MOCK_SOCKET_MODE_EINTR) {
errno = EINTR;
return rand() % ((ssize_t) n) - 1;
} else if (mock_socket_send_mode == MOCK_SOCKET_MODE_CLOSED) {
errno = 0; // TODO
return -1;
}
return (ssize_t) n;
}

20
test/mock_socket.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef SESIMOS_MOCK_SOCKET_H
#define SESIMOS_MOCK_SOCKET_H
#include <stdlib.h>
#define MOCK_SOCKET_MODE_SUCCESS 0
#define MOCK_SOCKET_MODE_EINTR 1
#define MOCK_SOCKET_MODE_CLOSED 2
#define socket(args...) mock_socket(args)
#define send(args...) mock_send(args)
extern int mock_socket_send_mode;
int mock_socket(int domain, int type, int protocol);
ssize_t mock_send(int fd, const void *buf, size_t n, int flags);
#endif //SESIMOS_MOCK_SOCKET_H

33
test/mock_ssl.c Normal file
View File

@ -0,0 +1,33 @@
#include <openssl/crypto.h>
int SSL_write(SSL *ssl, const void *buf, int num) {
return num;
}
int SSL_read(SSL *ssl, void *buf, int num) {
return num;
}
int SSL_peek(SSL *ssl, void *buf, int num) {
return num;
}
int SSL_get_error(const SSL *s, int ret_code) {
return 0;
}
const char *ERR_reason_error_string(unsigned long e) {
return "";
}
int SSL_shutdown(SSL *s) {
return 0;
}
void SSL_free(SSL *ssl) {}
unsigned long ERR_get_error(void) {
return 0;
}

92
test/test_list.c Normal file
View File

@ -0,0 +1,92 @@
#include <criterion/criterion.h>
#include <criterion/parameterized.h>
#include "../src/lib/list.h"
Test(list, simple) {
int v;
int *list = list_create(sizeof(int), 16);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 0);
v = 1;
list = list_append(list, &v);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 1);
cr_assert_eq(list[0], 1);
v = 3;
list = list_append(list, &v);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 2);
cr_assert_eq(list[0], 1);
cr_assert_eq(list[1], 3);
v = 2;
list = list_insert(list, &v, 1);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 3);
cr_assert_eq(list[0], 1);
cr_assert_eq(list[1], 2);
cr_assert_eq(list[2], 3);
list = list_remove(list, 0);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 2);
cr_assert_eq(list[0], 2);
cr_assert_eq(list[1], 3);
list = list_remove(list, 1);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 1);
cr_assert_eq(list[0], 2);
list = list_remove(list, 0);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 0);
list_free(list);
}
Test(list, resize) {
int v;
int *list = list_create(sizeof(int), 4);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 0);
for (int i = 0; i < 4096; i++) {
v = 9182 - i;
list = list_append(list, &v);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), i + 1);
for (int j = 0; j <= i; j++) {
cr_assert_eq(list[j], 9182 - j);
}
}
for (int i = 0; i < 4096; i++) {
list = list_remove(list, -1);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 4096 - i - 1);
for (int j = 0; j < 4096 - i; j++) {
cr_assert_eq(list[j], 9182 - j);
}
}
for (int i = 0; i < 4096; i++) {
v = 47391 - i;
list = list_append(list, &v);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), i + 1);
for (int j = 0; j <= i; j++) {
cr_assert_eq(list[j], 47391 - j);
}
}
list = list_clear(list);
cr_assert_not_null(list);
cr_assert_eq(list_size(list), 0);
list_free(list);
}

15
test/test_sock.c Normal file
View File

@ -0,0 +1,15 @@
#include <criterion/criterion.h>
#include "mock_socket.h"
#include "../src/lib/sock.h"
Test(sock, sock_send_1) {
int fd = socket(AF_INET6, SOCK_STREAM, 0);
sock s;
s.enc = 0;
s.socket = fd;
long ret = sock_send(&s, "Hello", 5, 0);
cr_assert_eq(ret, 5);
}

86
test/test_utils.c Normal file
View File

@ -0,0 +1,86 @@
#include <criterion/criterion.h>
#include <criterion/parameterized.h>
#include "../src/lib/utils.h"
struct url_encode_t {
long in_size;
char in[256];
long exp_size;
char exp[256];
};
struct format_duration_t {
unsigned long micros;
char exp[16];
};
ParameterizedTestParameters(utils, url_encode) {
static struct url_encode_t params[] = {
{0, "", 0, ""},
{9, "Test Text", 11, "Test%20Text"},
{21, "Text\0with\0null\0bytes\0", 29, "Text%00with%00null%00bytes%00"},
{59, "Text&with+some/strange_symbols-or#something?I%don't|know...", 59, "Text&with+some/strange_symbols-or#something?I%don't|know..."},
{33, "Data\x12With\x13Some" "\xFF" "Control" "\xFE" "Characters", 41, "Data%12With%13Some%FFControl%FECharacters"}
};
return cr_make_param_array(struct url_encode_t, params, sizeof(params) / sizeof(struct url_encode_t));
}
ParameterizedTest(struct url_encode_t *param, utils, url_encode) {
char out[256];
cr_assert_eq(url_encode(param->in, param->in_size, out, sizeof(out)), param->exp_size);
cr_assert_arr_eq(out, param->exp, param->exp_size + 1);
}
Test(utils, url_encode_bytes) {
char out[4];
char exp[4];
for (int i = 0; i < 256; i++) {
unsigned char ch = i;
if (ch <= 0x20 || ch >= 0x7F) {
cr_assert_eq(url_encode(&ch, 1, out, sizeof(out)), 3);
sprintf(exp, "%%%02X", ch);
cr_assert_str_eq(out, exp);
} else {
cr_assert_eq(url_encode(&ch, 1, out, sizeof(out)), 1);
sprintf(exp, "%c", ch);
cr_assert_str_eq(out, exp);
}
}
}
Test(utils, url_encode_invalid) {
cr_assert_eq(url_encode("Hello", 5, NULL, 0), 5);
}
ParameterizedTestParameters(utils, format_duration) {
static struct format_duration_t params[] = {
{0, "0.0 ms"},
{1, "0.0 ms"},
{90, "0.1 ms"},
{100, "0.1 ms"},
{110, "0.1 ms"},
{900, "0.9 ms"},
{1000, "1.0 ms"},
{9000, "9.0 ms"},
{9899, "9.9 ms"},
{9999, "10.0 ms"},
{10000, "10 ms"},
{11999, "12 ms"},
{999999, "1.0 s"},
{1000000, "1.0 s"},
{3000000, "3.0 s"},
{1000000 * 60, "1:00 min"},
{1000000 * 60 * 30L - 30000000, "29:30 min"},
{1000000 * 60 * 60L, "60:00 min"},
{1000000 * 60 * 120L, "120 min"},
};
return cr_make_param_array(struct format_duration_t, params, sizeof(params) / sizeof(struct format_duration_t));
}
ParameterizedTest(struct format_duration_t *param, utils, format_duration) {
char buf[16];
cr_assert_str_eq(format_duration(param->micros, buf), param->exp);
}