Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_network_ssl.c

Network module example, interacting with SSL.

Network module example, interacting with SSL

Author
Castagnier Mickael
Version
1.0
Date
10/09/2024
#include "nilorea/n_list.h"
#include "nilorea/n_str.h"
#include "nilorea/n_log.h"
char *port = NULL ;
char *addr = NULL ;
char *key = NULL ;
char *cert = NULL ;
char *root_dir = NULL ;
LIST *routes = NULL ;
int ip_version = NETWORK_IPALL ;
int max_http_request_size = 16384 ;
bool done = 0 ;
NETWORK *server = NULL,
*netw = NULL ;
void usage(void)
{
fprintf( stderr,
" -p 'port' : set the https server port\n"
" -k 'key file' : SSL key file path\n"
" -c 'cert file' : SSL certificate file path\n"
" -a 'address name/ip' : optional, specify where to bind interface\n"
" -i 'ipmode' : optional, force 'ipv4' or 'ipv6', default supports both\n"
" -s 'size' : optional, maximum http request size (default: %d)\n"
" -d 'html root' : optional, specify a different http root dir (default: ./DATAS/)\n"
" -v : version\n"
" -h : help\n"
" -V 'log level' : optional, set the log level (default: LOG_ERR)\n" , max_http_request_size );
}
void process_args( int argc, char **argv, char **addr, char **port, char **key, char **cert, LIST *routes , int *ip_version , int *max_http_request_size , char **root_dir )
{
int getoptret = 0,
log_level = LOG_ERR; /* default log level */
if( argc == 1 )
{
fprintf( stderr, "No arguments given, help:\n" );
usage();
exit( 1 );
}
while( ( getoptret = getopt( argc, argv, "hvs:V:p:i:a:r:k:c:s:d:" ) ) != EOF)
{
switch( getoptret )
{
case 'i' :
if( !strcmp( "v4", optarg ) )
{
(*ip_version) = NETWORK_IPV4 ;
n_log( LOG_NOTICE, "IPV4 selected" );
}
else if( !strcmp( "v6", optarg ) )
{
(*ip_version) = NETWORK_IPV6 ;
n_log( LOG_NOTICE, "IPV6 selected" );
}
else
{
n_log( LOG_NOTICE, "IPV4/6 selected" );
}
break;
case 'v' :
fprintf( stderr, "Date de compilation : %s a %s.\n", __DATE__, __TIME__ );
exit( 1 );
case 'V' :
if( !strncmp( "LOG_NULL", optarg, 8 ) )
{
log_level = LOG_NULL ;
}
else
{
if( !strncmp( "LOG_NOTICE", optarg, 10 ) )
{
log_level = LOG_NOTICE;
}
else
{
if( !strncmp( "LOG_INFO", optarg, 8 ) )
{
log_level = LOG_INFO;
}
else
{
if( !strncmp( "LOG_ERR", optarg, 7 ) )
{
log_level = LOG_ERR;
}
else
{
if( !strncmp( "LOG_DEBUG", optarg, 9 ) )
{
log_level = LOG_DEBUG;
}
else
{
fprintf( stderr, "%s n'est pas un niveau de log valide.\n", optarg );
exit( -1 );
}
}
}
}
}
break;
case 'p' :
(*port) = strdup( optarg );
break ;
case 'r':
list_push( routes , strdup( optarg ) , &free );
break;
case 'a' :
(*addr) = strdup( optarg );
break;
case 'k' :
(*key) = strdup( optarg );
break;
case 'c' :
(*cert) = strdup( optarg );
break;
case 's' :
(*max_http_request_size) = atoi( optarg );
break;
case 'd' :
(*root_dir) = strdup( optarg );
break;
default :
case '?' :
{
if( optopt == 'd' )
{
fprintf( stderr, "\n Missing html root directory\n" );
}
if( optopt == 's' )
{
fprintf( stderr, "\n Missing max http size string\n" );
}
if( optopt == 'k' )
{
fprintf( stderr, "\n Missing key file string\n" );
}
if( optopt == 'c' )
{
fprintf( stderr, "\n Missing certificate file string\n" );
}
if( optopt == 'r' )
{
fprintf( stderr, "\n Missing route string\n" );
}
if( optopt == 'a' )
{
fprintf( stderr, "\n Missing binding host/addr string\n" );
}
if( optopt == 'i' )
{
fprintf( stderr, "\n Missing ip version (v4 or v6) string \n" );
}
else if( optopt == 'V' )
{
fprintf( stderr, "\n Missing log level string\n" );
}
else if( optopt == 'p' )
{
fprintf( stderr, "\n Missing port\n" );
}
else if( optopt != 's' )
{
fprintf( stderr, "\n Unknow missing option %c\n", optopt );
}
usage();
exit( 1 );
}
case 'h' :
{
usage();
exit( 1 );
}
}
}
set_log_level( log_level );
} /* void process_args( ... ) */
/* Exit handling */
void action_on_sig( int recvd_signal )
{
static int nb_sigterm = 0 ;
switch( recvd_signal )
{
/* We should not use these signals as they make the debugging going funky */
#ifndef WIN32
case( SIGABRT ):
n_log( LOG_ERR, "Caught SIGABRT !" );
break;
case( SIGINT ):
n_log( LOG_ERR, "Caught SIGINT !" );
break;
case( SIGBUS ):
n_log( LOG_ERR, "Caught SIGBUS !" );
break;
case( SIGFPE ):
n_log( LOG_ERR, "Caught SIGFPE !" );
break;
case( SIGSEGV ):
n_log( LOG_ERR, "Caught SIGSEGV !" );
break;
case( SIGSYS ):
n_log( LOG_ERR, "Caught SIGSYS !" );
break;
case( SIGTERM ):
nb_sigterm ++ ;
if( nb_sigterm >= 2 )
{
n_log( LOG_ERR, "Caught too much SIGTERM, trying _exit() !!" );
_exit( -1 );
}
n_log( LOG_ERR, "Caught %d SIGTERM, exiting now !!" , nb_sigterm );
exit( -1 );
case( SIGUSR1 ):
done = TRUE ;
n_log( LOG_ERR, "Caught SIGUSR1 !" );
break;
case( SIGUSR2 ):
done = TRUE ;
n_log( LOG_ERR, "Caught SIGUSR1 !" );
break;
case( SIGHUP ):
n_log( LOG_NOTICE, "Caught SIGHUP !" );
break;
#endif
default:
n_log( LOG_ERR, "Caught unknow signal %d", recvd_signal );
break ;
}
} /* action_on_sig() */
// Function to handle different URLs and return appropriate responses
void handle_request(NETWORK *netw , LIST *routes) {
__n_assert( netw , return );
__n_assert( routes , return );
bool found = 0 ;
char **split_results = NULL ;
char *http_url = NULL ;
N_STR *dynamic_request_answer = NULL ;
// Read request
char *http_buffer = NULL ;
Alloca( http_buffer , max_http_request_size + 1 );
__n_assert( http_buffer , netw_close( &netw ) ; return );
SSL_read(netw -> ssl, http_buffer, max_http_request_size );
//n_log( LOG_DEBUG , "http_request: %s" , http_buffer );
// Extract URL from the request
char url[4096]="";
netw_get_url_from_http_request(http_buffer, url, sizeof(url));
n_log( LOG_DEBUG , "url: %s" , url );
// Handle the request based on the URL
N_STR *origin = new_nstr( 32 );
nstrprintf( origin , "%s:"SOCKET_SIZE_FORMAT , _str( netw -> link . ip ) , netw -> link . sock );
NETWORK_HTTP_INFO http_request = netw_extract_http_info( http_buffer );
N_STR *http_body = NULL ;
split_results = split( url , "?" , 0 );
if( !split_results || !split_results[ 0 ] )
{
http_body = char_to_nstr("<html><body><h1>Bad Request</h1></body></html>");
if( netw_build_http_response( &dynamic_request_answer, 400, "ex_network_ssl server", netw_guess_http_content_type( url ), "", http_body ) == FALSE )
{
n_log( LOG_ERR , "couldn't build a Bad Request answer for %s" , url );
}
n_log( LOG_ERR , "%s: %s %s 400" , _nstr( origin ) , http_request . type , url );
}
else
{
http_url = split_results[ 0 ];
n_log( LOG_INFO , "%s: %s %s request..." , _nstr( origin ) , http_request . type , url );
if( strcmp( "OPTIONS" , http_request . type ) == 0 )
{
if( netw_build_http_response( &dynamic_request_answer, 200, "ex_network_ssl server", netw_guess_http_content_type( url ), "Allow: OPTIONS, GET, POST\r\n", NULL ) == FALSE )
{
n_log( LOG_ERR , "couldn't build an OPTION answer for %s" , url );
}
n_log( LOG_INFO , "%s: %s %s 200" , _nstr( origin ) , http_request . type , url );
}
else if( strcmp( "GET" , http_request . type ) == 0 )
{
char system_url[ 4096 ] = "" ;
// example assume a root dir at DATAS
if( !root_dir )
{
snprintf(system_url, sizeof(system_url), "./DATAS%s" , http_url );
}
else
{
snprintf(system_url, sizeof(system_url), "%s%s" , root_dir ,http_url );
}
n_log( LOG_DEBUG , "%s: searching for file %s..." , _nstr( origin ) , system_url );
if( file_exist( system_url ) )
{
n_log( LOG_DEBUG , "%s: file %s found !" , _nstr( origin ) , system_url );
http_body = file_to_nstr( system_url );
if( !http_body )
{
http_body = char_to_nstr("<html><body><h1>Internal Server Error</h1></body></html>");
if( netw_build_http_response( &dynamic_request_answer, 500, "ex_network_ssl server", netw_guess_http_content_type( url ), "", http_body ) == FALSE )
{
n_log( LOG_ERR , "couldn't build an Internal Server Error answer for %s" , url );
}
n_log( LOG_ERR , "%s: %s %s 500" , _nstr( origin ) , http_request . type , url );
}
else
{
if( netw_build_http_response( &dynamic_request_answer, 200, "ex_network_ssl server", netw_guess_http_content_type( url ), "", http_body ) == FALSE )
{
n_log( LOG_ERR , "couldn't build an http answer for %s" , url );
}
n_log( LOG_INFO , "%s: %s %s 200" , _nstr( origin ) , http_request . type , url );
}
}
else
{
http_body = char_to_nstr("<html><body><h1>404 Not Found</h1></body></html>");
if( netw_build_http_response( &dynamic_request_answer, 404, "ex_network_ssl server", netw_guess_http_content_type( url ), "", http_body ) == FALSE )
{
n_log( LOG_ERR , "couldn't build a NOT FOUND answer for %s" , url );
}
n_log( LOG_ERR , "%s: %s %s 404" , _nstr( origin ) , http_request . type , url );
}
}
else if( strcmp( "POST" , http_request . type ) == 0 )
{
// Parse virtual route
found = 0 ;
list_foreach( node , routes )
{
if( strcmp( node -> ptr , http_url ) == 0 )
{
// Handle 200 OK from virtual route
HASH_TABLE *post_data = netw_parse_post_data( http_request . body );
if( post_data )
{
HT_FOREACH( node , post_data ,
{
n_log( LOG_DEBUG , "%s: POST DATA: %s=%s" , _nstr( origin ) , node -> key , (char *)node -> data . ptr );
});
destroy_ht( &post_data );
}
http_body = char_to_nstr("{\"status\":\"ok\"}");
if( netw_build_http_response( &dynamic_request_answer, 200, "ex_network_ssl server", "application/json" , "", http_body ) == FALSE )
{
n_log( LOG_ERR , "couldn't build a route 200 answer for %s" , url );
}
found = 1 ;
n_log( LOG_INFO , "%s: %s virtual:%s 200" , _nstr( origin ) , http_request . type , url );
break ;
}
}
if( !found )
{
http_body = char_to_nstr("<html><body><h1>404 Not Found</h1></body></html>");
if( netw_build_http_response( &dynamic_request_answer, 404, "ex_network_ssl server", netw_guess_http_content_type( url ), "", http_body ) == FALSE )
{
n_log( LOG_ERR , "couldn't build a NOT FOUND answer for %s" , url );
}
n_log( LOG_ERR , "%s: %s %s 404" , _nstr( origin ) , http_request . type , url );
}
}
else
{
http_body = char_to_nstr("<html><body><h1>Bad Request</h1></body></html>");
if( netw_build_http_response( &dynamic_request_answer, 400, "ex_network_ssl server", netw_guess_http_content_type( url ), "", http_body ) == FALSE )
{
n_log( LOG_ERR , "couldn't build a Bad Request answer for %s" , url );
}
n_log( LOG_ERR , "%s: %s %s 400" , _nstr( origin ) , http_request . type , url );
}
free_split_result( &split_results );
}
if( dynamic_request_answer )
{
SSL_write( netw -> ssl, _nstr( dynamic_request_answer ), dynamic_request_answer -> written );
free_nstr( &dynamic_request_answer );
}
else
{
n_log( LOG_ERR , "couldn't build an answer for %s: %s %s" , _nstr( origin ) , http_request . type , url );
}
netw_info_destroy( http_request );
free_nstr( &origin );
free_nstr( &http_body );
} /* handle_request */
{
void *ssl_network_thread( void *params )
{
__n_assert( params, return NULL );
handle_request(ssl_params -> netw , ssl_params -> routes);
netw_close( &ssl_params -> netw );
Free( ssl_params );
return NULL ;
}
int main( int argc , char *argv[] ) {
int exit_code = 0 ;
routes = new_generic_list( -1 );
__n_assert( routes , n_log( LOG_ERR , "could not allocate list !"); exit( 1 ) );
/* processing args and set log_level */
process_args( argc, argv, &addr, &port, &key, &cert, routes, &ip_version , &max_http_request_size , &root_dir );
if( !port )
{
n_log( LOG_ERR, "No port given. Exiting." );
exit_code = 1; goto clean_and_exit ;
}
if( !key )
{
n_log( LOG_ERR, "No key given. Exiting." );
exit_code = 1; goto clean_and_exit ;
}
if( !cert )
{
n_log( LOG_ERR, "No certificate given. Exiting." );
exit_code = 1; goto clean_and_exit ;
}
if( routes -> nb_items == 0 )
{
n_log( LOG_ERR, "No route given. Exiting." );
exit_code = 1; goto clean_and_exit ;
}
#ifndef __windows__
errno = 0 ;
sigignore( SIGPIPE );
/* initializing signal catching */
struct sigaction signal_catcher ;
/* quit on sig */
signal_catcher . sa_handler = action_on_sig ;
sigemptyset( &signal_catcher . sa_mask );
signal_catcher . sa_flags = SA_SIGINFO;
sigaction( SIGTERM, &signal_catcher, NULL );
sigaction( SIGUSR1, &signal_catcher, NULL );
#endif
int nb_active_threads = get_nb_cpu_cores();
int nb_waiting_threads = 10 * nb_active_threads ;
n_log( LOG_INFO, "Creating a new thread pool of %d active and %d waiting threads" , nb_active_threads , nb_waiting_threads );
THREAD_POOL *thread_pool = new_thread_pool( nb_active_threads , nb_waiting_threads );
n_log( LOG_INFO, "Creating listening network for %s:%s %d", _str( addr ), _str( port ), ip_version );
/* create listening network */
if( netw_make_listening( &server , addr, port, SOMAXCONN , ip_version ) == FALSE )
{
n_log( LOG_ERR, "Fatal error with network initialization" );
exit( -1 );
}
netw_set_crypto( server , key , cert );
while( !done )
{
n_log( LOG_DEBUG, "Blocking on accept..." );
/* get any accepted client on a network */
int return_code = 0 ;
if( !(netw = netw_accept_from_ex( server , 0 , 0 , 0 , &return_code ) ) )
{
if( return_code == EINTR )
{
n_log( LOG_INFO, "accept exited after catching a signal");
goto clean_and_exit ;
}
else
{
n_log( LOG_ERR, "error on accept, %s !" , strerror( return_code ) );
}
}
else
{
NETWORK_SSL_THREAD_PARAMS *netw_ssl_params = NULL ;
Malloc( netw_ssl_params , NETWORK_SSL_THREAD_PARAMS , 1 );
netw_ssl_params -> netw = netw ;
netw_ssl_params -> routes = routes ;
if( add_threaded_process( thread_pool, &ssl_network_thread, (void *)netw_ssl_params, DIRECT_PROC) == FALSE )
{
n_log( LOG_ERR, "Error adding client management to thread pool" );
}
}
}
clean_and_exit:
wait_for_threaded_pool( thread_pool, 1000 );
destroy_threaded_pool( &thread_pool, 1000 );
netw_close( &server );
netw_unload();
list_destroy( &routes );
exit( exit_code );
}
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:191
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:284
#define _str(__PTR)
define true
Definition n_common.h:180
#define Alloca(__ptr, __size)
Malloca Handler to get errors and set to 0.
Definition n_common.h:206
int file_exist(const char *filename)
test if file exist and if it's readable
Definition n_common.c:94
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:264
#define _nstr(__PTR)
N_STR or "NULL" string for logging purposes.
Definition n_common.h:186
int destroy_ht(HASH_TABLE **table)
empty a table and destroy it
Definition n_hash.c:2449
#define HT_FOREACH(__ITEM_, __HASH_,...)
ForEach macro helper.
Definition n_hash.h:194
structure of a hash table
Definition n_hash.h:108
int list_push(LIST *list, void *ptr, void(*destructor)(void *ptr))
Add a pointer to the end of the list.
Definition n_list.c:244
#define list_foreach(__ITEM_, __LIST_)
ForEach macro helper.
Definition n_list.h:70
int list_destroy(LIST **list)
Empty and Free a list container.
Definition n_list.c:605
LIST * new_generic_list(int max_items)
Initialiaze a generic list container to max_items pointers.
Definition n_list.c:20
Structure of a generic LIST container.
Definition n_list.h:45
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:74
#define LOG_DEBUG
debug-level messages
Definition n_log.h:66
#define LOG_ERR
error conditions
Definition n_log.h:58
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL )
Definition n_log.c:97
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:62
#define LOG_NULL
no log output
Definition n_log.h:27
#define LOG_INFO
informational
Definition n_log.h:64
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:222
#define nstrprintf(__nstr_var,...)
Macro to quickly allocate and sprintf to N_STR
Definition n_str.h:97
N_STR * char_to_nstr(const char *src)
Convert a char into a N_STR, short version.
Definition n_str.c:273
N_STR * new_nstr(NSTRBYTE size)
create a new N_STR string
Definition n_str.c:215
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.
Definition n_str.c:1032
N_STR * file_to_nstr(char *filename)
Load a whole file into a N_STR.
Definition n_str.c:332
int free_split_result(char ***tab)
Free a split result allocated array.
Definition n_str.c:1131
A box including a string and his lenght.
Definition n_str.h:173
NETWORK * netw_accept_from_ex(NETWORK *from, int send_list_limit, int recv_list_limit, int blocking, int *retval)
make a normal 'accept' .
Definition n_network.c:1923
#define NETWORK_IPV6
Flag to force IPV6
Definition n_network.h:31
int netw_make_listening(NETWORK **netw, char *addr, char *port, int nbpending, int ip_version)
Make a NETWORK be a Listening network.
Definition n_network.c:1797
#define NETWORK_IPV4
Flag to force IPV4
Definition n_network.h:29
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
Definition n_network.c:3852
#define SOCKET_SIZE_FORMAT
socket associated printf style
Definition n_network.h:54
#define NETWORK_IPALL
Flag for auto detection by OS of ip version to use.
Definition n_network.h:27
void 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.
Definition n_network.c:3662
int netw_close(NETWORK **netw)
Closing a specified Network, destroy queues, free the structure.
Definition n_network.c:1588
NETWORK_HTTP_INFO netw_extract_http_info(char *request)
extract a lot of informations, mostly as pointers, and populate a NETWORK_HTTP_INFO structure
Definition n_network.c:3592
int netw_info_destroy(NETWORK_HTTP_INFO http_request)
destroy a NETWORK_HTTP_INFO loaded informations
Definition n_network.c:3648
HASH_TABLE * netw_parse_post_data(const char *post_data)
Function to parse POST data.
Definition n_network.c:3710
const char * netw_guess_http_content_type(const char *url)
function to guess the content type based on URL extension
Definition n_network.c:3755
Structure of a NETWORK.
Definition n_network.h:233
structure for splitting HTTP requests
Definition n_network.h:331
int get_nb_cpu_cores()
get number of core of current system
int destroy_threaded_pool(THREAD_POOL **pool, int delay)
delete a thread_pool, exit the threads and free the structs
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
#define DIRECT_PROC
processing mode for added func, direct start
int wait_for_threaded_pool(THREAD_POOL *thread_pool, int delay)
Wait for all the launched process in the thread pool to terminate.
THREAD_POOL * new_thread_pool(int nbmaxthr, int nb_max_waiting)
Create a new pool of nbmaxthr threads.
Structure of a trhead pool.
List structures and definitions.
Generic log system.
Network Engine.
Signals general handling with stack printing, from https://gist.github.com/jvranish/4441299.
N_STR and string function declaration.
Thread pool declaration.
structure of a NETWORK_SSL_THREAD_PARAMS
LIST * routes
virtual routes for the server
NETWORK * netw
network to use for the receiving thread