43#define realpath(rel, abs) _fullpath(abs, rel, MAX_PATH)
55#define MAX_HTTP_REQUEST_SIZE_LIMIT 1048576
56#define CONNECTION_TIMEOUT_SECONDS 10
65#define SECURITY_HEADERS \
66 "Strict-Transport-Security: max-age=31536000\r\n" \
67 "X-Content-Type-Options: nosniff\r\n" \
68 "X-Frame-Options: DENY\r\n" \
69 "Content-Security-Policy: default-src 'self'\r\n" \
70 "Referrer-Policy: strict-origin-when-cross-origin\r\n" \
71 "Permissions-Policy: geolocation=(), camera=(), microphone=()\r\n"
75 " -p 'port' : set the https server port\n"
76 " -k 'key file' : SSL key file path\n"
77 " -c 'cert file' : SSL certificate file path\n"
78 " -A 'CA file' : optional, CA certificate file for chain verification\n"
79 " -e : optional, enable SSL peer certificate verification\n"
80 " -a 'address name/ip' : optional, specify where to bind interface\n"
81 " -i 'ipmode' : optional, force 'ipv4' or 'ipv6', default supports both\n"
82 " -s 'size' : optional, maximum http request size (default: %d, max: %d)\n"
83 " -n 'count' : optional, exit after count connections (0 = unlimited, default)\n"
84 " -d 'html root' : optional, specify a different http root dir (default: ./DATAS/)\n"
87 " -V 'log level' : optional, set the log level (default: LOG_ERR)\n",
91void process_args(
int argc_nb,
char** argv_ptr,
char** addr_ptr,
char** port_ptr,
char** key_ptr,
char** cert_ptr,
char** ca_file_ptr,
int* ssl_verify_ptr,
LIST* routes_ptr,
int* ip_version_ptr,
int* max_http_request_size_ptr,
char** root_dir_ptr) {
96 fprintf(stderr,
"No arguments given, help:\n");
100 while ((
getoptret = getopt(argc_nb, argv_ptr,
"hven:s:V:p:i:a:r:k:c:s:d:A:")) != EOF) {
106 if (!strcmp(
"v4", optarg)) {
109 }
else if (!strcmp(
"v6", optarg)) {
117 fprintf(stderr,
"Date de compilation : %s a %s.\n", __DATE__, __TIME__);
120 if (!strncmp(
"LOG_NULL", optarg, 8)) {
123 if (!strncmp(
"LOG_NOTICE", optarg, 10)) {
126 if (!strncmp(
"LOG_INFO", optarg, 8)) {
129 if (!strncmp(
"LOG_ERR", optarg, 7)) {
132 if (!strncmp(
"LOG_DEBUG", optarg, 9)) {
135 fprintf(stderr,
"%s n'est pas un niveau de log valide.\n", optarg);
144 (*ssl_verify_ptr) = 1;
147 (*port_ptr) = strdup(optarg);
150 list_push(routes_ptr, strdup(optarg), &free);
153 (*addr_ptr) = strdup(optarg);
156 (*key_ptr) = strdup(optarg);
159 (*cert_ptr) = strdup(optarg);
162 (*ca_file_ptr) = strdup(optarg);
165 int val = atoi(optarg);
170 (*max_http_request_size_ptr) = val;
174 (*root_dir_ptr) = strdup(optarg);
179 fprintf(stderr,
"\n Missing html root directory\n");
182 fprintf(stderr,
"\n Missing max http size string\n");
185 fprintf(stderr,
"\n Missing key file string\n");
188 fprintf(stderr,
"\n Missing certificate file string\n");
191 fprintf(stderr,
"\n Missing CA file string\n");
194 fprintf(stderr,
"\n Missing route string\n");
197 fprintf(stderr,
"\n Missing binding host/addr string\n");
200 fprintf(stderr,
"\n Missing ip version (v4 or v6) string \n");
201 }
else if (optopt ==
'V') {
202 fprintf(stderr,
"\n Missing log level string\n");
203 }
else if (optopt ==
'p') {
204 fprintf(stderr,
"\n Missing port\n");
205 }
else if (optopt !=
's') {
206 fprintf(stderr,
"\n Unknow missing option %c\n", optopt);
239#if OPENSSL_VERSION_NUMBER >= 0x10101000L
240 if (!SSL_CTX_set_min_proto_version(ssl_netw->
ctx, TLS1_2_VERSION)) {
241 n_log(
LOG_ERR,
"Failed to set minimum TLS version to 1.2");
245 SSL_CTX_set_options(ssl_netw->
ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
254 SSL_CTX_set_options(ssl_netw->
ctx,
255 SSL_OP_NO_COMPRESSION |
256 SSL_OP_CIPHER_SERVER_PREFERENCE |
257 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
259 SSL_OP_SINGLE_DH_USE |
260 SSL_OP_SINGLE_ECDH_USE);
263 const char* cipher_list =
"ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:!aNULL:!eNULL:!MD5:!DSS:!RC4:!3DES:!SEED:!IDEA:!CAMELLIA";
264 if (!SSL_CTX_set_cipher_list(ssl_netw->
ctx, cipher_list)) {
270#if OPENSSL_VERSION_NUMBER >= 0x10101000L
271 SSL_CTX_set_ciphersuites(ssl_netw->
ctx,
"TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256");
275#if OPENSSL_VERSION_NUMBER >= 0x10101000L
276 if (!SSL_CTX_set1_groups_list(ssl_netw->
ctx,
"X25519:P-256:P-384")) {
280#elif OPENSSL_VERSION_NUMBER >= 0x10002000L
281 SSL_CTX_set_ecdh_auto(ssl_netw->
ctx, 1);
285 SSL_CTX_set_session_cache_mode(ssl_netw->
ctx, SSL_SESS_CACHE_SERVER);
286 SSL_CTX_set_timeout(ssl_netw->
ctx, 300);
288 n_log(
LOG_INFO,
"SSL context hardened: TLS 1.2+, strong ciphers, no compression");
301 tv.tv_sec = timeout_seconds;
303 if (setsockopt((
int)sock, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv)) < 0) {
304 n_log(
LOG_ERR,
"Failed to set SO_RCVTIMEO: %s", strerror(errno));
307 if (setsockopt((
int)sock, SOL_SOCKET, SO_SNDTIMEO, &tv,
sizeof(tv)) < 0) {
308 n_log(
LOG_ERR,
"Failed to set SO_SNDTIMEO: %s", strerror(errno));
312 DWORD tv = (DWORD)(timeout_seconds * 1000);
313 if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (
const char*)&tv,
sizeof(tv)) < 0) {
317 if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (
const char*)&tv,
sizeof(tv)) < 0) {
338int validate_path(
const char* requested_path,
const char* root,
char* resolved,
size_t resolved_size) {
339 char resolved_root[PATH_MAX] =
"";
340 char resolved_request[PATH_MAX] =
"";
343 if (!realpath(root, resolved_root)) {
344 n_log(
LOG_ERR,
"Cannot resolve root directory: %s (%s)", root, strerror(errno));
349 if (!realpath(requested_path, resolved_request)) {
357 size_t root_len = strlen(resolved_root);
358 if (strncmp(resolved_request, resolved_root, root_len) != 0) {
359 n_log(
LOG_ERR,
"Path traversal attempt blocked: %s (resolved: %s, root: %s)",
360 requested_path, resolved_request, resolved_root);
364 if (resolved_request[root_len] !=
'/' && resolved_request[root_len] !=
'\0') {
365 n_log(
LOG_ERR,
"Path traversal attempt blocked (prefix match): %s", requested_path);
369 strncpy(resolved, resolved_request, resolved_size - 1);
370 resolved[resolved_size - 1] =
'\0';
378 static int nb_sigterm = 0;
379 switch (recvd_signal) {
400 if (nb_sigterm >= 2) {
401 n_log(
LOG_ERR,
"Caught too much SIGTERM, trying _exit() !!");
404 n_log(
LOG_ERR,
"Caught %d SIGTERM, exiting now !!", nb_sigterm);
418 n_log(
LOG_ERR,
"Caught unknow signal %d", recvd_signal);
426 char headers[2048] =
"";
427 snprintf(headers,
sizeof(headers),
"%s%s",
SECURITY_HEADERS, extra_headers ? extra_headers :
"");
437 char** split_results = NULL;
438 char* http_url = NULL;
439 N_STR* dynamic_request_answer = NULL;
442 char* http_buffer = NULL;
453 if (ssl_read_ret <= 0) {
454 int ssl_error = SSL_get_error(netw_ptr->
ssl, ssl_read_ret);
455 if (ssl_error == SSL_ERROR_ZERO_RETURN) {
456 n_log(
LOG_INFO,
"SSL_read: peer closed the connection cleanly");
457 }
else if (ssl_error == SSL_ERROR_SYSCALL && errno == EAGAIN) {
460 n_log(
LOG_ERR,
"SSL_read failed with SSL error %d", ssl_error);
466 http_buffer[ssl_read_ret] =
'\0';
478 N_STR* http_body = NULL;
480 split_results =
split(url,
"?", 0);
481 if (!split_results || !split_results[0]) {
482 http_body =
char_to_nstr(
"<html><body><h1>Bad Request</h1></body></html>");
484 n_log(
LOG_ERR,
"couldn't build a Bad Request answer for %s", url);
488 http_url = split_results[0];
490 if (strcmp(
"OPTIONS", http_request.
type) == 0) {
492 n_log(
LOG_ERR,
"couldn't build an OPTION answer for %s", url);
495 }
else if (strcmp(
"GET", http_request.
type) == 0) {
496 char system_url[4096] =
"";
498 snprintf(system_url,
sizeof(system_url),
"%s%s", effective_root, http_url);
503 char validated_path[PATH_MAX] =
"";
504 if (!
validate_path(system_url, effective_root, validated_path,
sizeof(validated_path))) {
507 if (stat(system_url, &st) == 0) {
510 http_body =
char_to_nstr(
"<html><body><h1>403 Forbidden</h1></body></html>");
512 n_log(
LOG_ERR,
"couldn't build a Forbidden answer for %s", url);
516 http_body =
char_to_nstr(
"<html><body><h1>404 Not Found</h1></body></html>");
518 n_log(
LOG_ERR,
"couldn't build a NOT FOUND answer for %s", url);
524 if (stat(validated_path, &st) == 0 && S_ISREG(st.st_mode)) {
528 http_body =
char_to_nstr(
"<html><body><h1>Internal Server Error</h1></body></html>");
530 n_log(
LOG_ERR,
"couldn't build an Internal Server Error answer for %s", url);
535 n_log(
LOG_ERR,
"couldn't build an http answer for %s", url);
539 }
else if (stat(validated_path, &st) == 0 && S_ISDIR(st.st_mode)) {
541 char index_path[PATH_MAX + 16] =
"";
542 size_t url_len = strlen(http_url);
543 const char* url_slash = (url_len > 0 && http_url[url_len - 1] ==
'/') ?
"" :
"/";
545 size_t vp_len = strlen(validated_path);
546 if (vp_len +
sizeof(
"/index.html") >
sizeof(index_path)) {
547 n_log(
LOG_ERR,
"path too long for index lookup: %s", validated_path);
548 http_body =
char_to_nstr(
"<html><body><h1>414 URI Too Long</h1></body></html>");
550 n_log(
LOG_ERR,
"couldn't build a URI Too Long answer for %s", url);
553 snprintf(index_path,
sizeof(index_path),
"%s/index.html", validated_path);
554 char validated_index[PATH_MAX] =
"";
556 if (
validate_path(index_path, effective_root, validated_index,
sizeof(validated_index)) &&
557 stat(validated_index, &idx_st) == 0 && S_ISREG(idx_st.st_mode)) {
558 char location_header[4096] =
"";
559 snprintf(location_header,
sizeof(location_header),
"Location: %s%sindex.html\r\n", http_url, url_slash);
560 if (
build_secure_response(&dynamic_request_answer, 301,
"text/html", location_header, NULL) == FALSE) {
561 n_log(
LOG_ERR,
"couldn't build a redirect answer for %s", url);
563 n_log(
LOG_INFO,
"%s: %s %s 301 -> %s%sindex.html",
_nstr(origin), http_request.
type, url, http_url, url_slash);
565 http_body =
char_to_nstr(
"<html><body><h1>404 Not Found</h1></body></html>");
567 n_log(
LOG_ERR,
"couldn't build a NOT FOUND answer for %s", url);
573 http_body =
char_to_nstr(
"<html><body><h1>404 Not Found</h1></body></html>");
575 n_log(
LOG_ERR,
"couldn't build a NOT FOUND answer for %s", url);
580 }
else if (strcmp(
"POST", http_request.
type) == 0) {
584 if (strcmp(node->ptr, http_url) == 0) {
590 n_log(
LOG_DEBUG,
"%s: POST DATA: %s=%s",
_nstr(origin), hnode->key, (
char*)hnode->data.ptr);
595 if (
build_secure_response(&dynamic_request_answer, 200,
"application/json",
"", http_body) == FALSE) {
596 n_log(
LOG_ERR,
"couldn't build a route 200 answer for %s", url);
604 http_body =
char_to_nstr(
"<html><body><h1>404 Not Found</h1></body></html>");
606 n_log(
LOG_ERR,
"couldn't build a NOT FOUND answer for %s", url);
611 http_body =
char_to_nstr(
"<html><body><h1>400 Bad Request</h1></body></html>");
613 n_log(
LOG_ERR,
"couldn't build a Bad Request answer for %s", url);
619 if (dynamic_request_answer) {
620 if (dynamic_request_answer->
written > UINT32_MAX) {
621 n_log(
LOG_ERR,
"response too large to send for %s: %s %s (size: %zu)",
_nstr(origin), http_request.
type, url, dynamic_request_answer->
written);
652int main(
int argc,
char* argv[]) {
659 process_args(argc, argv, &
addr, &
port, &
key, &
cert, &
ca_file, &
ssl_verify,
routes, &
ip_version, &
max_http_request_size, &
root_dir);
679 signal(SIGPIPE, SIG_IGN);
681 struct sigaction signal_catcher;
685 sigemptyset(&signal_catcher.sa_mask);
686 signal_catcher.sa_flags = 0;
688 sigaction(SIGTERM, &signal_catcher, NULL);
689 sigaction(SIGUSR1, &signal_catcher, NULL);
693 int nb_active_threads = (cores > 0) ? (
int)cores : 1;
694 int nb_waiting_threads = 10 * nb_active_threads;
695 n_log(
LOG_INFO,
"Creating a new thread pool of %d active and %d waiting threads", nb_active_threads, nb_waiting_threads);
701 n_log(
LOG_ERR,
"Fatal error with network initialization");
714 n_log(
LOG_ERR,
"Failed to harden SSL context. Exiting.");
720 n_log(
LOG_INFO,
"SSL peer certificate verification enabled");
723 int accepted_count = 0;
730 if (return_code == EINTR) {
734 n_log(
LOG_ERR,
"error on accept, NULL netw returned !");
743 n_log(
LOG_ERR,
"Error adding client management to thread pool");
void process_args(int argc, char **argv)
THREAD_POOL * thread_pool
NETWORK * netw
Network for server mode, accepting incomming.
int max_http_request_size
void * ssl_network_thread(void *params)
void action_on_sig(int recvd_signal)
void handle_request(NETWORK *netw_ptr, LIST *routes_ptr)
int ssl_harden_context(NETWORK *ssl_netw)
Harden the SSL context with modern security settings.
#define CONNECTION_TIMEOUT_SECONDS
#define SECURITY_HEADERS
Network for managing connections.
LIST * routes
virtual routes for the server
#define MAX_HTTP_REQUEST_SIZE_LIMIT
int set_socket_timeout(SOCKET sock, int timeout_seconds)
Set socket read/write timeout to prevent slow-loris attacks.
NETWORK * netw
network to use for the receiving thread
int validate_path(const char *requested_path, const char *root, char *resolved, size_t resolved_size)
Validate that a resolved path stays within the document root.
int build_secure_response(N_STR **response, int status_code, const char *content_type, char *extra_headers, N_STR *body)
structure of a NETWORK_SSL_THREAD_PARAMS
#define FreeNoLog(__ptr)
Free Handler without log.
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
#define __n_assert(__ptr, __ret)
macro to assert things
#define _str(__PTR)
define true
#define Free(__ptr)
Free Handler to get errors.
#define _nstr(__PTR)
N_STR or "NULL" string for logging purposes.
int destroy_ht(HASH_TABLE **table)
empty a table and destroy it
#define HT_FOREACH(__ITEM_, __HASH_,...)
ForEach macro helper.
structure of a hash table
int list_push(LIST *list, void *ptr, void(*destructor)(void *ptr))
Add a pointer to the end of the list.
#define list_foreach(__ITEM_, __LIST_)
ForEach macro helper, safe for node removal during iteration.
int list_destroy(LIST **list)
Empty and Free a list container.
LIST * new_generic_list(size_t max_items)
Initialiaze a generic list container to max_items pointers.
#define MAX_LIST_ITEMS
flag to pass to new_generic_list for the maximum possible number of item in a list
Structure of a generic LIST container.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_DEBUG
debug-level messages
#define LOG_ERR
error conditions
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
#define LOG_NOTICE
normal but significant condition
#define LOG_NULL
no log output
#define LOG_INFO
informational
size_t written
size of the written data inside the string
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
N_STR * char_to_nstr(const char *src)
Convert a char into a N_STR, short version.
N_STR * new_nstr(NSTRBYTE size)
create a new N_STR string
#define nstrprintf(__nstr_var, __format,...)
Macro to quickly allocate and sprintf to N_STR.
char ** split(const char *str, const char *delim, int empty)
split the strings into a an array of char *pointer , ended by a NULL one.
N_STR * file_to_nstr(char *filename)
Load a whole file into a N_STR.
int free_split_result(char ***tab)
Free a split result allocated array.
A box including a string and his lenght.
char * ip
ip of the connected socket
N_SOCKET link
networking socket
char * type
Type of request.
char * body
Pointer to the body data.
SOCKET sock
a normal socket
SSL_CTX * ctx
SSL context holder.
ssize_t send_ssl_data(void *netw, char *buf, uint32_t n)
send data onto the socket
int netw_ssl_set_verify(NETWORK *netw, int enable)
enable or disable SSL peer certificate verification
int netw_get_url_from_http_request(const char *request, char *url, size_t size)
Helper function to extract the URL from the HTTP request line.
int netw_set_crypto(NETWORK *netw, char *key, char *certificate)
activate SSL encryption on selected network, using key and certificate
#define NETWORK_IPV6
Flag to force IPV6
int netw_make_listening(NETWORK **netw, char *addr, char *port, int nbpending, int ip_version)
Make a NETWORK be a Listening network.
#define NETWORK_IPV4
Flag to force IPV4
int netw_build_http_response(N_STR **http_response, int status_code, const char *server_name, const char *content_type, char *additional_headers, N_STR *body)
function to dynamically generate an HTTP response
#define SOCKET_SIZE_FORMAT
socket associated printf style
NETWORK * netw_accept_from_ex(NETWORK *from, size_t send_list_limit, size_t recv_list_limit, int blocking, int *retval)
make a normal 'accept' .
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
int SOCKET
default socket declaration
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
int netw_set_crypto_chain(NETWORK *netw, char *key, char *certificate, char *ca_file)
activate SSL encryption using key/certificate files and a CA file for chain verification
NETWORK_HTTP_INFO netw_extract_http_info(char *request)
extract a lot of informations, mostly as pointers, and populate a NETWORK_HTTP_INFO structure
int netw_info_destroy(NETWORK_HTTP_INFO http_request)
destroy a NETWORK_HTTP_INFO loaded informations
HASH_TABLE * netw_parse_post_data(const char *post_data)
Function to parse POST data.
const char * netw_guess_http_content_type(const char *url)
function to guess the content type based on URL extension
structure for splitting HTTP requests
THREAD_POOL * new_thread_pool(size_t nbmaxthr, size_t nb_max_waiting)
Create a new pool of nbmaxthr threads.
int add_threaded_process(THREAD_POOL *thread_pool, void *(*func_ptr)(void *param), void *param, int mode)
add a function and params to a thread pool
int wait_for_threaded_pool(THREAD_POOL *thread_pool)
Wait for the thread pool to become idle (no active threads, empty waiting list), blocking without pol...
int destroy_threaded_pool(THREAD_POOL **pool, unsigned int delay)
delete a thread_pool, exit the threads and free the structs
long int get_nb_cpu_cores()
get number of core of current system
#define DIRECT_PROC
processing mode for added func, direct start, not queued
Structure of a thread pool.
List structures and definitions.
Signals general handling with stack printing, from https://gist.github.com/jvranish/4441299.
N_STR and string function declaration.