diff options
author | stderr64 <linuxwizard@voidnet.dy.fi> | 2023-08-17 21:19:10 +0300 |
---|---|---|
committer | stderr64 <linuxwizard@voidnet.dy.fi> | 2023-08-17 21:19:10 +0300 |
commit | f484331cf374031f68566162f16ba00eedc1b7f0 (patch) | |
tree | c5632b5b7c574bf31642cdec12a409adfbabb31c | |
download | CWebHook-f484331cf374031f68566162f16ba00eedc1b7f0.tar.gz CWebHook-f484331cf374031f68566162f16ba00eedc1b7f0.tar.zst |
Recreated repository
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 10 | ||||
-rw-r--r-- | auth.h | 10 | ||||
-rw-r--r-- | config.example.json | 10 | ||||
-rw-r--r-- | cwebhook.c | 127 | ||||
-rw-r--r-- | http_headers.h | 88 | ||||
-rw-r--r-- | https_response.h | 79 | ||||
-rw-r--r-- | https_server.h | 152 | ||||
-rw-r--r-- | https_server_structs.h | 28 | ||||
-rw-r--r-- | load_config.h | 78 | ||||
-rw-r--r-- | load_webhooks.h | 56 | ||||
-rw-r--r-- | log.h | 26 | ||||
-rw-r--r-- | socket_init.h | 32 | ||||
-rw-r--r-- | structs.h | 25 | ||||
-rw-r--r-- | webhook_exec.h | 58 | ||||
-rw-r--r-- | webhooks.example.json | 10 |
16 files changed, 791 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a79b018 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +devcerts/* +config.json diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cf8b416 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +CC=gcc +MAX_PEEK_BYTES=512000 +MAX_RECV_BYTES=512000 +MAX_WEBHOOK_OUTPUT_LENGTH=512000 + +rel: + $(CC) -DMAX_PEEK_BYTES=${MAX_PEEK_BYTES} -DMAX_RECV_BYTES=${MAX_RECV_BYTES} -DMAX_WEBHOOK_OUTPUT_LENGTH=${MAX_WEBHOOK_OUTPUT_LENGTH} -Wall -pedantic-errors -Werror -lssl -lcrypto -lcjson cwebhook.c -o cwebhook + +debug: + $(CC) -DMAX_PEEK_BYTES=${MAX_PEEK_BYTES} -DMAX_RECV_BYTES=${MAX_RECV_BYTES} -DMAX_WEBHOOK_OUTPUT_LENGTH=${MAX_WEBHOOK_OUTPUT_LENGTH} -Wall -pedantic-errors -Werror -ggdb -lssl -lcrypto -lcjson cwebhook.c -o cwebhook @@ -0,0 +1,10 @@ +#define AUTH_SUCCESS 0 +#define AUTH_FAILED -1 + +int check_password( char *pw, char *correct_pw ){ + if ( pw == NULL || (uint64_t)strlen(pw) <= 0 ) + return AUTH_FAILED; + if ( strcmp((const char*)pw, (const char*)correct_pw) != 0 ) + return AUTH_FAILED; + return AUTH_SUCCESS; +} diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..b5dd8b4 --- /dev/null +++ b/config.example.json @@ -0,0 +1,10 @@ +{ + "log_enabled": "yes", + "log_file": "/tmp/cwebhook.log", + "bind_address": "127.0.0.1", + "bind_port": "8500", + "server_password": "admin", + "cert_file": "path to certificate file for https (PEM format)", + "cert_key_file": "path to the certificate key file for https", + "webhooks_file": "path to file with webhooks configuration file (see webhooks.example.json)" +} diff --git a/cwebhook.c b/cwebhook.c new file mode 100644 index 0000000..b03d752 --- /dev/null +++ b/cwebhook.c @@ -0,0 +1,127 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <inttypes.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <locale.h> +#include <poll.h> +#include <openssl/bio.h> +#include <openssl/ssl.h> +#include <cjson/cJSON.h> +#include "structs.h" +#include "https_server_structs.h" +#include "log.h" +#include "load_config.h" +#include "load_webhooks.h" +#include "socket_init.h" +#include "https_server.h" + +server_socket_t ssocket; +http_request_data_t hrequest; +client_info_t cinfo; +webhooks_data_t whinfo; +server_config_t sconfig; +log_file_t ldata; + +void safe_exit( int sigc, siginfo_t *s_info, void *ectx ){ + if ( ssocket.tls_context != NULL ) + SSL_CTX_free( ssocket.tls_context ); + if ( ssocket.socket_fd > 0 ) + close( ssocket.socket_fd ); + if ( ssocket.client_socket_fd > 0 ) + close( ssocket.client_socket_fd ); + if ( sconfig.config_parsed != NULL ) + cJSON_Delete( sconfig.config_parsed ); + if ( whinfo.webhooks_parsed != NULL ) + cJSON_Delete( whinfo.webhooks_parsed ); + if ( ldata.log_file != NULL ) + fclose( ldata.log_file ); + if ( sigc == 11 || s_info->si_signo == 11 ){ + perror( "status" ); + fprintf( stderr, "segfault (address: %p)\n", s_info->si_addr ); + } + return; +} + +struct sigaction s_act; +struct sigaction s_p; + +void sigp_handler( int ecd ){ + fprintf( stderr, "Received connection invalid (error code %i)\n", ecd ); + return; +} + +int main( int arg_c, char *args[] ){ + setlocale( LC_ALL, "C" ); + if ( setvbuf(stdout, NULL, _IOLBF, 0) != 0 ){ + fputs( "Failed to set stdout to be line buffered\n", stderr ); + return EXIT_FAILURE; + } + if ( setvbuf(stderr, NULL, _IOLBF, 0) != 0 ){ + fputs( "Failed to set stderr to be line buffered\n", stderr ); + return EXIT_FAILURE; + } + if ( sigemptyset(&s_act.sa_mask) != 0 ){ + perror( "failed to initialize empty set" ); + return EXIT_FAILURE; + } + s_act.sa_flags = SA_SIGINFO | SA_RESTART; + s_act.sa_sigaction = &safe_exit; + if ( sigemptyset(&s_p.sa_mask) != 0 ){ + perror( "failed to initialize empty set" ); + return EXIT_FAILURE; + } + s_p.sa_handler = &sigp_handler; + if ( sigaction(SIGSEGV, (const struct sigaction*)&s_act, NULL) != 0 ){ + perror( "failed to register SIGSEGV handler" ); + return EXIT_FAILURE; + } + if ( sigaction(SIGINT, (const struct sigaction*)&s_act, NULL) != 0 ){ + perror( "failed to register SIGINT handler" ); + return EXIT_FAILURE; + } + if ( sigaction(SIGPIPE, (const struct sigaction*)&s_p, NULL) != 0 ){ + perror( "failed to register SIGPIPE handler" ); + return EXIT_FAILURE; + } + if ( arg_c <= 1 ){ + fprintf( stdout, "No configuration file specified\nUsage: %s [config file]\n", args[0] ); + return EXIT_FAILURE; + } + fputs( "Loading config\n", stdout ); + if ( load_config(args[1], &sconfig) != CONFIG_LOAD_SUCCESS ){ + fputs( "Failed to load config\n", stderr ); + return EXIT_FAILURE; + } + fputs( "Config loaded\n", stdout ); + fputs( "Initializing log with configured options\n", stdout ); + if ( log_init(&sconfig, &ldata) != LOG_INIT_SUCCESS ){ + fputs( "Failed to initialize log\n", stderr ); + return EXIT_FAILURE; + } + fputs( "Log initialized\n", stdout ); + fputs( "Loading webhooks\n", stdout ); + if ( load_webhooks(sconfig.webhooks_file->valuestring, &whinfo) != WH_LOAD_SUCCESS ){ + fputs( "Failed to load webhooks\n", stderr ); + return EXIT_FAILURE; + } + fputs( "Webhooks loaded\n", stdout ); + fputs( "Initializing socket\n", stdout ); + if ( init_socket(&ssocket, &sconfig) != SOCKET_INIT_SUCCESS ){ + fputs( "Failed to initialize socket\n", stderr ); + return EXIT_FAILURE; + } + fputs( "Socket initialized\n", stdout ); + server_start( &ssocket, &cinfo, &sconfig, &whinfo, &hrequest, &ldata ); + fputs( "Server terminated\n", stdout ); + return EXIT_SUCCESS; +} diff --git a/http_headers.h b/http_headers.h new file mode 100644 index 0000000..f3aa397 --- /dev/null +++ b/http_headers.h @@ -0,0 +1,88 @@ +#define REQ_READ_SUCCESS 0 +#define REQ_READ_ERROR -1 + +int get_request_data( http_request_data_t *hdt, char *req_contents ){ + if ( req_contents == NULL || (uint64_t)strlen(req_contents) <= 0 ) + return REQ_READ_ERROR; + hdt->request_size = (size_t)strlen( req_contents ); + hdt->request_copy = (char*)calloc( hdt->request_size, sizeof(char) ); + if ( hdt->request_copy == NULL ) + return REQ_READ_ERROR; + strncpy( hdt->request_copy, req_contents, hdt->request_size ); + if ( (size_t)strlen(hdt->request_copy) != hdt->request_size ) + return REQ_READ_ERROR; + hdt->tok_current = strtok_r( hdt->request_copy, "\r\n", &hdt->tok_prev ); + if ( hdt->tok_current == NULL ) + return REQ_READ_ERROR; + if ( strstr(hdt->tok_current, "GET /") == NULL && strstr(hdt->tok_current, "POST /") == NULL ) + return REQ_READ_ERROR; + hdt->tok_current = strtok_r( hdt->request_copy, " ", &hdt->tok_prev ); + if ( hdt->tok_current == NULL ) + return REQ_READ_ERROR; + hdt->request_method = (char*)calloc( (size_t)strlen(hdt->tok_current), sizeof(char) ); + if ( hdt->request_method == NULL ) + return REQ_READ_ERROR; + strncpy( hdt->request_method, hdt->tok_current, (size_t)strlen(hdt->tok_current) ); + if ( (size_t)strlen(hdt->request_method) != (size_t)strlen(hdt->tok_current) ) + return REQ_READ_ERROR; + hdt->tok_current = strtok_r( NULL, " ", &hdt->tok_prev ); + if ( hdt->tok_current == NULL ) + return REQ_READ_ERROR; + hdt->request_path = (char*)calloc( (size_t)strlen(hdt->tok_current), sizeof(char) ); + if ( hdt->request_path == NULL ) + return REQ_READ_ERROR; + strncpy( hdt->request_path, hdt->tok_current, (size_t)strlen(hdt->tok_current) ); + if ( (size_t)strlen(hdt->request_path) != (size_t)strlen(hdt->tok_current) ) + return REQ_READ_ERROR; + memset( hdt->request_copy, 0, hdt->request_size ); + strncpy( hdt->request_copy, req_contents, hdt->request_size ); + if ( (size_t)strlen(hdt->request_copy) != hdt->request_size ) + return REQ_READ_ERROR; + char *request_body_begin = strstr( hdt->request_copy, "\r\n\r\n" ); + if ( request_body_begin == NULL ){ + hdt->request_body = NULL; + return REQ_READ_SUCCESS; + } + hdt->tok_current = strtok_r( request_body_begin, "\r\n\r\n", &hdt->tok_prev ); + if ( hdt->tok_current == NULL ){ + hdt->request_body = NULL; + return REQ_READ_SUCCESS; + } + hdt->request_body = (char*)calloc( (size_t)strlen(hdt->tok_current), sizeof(char) ); + if ( hdt->request_body == NULL ) + return REQ_READ_ERROR; + strncpy( hdt->request_body, hdt->tok_current, (size_t)strlen(hdt->tok_current) ); + if ( (size_t)strlen(hdt->request_body) != (size_t)strlen(hdt->tok_current) ) + return REQ_READ_ERROR; + return REQ_READ_SUCCESS; +} + +void clear_request_copy( http_request_data_t *hrq ){ + if ( hrq->request_copy != NULL ){ + free( hrq->request_copy ); + hrq->request_copy = NULL; + } + return; +} + +void clear_request_data( http_request_data_t *hreq_data ){ + if ( hreq_data->request_path != NULL ){ + free( hreq_data->request_path ); + hreq_data->request_path = NULL; + } + if ( hreq_data->request_body != NULL ){ + free( hreq_data->request_body ); + hreq_data->request_body = NULL; + } + if ( hreq_data->request_method != NULL ){ + free( hreq_data->request_method ); + hreq_data->request_method = NULL; + } + if ( hreq_data->request_size > 0 ) + hreq_data->request_size = 0; + if ( hreq_data->tok_prev != NULL ) + hreq_data->tok_prev = NULL; + if ( hreq_data->tok_current != NULL ) + hreq_data->tok_current = NULL; + return; +} diff --git a/https_response.h b/https_response.h new file mode 100644 index 0000000..58be84b --- /dev/null +++ b/https_response.h @@ -0,0 +1,79 @@ +#include "http_headers.h" +#include "auth.h" +#include "webhook_exec.h" + +#define HTTP_TEXT_BASE "HTTP/1.1 200 OK\r\nServer: CWebHook\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n" +#define HTTP_ERROR_HEADERS "HTTP/1.1 500 Internal Server Error\r\nServer: CWebHook\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nError: failed to read some request data" +#define HTTP_DISALLOWED_METHOD "HTTP/1.1 403 Forbidden\r\nServer: CWebHook\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nError: disallowed request method" +#define HTTP_MEM_ALLOC_ERROR "HTTP/1.1 500 Internal Server Error\r\nServer: CWebHook\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nError: failed to allocate memory for webhook response" +#define HTTP_MEM_WRITE_ERROR "HTTP/1.1 500 Internal Server Error\r\nServer: CWebHook\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nError: failed to write to allocated memory" +#define HTTP_UNKNOWN_ENDPOINT "HTTP/1.1 404 Not Found\r\nServer: CWebHook\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nError: no such API endpoint found" +#define HTTP_UNAUTHORIZED "HTTP/1.1 403 Forbidden\r\nServer: CWebHook\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nUnauthorized" + +#define RES_SUCCESS 0 +#define RES_FAILED -1 +#define RES_UNAUTHORIZED -2 + +int send_response( server_socket_t *sdata, server_config_t *scf, webhooks_data_t *wdt, http_request_data_t *hdata, log_file_t *lfd ){ + if ( get_request_data(hdata, sdata->recv_data) != REQ_READ_SUCCESS ){ + clear_request_copy( hdata ); + clear_request_data( hdata ); + log_write( scf, lfd, "Error: failed to read headers" ); + if ( (long long int)SSL_write(sdata->tls_session, HTTP_ERROR_HEADERS, (size_t)strlen(HTTP_ERROR_HEADERS)) <= 0 ) + return RES_FAILED; + return RES_SUCCESS; + } + if ( strcmp((const char*)hdata->request_method, "GET") != 0 && strcmp((const char*)hdata->request_method, "POST") != 0 ){ + clear_request_copy( hdata ); + clear_request_data( hdata ); + if ( (long long int)SSL_write(sdata->tls_session, HTTP_DISALLOWED_METHOD, (size_t)strlen(HTTP_DISALLOWED_METHOD)) <= 0 ) + return RES_FAILED; + return RES_SUCCESS; + } + if ( hdata->request_body == NULL || (uint64_t)strlen(hdata->request_body) <= 0 ){ + clear_request_copy( hdata ); + clear_request_data( hdata ); + if ( (long long int)SSL_write(sdata->tls_session, HTTP_UNAUTHORIZED, (size_t)strlen(HTTP_UNAUTHORIZED)) <= 0 ) + return RES_FAILED; + return RES_UNAUTHORIZED; + } + if ( check_password(hdata->request_body, scf->server_password->valuestring) != AUTH_SUCCESS ){ + clear_request_copy( hdata ); + clear_request_data( hdata ); + if ( (long long int)SSL_write(sdata->tls_session, HTTP_UNAUTHORIZED, (size_t)strlen(HTTP_UNAUTHORIZED)) <= 0 ) + return RES_FAILED; + return RES_UNAUTHORIZED; + } + char *wh_exec_res = webhook_exec( wdt, hdata->request_path ); + size_t r_size = (size_t)((size_t)strlen(HTTP_TEXT_BASE) + (size_t)strlen(wh_exec_res) + 1); + sdata->send_buffer = (char*)calloc( r_size, sizeof(char) ); + if ( sdata->send_buffer == NULL ){ + r_size = 0; + free_wh_output( wdt ); + wh_exec_res = NULL; + if ( (long long int)SSL_write(sdata->tls_session, HTTP_MEM_ALLOC_ERROR, (size_t)strlen(HTTP_MEM_ALLOC_ERROR)) <= 0 ) + return RES_FAILED; + return RES_SUCCESS; + } + if ( snprintf(sdata->send_buffer, r_size * sizeof(char), "%s%s", HTTP_TEXT_BASE, wh_exec_res) <= 0 ){ + free( sdata->send_buffer ); + r_size = 0; + free_wh_output( wdt ); + wh_exec_res = NULL; + if ( (long long int)SSL_write(sdata->tls_session, HTTP_MEM_WRITE_ERROR, (size_t)strlen(HTTP_MEM_WRITE_ERROR)) <= 0 ) + return RES_FAILED; + return RES_SUCCESS; + } + if ( (long long int)SSL_write(sdata->tls_session, sdata->send_buffer, r_size) <= 0 ){ + free( sdata->send_buffer ); + free_wh_output( wdt ); + wh_exec_res = NULL; + r_size = 0; + return RES_FAILED; + } + free( sdata->send_buffer ); + free_wh_output( wdt ); + wh_exec_res = NULL; + r_size = 0; + return RES_SUCCESS; +} diff --git a/https_server.h b/https_server.h new file mode 100644 index 0000000..4b1776f --- /dev/null +++ b/https_server.h @@ -0,0 +1,152 @@ +#include "https_response.h" + +char *get_client_ip( server_socket_t *svd, client_info_t *cl_info ){ + cl_info->client_ip = (char*)malloc( 8192 * sizeof(char) ); + if ( inet_ntop(AF_INET, &svd->client_info.sin_addr, cl_info->client_ip, (socklen_t)(8192 * sizeof(char))) != NULL ) + return cl_info->client_ip; + return "Unknown"; +} + +void clear_client_ip( client_info_t *clf ){ + if ( clf->client_ip != NULL ){ + free( clf->client_ip ); + clf->client_ip = NULL; + } + return; +} + +void https_server_event_loop( server_socket_t *sv, client_info_t *cl, server_config_t *svcfg, webhooks_data_t *wdata, http_request_data_t *http_req_data, log_file_t *logf ){ + int ev_count = 0; + socklen_t cl_info_len = (socklen_t)sizeof( sv->client_info ); + while ( (ev_count = poll(sv->pstruct, 1, 0)) != -1 ){ + if ( ev_count > 0 ){ + sv->client_socket_fd = accept( sv->socket_fd, (struct sockaddr*)&sv->client_info, &cl_info_len ); + if ( sv->client_socket_fd == -1 ){ + log_write( svcfg, logf, "Error: failed to accept connection" ); + continue; + } + sv->tls_session = SSL_new( sv->tls_context ); + if ( sv->tls_session == NULL ){ + shutdown( sv->client_socket_fd, SHUT_RDWR ); + close( sv->client_socket_fd ); + log_write( svcfg, logf, "Error: failed to create TLS session" ); + continue; + } + if ( SSL_set_fd(sv->tls_session, sv->client_socket_fd) != 1 ){ + shutdown( sv->client_socket_fd, SHUT_RDWR ); + close( sv->client_socket_fd ); + if ( sv->tls_session != NULL ) + SSL_free( sv->tls_session ); + log_write( svcfg, logf, "Error: failed to set client file descriptor" ); + continue; + } + SSL_set_accept_state( sv->tls_session ); + if ( SSL_accept(sv->tls_session) != 1 ){ + if ( sv->tls_session != NULL ) + SSL_shutdown( sv->tls_session ); + shutdown( sv->client_socket_fd, SHUT_RDWR ); + close( sv->client_socket_fd ); + if ( sv->tls_session != NULL ) + SSL_free( sv->tls_session ); + log_write( svcfg, logf, "Error: failed to initiate handshake" ); + continue; + } + if ( strcmp((const char*)svcfg->log_enabled->valuestring, "yes") == 0 ){ + fprintf( logf->log_file, "Received connection from %s\n", get_client_ip(sv, cl) ); + clear_client_ip( cl ); + } + sv->recv_peek = (char*)calloc( (size_t)((uint64_t)MAX_PEEK_BYTES), sizeof(char) ); + if ( sv->recv_peek == NULL ){ + SSL_shutdown( sv->tls_session ); + shutdown( sv->client_socket_fd, SHUT_RDWR ); + close( sv->client_socket_fd ); + if ( sv->tls_session != NULL ) + SSL_free( sv->tls_session ); + log_write( svcfg, logf, "Error: failed to allocate memory for reading amount of incoming data" ); + continue; + } + sv->recv_pending_bytes = (long long int)SSL_peek( sv->tls_session, sv->recv_peek, (long long int)MAX_PEEK_BYTES ); + if ( sv->recv_pending_bytes <= 0 || SSL_get_error(sv->tls_session, sv->recv_pending_bytes) != SSL_ERROR_NONE ){ + free( sv->recv_peek ); + sv->recv_peek = NULL; + SSL_shutdown( sv->tls_session ); + shutdown( sv->client_socket_fd, SHUT_RDWR ); + close( sv->client_socket_fd ); + if ( sv->tls_session != NULL ) + SSL_free( sv->tls_session ); + sv->recv_pending_bytes = 0; + log_write( svcfg, logf, "Error: no bytes were received" ); + continue; + } + free( sv->recv_peek ); + sv->recv_peek = NULL; + if ( sv->recv_pending_bytes > (long long int)MAX_RECV_BYTES ){ + SSL_shutdown( sv->tls_session ); + shutdown( sv->client_socket_fd, SHUT_RDWR ); + close( sv->client_socket_fd ); + if ( sv->tls_session != NULL ) + SSL_free( sv->tls_session ); + log_write( svcfg, logf, "Error: too many bytes received" ); + continue; + } + sv->recv_data = (char*)calloc( (size_t)sv->recv_pending_bytes, sizeof(char) ); + if ( sv->recv_data == NULL ){ + SSL_shutdown( sv->tls_session ); + close( sv->client_socket_fd ); + if ( sv->tls_session != NULL ) + SSL_free( sv->tls_session ); + log_write( svcfg, logf, "Error: failed to allocate memory for received bytes" ); + continue; + } + sv->recv_read_bytes = (long long int)SSL_read( sv->tls_session, sv->recv_data, sv->recv_pending_bytes ); + if ( sv->recv_read_bytes > 0 && SSL_get_error(sv->tls_session, sv->recv_read_bytes) == SSL_ERROR_NONE ){ + int res_status = send_response( sv, svcfg, wdata, http_req_data, logf ); + if ( res_status == RES_SUCCESS ){ + log_write( svcfg, logf, "Success, response sent" ); + } + else if ( res_status == RES_UNAUTHORIZED ){ + log_write( svcfg, logf, "Error: unauthorized request" ); + } + else + { + log_write( svcfg, logf, "Error: failed to send response" ); + } + res_status = 0; + } + else + { + log_write( svcfg, logf, "Error: no bytes received" ); + } + sv->recv_pending_bytes = 0; + sv->recv_read_bytes = 0; + free( sv->recv_data ); + sv->recv_data = NULL; + SSL_shutdown( sv->tls_session ); + shutdown( sv->client_socket_fd, SHUT_RDWR ); + close( sv->client_socket_fd ); + if ( sv->tls_session != NULL ) + SSL_free( sv->tls_session ); + } + } + perror( "poll" ); + cl_info_len = 0; + ev_count = 0; + return; +} + +void server_start( server_socket_t *srv, client_info_t *cli, server_config_t *sconf, webhooks_data_t *whdata, http_request_data_t *hreq, log_file_t *lfdt ){ + fputs( "Starting server\n", stdout ); + memset( &srv->client_info, 0, sizeof(srv->client_info) ); + whdata->wh_command = NULL; + whdata->wh_response = NULL; + whdata->wh_output = NULL; + srv->pstruct[0].fd = srv->socket_fd; + srv->pstruct[0].events = POLLIN; + if ( listen(srv->socket_fd, 1024) == -1 ){ + fputs( "Failed to start listening for connections\n", stderr ); + return; + } + fputs( "Server started\n", stdout ); + https_server_event_loop( srv, cli, sconf, whdata, hreq, lfdt ); + return; +} diff --git a/https_server_structs.h b/https_server_structs.h new file mode 100644 index 0000000..09b7074 --- /dev/null +++ b/https_server_structs.h @@ -0,0 +1,28 @@ +typedef struct server_socket{ + int socket_fd; + struct sockaddr_in sck_in; + int client_socket_fd; + struct sockaddr_in client_info; + struct pollfd pstruct[1]; + SSL *tls_session; + SSL_CTX *tls_context; + long long int recv_pending_bytes; + long long int recv_read_bytes; + char *recv_peek; + char *recv_data; + char *send_buffer; +} server_socket_t; + +typedef struct http_request_data{ + char *tok_prev; + char *tok_current; + char *request_copy; + size_t request_size; + char *request_method; + char *request_path; + char *request_body; +} http_request_data_t; + +typedef struct client_info{ + char *client_ip; +} client_info_t; diff --git a/load_config.h b/load_config.h new file mode 100644 index 0000000..165c676 --- /dev/null +++ b/load_config.h @@ -0,0 +1,78 @@ +#define CONFIG_LOAD_SUCCESS 0 +#define CONFIG_LOAD_FAILED -1 + +#define CONFIG_PARSE_SUCCESS 0 +#define CONFIG_PARSE_FAILED -1 + +int parse_config( server_config_t *svconfig, long long int cfg_len ){ + if ( svconfig->config_contents == NULL || cfg_len <= 0 ) + return CONFIG_PARSE_FAILED; + svconfig->config_parsed = cJSON_ParseWithLength( svconfig->config_contents, (size_t)cfg_len ); + if ( svconfig->config_parsed == NULL ) + return CONFIG_PARSE_FAILED; + svconfig->log_enabled = cJSON_GetObjectItemCaseSensitive( svconfig->config_parsed, "log_enabled" ); + if ( svconfig->log_enabled == NULL || (long long int)strlen(svconfig->log_enabled->valuestring) <= 0 ) + return CONFIG_PARSE_FAILED; + svconfig->log_file = cJSON_GetObjectItemCaseSensitive( svconfig->config_parsed, "log_file" ); + if ( svconfig->log_file == NULL || (long long int)strlen(svconfig->log_file->valuestring) <= 0 ) + return CONFIG_PARSE_FAILED; + svconfig->bind_address = cJSON_GetObjectItemCaseSensitive( svconfig->config_parsed, "bind_address" ); + if ( svconfig->bind_address == NULL || (long long int)strlen(svconfig->bind_address->valuestring) <= 0 ) + return CONFIG_PARSE_FAILED; + svconfig->bind_port = cJSON_GetObjectItemCaseSensitive( svconfig->config_parsed, "bind_port" ); + if ( svconfig->bind_port == NULL || (long long int)strlen(svconfig->bind_port->valuestring) <= 0 ) + return CONFIG_PARSE_FAILED; + svconfig->server_password = cJSON_GetObjectItemCaseSensitive( svconfig->config_parsed, "server_password" ); + if ( svconfig->server_password == NULL || (long long int)strlen(svconfig->server_password->valuestring) <= 0 ) + return CONFIG_PARSE_FAILED; + svconfig->cert_file = cJSON_GetObjectItemCaseSensitive( svconfig->config_parsed, "cert_file" ); + if ( svconfig->cert_file == NULL || (long long int)strlen(svconfig->cert_file->valuestring) <= 0 ) + return CONFIG_PARSE_FAILED; + svconfig->cert_key_file = cJSON_GetObjectItemCaseSensitive( svconfig->config_parsed, "cert_key_file" ); + if ( svconfig->cert_key_file == NULL || (long long int)strlen(svconfig->cert_key_file->valuestring) <= 0 ) + return CONFIG_PARSE_FAILED; + svconfig->webhooks_file = cJSON_GetObjectItemCaseSensitive( svconfig->config_parsed, "webhooks_file" ); + if ( svconfig->webhooks_file == NULL || (long long int)strlen(svconfig->webhooks_file->valuestring) <= 0 ) + return CONFIG_PARSE_FAILED; + return CONFIG_PARSE_SUCCESS; +} + +int load_config( char *config_file_name, server_config_t *sv_config ){ + if ( config_file_name == NULL || (long long int)strlen(config_file_name) <= 0 || strcmp((const char*)config_file_name, "") == 0 ) + return CONFIG_LOAD_FAILED; + FILE *cfg_file = fopen( config_file_name, "r" ); + if ( cfg_file == NULL ) + return CONFIG_LOAD_FAILED; + if ( fseek(cfg_file, 0, SEEK_END) == -1 ){ + fclose( cfg_file ); + return CONFIG_LOAD_FAILED; + } + long long int cfg_file_size = (long long int)ftell( cfg_file ); + if ( cfg_file_size <= 0 ){ + rewind( cfg_file ); + fclose( cfg_file ); + return CONFIG_LOAD_FAILED; + } + rewind( cfg_file ); + sv_config->config_contents = (char*)calloc( (size_t)cfg_file_size, sizeof(char) ); + if ( sv_config->config_contents == NULL ){ + fclose( cfg_file ); + cfg_file_size = 0; + return CONFIG_LOAD_FAILED; + } + if ( (long long int)fread(sv_config->config_contents, sizeof(char), (size_t)(cfg_file_size * sizeof(char)), cfg_file) == (long long int)-1 ){ + free( sv_config->config_contents ); + sv_config->config_contents = NULL; + fclose( cfg_file ); + cfg_file_size = 0; + return CONFIG_LOAD_FAILED; + } + if ( parse_config(sv_config, cfg_file_size) != CONFIG_PARSE_SUCCESS ){ + free( sv_config->config_contents ); + sv_config->config_contents = NULL; + return CONFIG_LOAD_FAILED; + } + free( sv_config->config_contents ); + sv_config->config_contents = NULL; + return CONFIG_LOAD_SUCCESS; +} diff --git a/load_webhooks.h b/load_webhooks.h new file mode 100644 index 0000000..5c3a60b --- /dev/null +++ b/load_webhooks.h @@ -0,0 +1,56 @@ +#define WH_LOAD_SUCCESS 0 +#define WH_LOAD_FAILED -1 + +#define WH_PARSE_SUCCESS 0 +#define WH_PARSE_FAILED -1 + +int parse_webhooks( webhooks_data_t *whdt, size_t wh_file_len ){ + if ( whdt->webhooks_config == NULL || (uint64_t)strlen(whdt->webhooks_config) <= 0 ) + return WH_PARSE_FAILED; + if ( wh_file_len <= 0 ) + return WH_PARSE_FAILED; + whdt->webhooks_parsed = cJSON_ParseWithLength( whdt->webhooks_config, wh_file_len ); + if ( whdt->webhooks_parsed == NULL ) + return WH_PARSE_FAILED; + return WH_PARSE_SUCCESS; +} + +int load_webhooks( char *wh_file_name, webhooks_data_t *wdt ){ + if ( wh_file_name == NULL || (uint64_t)strlen(wh_file_name) <= 0 ) + return WH_LOAD_FAILED; + FILE *whconf = fopen( wh_file_name, "r" ); + if ( whconf == NULL ) + return WH_LOAD_FAILED; + if ( fseek(whconf, 0, SEEK_END) == -1 ){ + fclose( whconf ); + return WH_LOAD_FAILED; + } + long long int wh_file_size = (long long int)ftell( whconf ); + if ( wh_file_size <= 0 ){ + wh_file_size = 0; + fclose( whconf ); + return WH_LOAD_FAILED; + } + rewind( whconf ); + wdt->webhooks_config = (char*)calloc( (size_t)wh_file_size, sizeof(char) ); + if ( wdt->webhooks_config == NULL ){ + wh_file_size = 0; + fclose( whconf ); + return WH_LOAD_FAILED; + } + if ( (long long int)fread(wdt->webhooks_config, sizeof(char), (size_t)(wh_file_size * sizeof(char)), whconf) == (long long int)-1 ){ + wh_file_size = 0; + free( wdt->webhooks_config ); + fclose( whconf ); + return WH_LOAD_FAILED; + } + fclose( whconf ); + if ( parse_webhooks(wdt, wh_file_size) != WH_PARSE_SUCCESS ){ + free( wdt->webhooks_config ); + wh_file_size = 0; + return WH_LOAD_FAILED; + } + wh_file_size = 0; + free( wdt->webhooks_config ); + return WH_LOAD_SUCCESS; +} @@ -0,0 +1,26 @@ +#define LOG_INIT_SUCCESS 0 +#define LOG_INIT_FAILED -1 + +int log_init( server_config_t *sc, log_file_t *lf ){ + if ( strcmp((const char*)sc->log_enabled->valuestring, "no") == 0 ) + return LOG_INIT_SUCCESS; + if ( sc->log_file == NULL || (long long int)strlen(sc->log_file->valuestring) <= 0 ) + return LOG_INIT_FAILED; + lf->log_file = fopen( sc->log_file->valuestring, "a" ); + if ( lf->log_file == NULL ) + return LOG_INIT_FAILED; + if ( syscall(SYS_chmod, (const char*)sc->log_file->valuestring, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1 ) + return LOG_INIT_FAILED; + return LOG_INIT_SUCCESS; +} + +void log_write( server_config_t *scd, log_file_t *lf_info, char *log_message ){ + if ( strcmp((const char*)scd->log_enabled->valuestring, "no") == 0 ) + return; + if ( lf_info->log_file == NULL ) + return; + fprintf( lf_info->log_file, "%s\n", log_message ); + if ( fflush(lf_info->log_file) == EOF ) + fputs( "WARNING: failed to write buffered data to disk\n", stderr ); + return; +} diff --git a/socket_init.h b/socket_init.h new file mode 100644 index 0000000..ceddb9c --- /dev/null +++ b/socket_init.h @@ -0,0 +1,32 @@ +#define SOCKET_INIT_SUCCESS 0 +#define SOCKET_INIT_FAILED -1 + +int init_socket( server_socket_t *sckdata, server_config_t *scfg ){ + if ( (uint16_t)atoi(scfg->bind_port->valuestring) == 0 ) + return SOCKET_INIT_FAILED; + if ( scfg->bind_address == NULL || (long long int)strlen(scfg->bind_address->valuestring) <= 0 || strcmp((const char*)scfg->bind_address->valuestring, "") == 0 ) + return SOCKET_INIT_FAILED; + sckdata->socket_fd = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ); + if ( sckdata->socket_fd == -1 ) + return SOCKET_INIT_FAILED; + sckdata->sck_in.sin_family = AF_INET; + sckdata->sck_in.sin_port = (in_port_t)htons( (uint16_t)atoi(scfg->bind_port->valuestring) ); + sckdata->sck_in.sin_addr.s_addr = (uint32_t)inet_addr( (const char*)scfg->bind_address->valuestring ); + int reuseaddr_val = 1; + int reuseport_val = 1; + if ( setsockopt(sckdata->socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_val, (socklen_t)sizeof(reuseaddr_val)) == -1 ) + return SOCKET_INIT_FAILED; + if ( setsockopt(sckdata->socket_fd, SOL_SOCKET, SO_REUSEPORT, &reuseport_val, (socklen_t)sizeof(reuseport_val)) == -1 ) + return SOCKET_INIT_FAILED; + if ( bind(sckdata->socket_fd, (struct sockaddr*)&sckdata->sck_in, (socklen_t)sizeof(sckdata->sck_in)) == -1 ) + return SOCKET_INIT_FAILED; + sckdata->tls_context = SSL_CTX_new( TLS_server_method() ); + if ( sckdata->tls_context == NULL ) + return SOCKET_INIT_FAILED; + if ( SSL_CTX_use_certificate_file(sckdata->tls_context, (const char*)scfg->cert_file->valuestring, SSL_FILETYPE_PEM) != 1 ) + return SOCKET_INIT_FAILED; + if ( SSL_CTX_use_PrivateKey_file(sckdata->tls_context, (const char*)scfg->cert_key_file->valuestring, SSL_FILETYPE_PEM) != 1 ) + return SOCKET_INIT_FAILED; + SSL_CTX_set_verify( sckdata->tls_context, SSL_VERIFY_NONE, NULL ); + return SOCKET_INIT_SUCCESS; +} diff --git a/structs.h b/structs.h new file mode 100644 index 0000000..e493c5f --- /dev/null +++ b/structs.h @@ -0,0 +1,25 @@ +typedef struct server_config{ + char *config_contents; + cJSON *config_parsed; + cJSON *log_enabled; + cJSON *log_file; + cJSON *bind_address; + cJSON *bind_port; + cJSON *server_password; + cJSON *cert_file; + cJSON *cert_key_file; + cJSON *webhooks_file; +} server_config_t; + +typedef struct webhooks_data{ + char *webhooks_config; + cJSON *webhooks_parsed; + cJSON *wh_exec_data; + cJSON *wh_command; + cJSON *wh_response; + char *wh_output; +} webhooks_data_t; + +typedef struct log_file{ + FILE *log_file; +} log_file_t; diff --git a/webhook_exec.h b/webhook_exec.h new file mode 100644 index 0000000..445ed88 --- /dev/null +++ b/webhook_exec.h @@ -0,0 +1,58 @@ +char *webhook_exec( webhooks_data_t *wh_data, char *wh_endpoint ){ + if ( wh_data->webhooks_parsed == NULL ) + return "Error: webhooks haven't been parsed"; + wh_data->wh_exec_data = cJSON_GetObjectItemCaseSensitive( wh_data->webhooks_parsed, wh_endpoint ); + if ( wh_data->wh_exec_data == NULL ) + return "Error: no such webhook found"; + wh_data->wh_command = cJSON_GetObjectItemCaseSensitive( wh_data->wh_exec_data, "cmd" ); + if ( wh_data->wh_command == NULL || (uint64_t)strlen(wh_data->wh_command->valuestring) <= 0 ){ + wh_data->wh_exec_data = NULL; + return "Error: no command set for given webhook"; + } + wh_data->wh_response = cJSON_GetObjectItemCaseSensitive( wh_data->wh_exec_data, "response" ); + if ( wh_data->wh_response == NULL || (uint64_t)strlen(wh_data->wh_response->valuestring) <= 0 ){ + wh_data->wh_command = NULL; + wh_data->wh_exec_data = NULL; + return "Error: no response specified"; + } + if ( strcmp((const char*)wh_data->wh_response->valuestring, "output") != 0 ){ + system( wh_data->wh_command->valuestring ); + wh_data->wh_command = NULL; + wh_data->wh_exec_data = NULL; + return wh_data->wh_response->valuestring; + } + wh_data->wh_response = NULL; + wh_data->wh_output = (char*)calloc( (size_t)MAX_WEBHOOK_OUTPUT_LENGTH, sizeof(char) ); + FILE *wh_proc = popen( wh_data->wh_command->valuestring, "r" ); + if ( wh_proc == NULL ){ + free( wh_data->wh_output ); + wh_data->wh_output = NULL; + wh_data->wh_command = NULL; + wh_data->wh_exec_data = NULL; + return "Error: failed to execute webhook command"; + } + size_t read_bytes = 0; + char *current_line = (char*)calloc( 8192, sizeof(char) ); + while ( fgets(current_line, 8192 * sizeof(char), wh_proc) != NULL ){ + if ( (size_t)((size_t)strlen(current_line) + read_bytes) > (size_t)MAX_WEBHOOK_OUTPUT_LENGTH ) + break; + strncat( wh_data->wh_output, current_line, (size_t)strlen(current_line) ); + read_bytes += (size_t)strlen( current_line ); + memset( current_line, 0, 8192 * sizeof(char) ); + } + free( current_line ); + pclose( wh_proc ); + if ( wh_data->wh_output == NULL || (size_t)strlen(wh_data->wh_output) <= 0 ) + return "Error: failed to get output"; + return wh_data->wh_output; +} + +void free_wh_output( webhooks_data_t *hooksdata ){ + if ( hooksdata->wh_output != NULL ){ + free( hooksdata->wh_output ); + hooksdata->wh_output = NULL; + } + if ( hooksdata->wh_response != NULL ) + hooksdata->wh_response = NULL; + return; +} diff --git a/webhooks.example.json b/webhooks.example.json new file mode 100644 index 0000000..62320d1 --- /dev/null +++ b/webhooks.example.json @@ -0,0 +1,10 @@ +{ + "/example": { + "cmd": "echo example webhook that sends output of command as response", + "response": "output" + }, + "/example2": { + "cmd": "echo this webhook will only send the response set it response", + "response": "this will be sent as response instead of command output" + } +} |