summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstderr64 <linuxwizard@voidnet.dy.fi>2023-08-17 21:19:10 +0300
committerstderr64 <linuxwizard@voidnet.dy.fi>2023-08-17 21:19:10 +0300
commitf484331cf374031f68566162f16ba00eedc1b7f0 (patch)
treec5632b5b7c574bf31642cdec12a409adfbabb31c
downloadCWebHook-f484331cf374031f68566162f16ba00eedc1b7f0.tar.gz
CWebHook-f484331cf374031f68566162f16ba00eedc1b7f0.tar.zst
Recreated repository
-rw-r--r--.gitignore2
-rw-r--r--Makefile10
-rw-r--r--auth.h10
-rw-r--r--config.example.json10
-rw-r--r--cwebhook.c127
-rw-r--r--http_headers.h88
-rw-r--r--https_response.h79
-rw-r--r--https_server.h152
-rw-r--r--https_server_structs.h28
-rw-r--r--load_config.h78
-rw-r--r--load_webhooks.h56
-rw-r--r--log.h26
-rw-r--r--socket_init.h32
-rw-r--r--structs.h25
-rw-r--r--webhook_exec.h58
-rw-r--r--webhooks.example.json10
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
diff --git a/auth.h b/auth.h
new file mode 100644
index 0000000..0e5c0ea
--- /dev/null
+++ b/auth.h
@@ -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;
+}
diff --git a/log.h b/log.h
new file mode 100644
index 0000000..6fd3235
--- /dev/null
+++ b/log.h
@@ -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"
+ }
+}