From 4243ebae5e33e7b8bad7d73fdf5f41ca9ce95250 Mon Sep 17 00:00:00 2001 From: alashkova Date: Wed, 21 Aug 2024 12:47:16 +0300 Subject: [PATCH] =?UTF-8?q?SUPPORT-8455.=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB?= =?UTF-8?q?=D1=8C=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 + CMakeLists.txt | 101 +++++ README.md | 106 +++++ conf/ervu-esia-module.conf | 14 + src/config.h.in | 6 + src/fcgisrv/fcgi_map.c | 103 +++++ src/fcgisrv/fcgi_server.c | 305 +++++++++++++ src/fcgisrv/fcgi_server.h | 73 +++ src/fcgisrv/fcgi_server_internal.h | 70 +++ src/fcgisrv/fcgi_thread.c | 178 ++++++++ src/fcgisrv/fcgi_utils.c | 79 ++++ src/fcgisrv/fcgi_utils.h | 189 ++++++++ src/fcgisrv/fcgi_worker.c | 139 ++++++ src/main.c | 101 +++++ src/main_conf.c | 75 ++++ src/main_conf.h | 15 + src/master.c | 220 +++++++++ src/master.h | 9 + src/modules/service_sign.c | 375 ++++++++++++++++ src/modules/service_sign.h | 29 ++ src/service_manager.c | 245 ++++++++++ src/service_manager.h | 76 ++++ src/utils/base64.c | 82 ++++ src/utils/base64.h | 10 + src/utils/capi.c | 37 ++ src/utils/capi.h | 154 +++++++ src/utils/conf_file_context.c | 700 +++++++++++++++++++++++++++++ src/utils/conf_file_context.h | 72 +++ src/utils/cryptopro.c | 295 ++++++++++++ src/utils/cryptopro.h | 29 ++ src/utils/gconf_file.c | 244 ++++++++++ src/utils/gconf_file.h | 68 +++ src/utils/library.c | 63 +++ src/utils/library.h | 18 + src/utils/logger.c | 15 + src/utils/logger.h | 111 +++++ src/utils/str_t.c | 51 +++ src/utils/str_t.h | 95 ++++ src/utils/types.h | 176 ++++++++ src/version.h.in | 6 + src/worker.c | 137 ++++++ src/worker.h | 13 + 42 files changed, 4889 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 conf/ervu-esia-module.conf create mode 100644 src/config.h.in create mode 100644 src/fcgisrv/fcgi_map.c create mode 100644 src/fcgisrv/fcgi_server.c create mode 100644 src/fcgisrv/fcgi_server.h create mode 100644 src/fcgisrv/fcgi_server_internal.h create mode 100644 src/fcgisrv/fcgi_thread.c create mode 100644 src/fcgisrv/fcgi_utils.c create mode 100644 src/fcgisrv/fcgi_utils.h create mode 100644 src/fcgisrv/fcgi_worker.c create mode 100644 src/main.c create mode 100644 src/main_conf.c create mode 100644 src/main_conf.h create mode 100644 src/master.c create mode 100644 src/master.h create mode 100644 src/modules/service_sign.c create mode 100644 src/modules/service_sign.h create mode 100644 src/service_manager.c create mode 100644 src/service_manager.h create mode 100644 src/utils/base64.c create mode 100644 src/utils/base64.h create mode 100644 src/utils/capi.c create mode 100644 src/utils/capi.h create mode 100644 src/utils/conf_file_context.c create mode 100644 src/utils/conf_file_context.h create mode 100644 src/utils/cryptopro.c create mode 100644 src/utils/cryptopro.h create mode 100644 src/utils/gconf_file.c create mode 100644 src/utils/gconf_file.h create mode 100644 src/utils/library.c create mode 100644 src/utils/library.h create mode 100644 src/utils/logger.c create mode 100644 src/utils/logger.h create mode 100644 src/utils/str_t.c create mode 100644 src/utils/str_t.h create mode 100644 src/utils/types.h create mode 100644 src/version.h.in create mode 100644 src/worker.c create mode 100644 src/worker.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fce63e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +CMakeFiles/ +CMakeLists.txt.user +src/config.h +src/version.h + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..33d236c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,101 @@ +CMAKE_MINIMUM_REQUIRED (VERSION 3.0) + +PROJECT (ervu-esia-module VERSION 1.0.0 LANGUAGES C) + +SET (CMAKE_C_COMPILER "gcc") + +IF (CMAKE_VERBOSE) + SET (CMAKE_VERBOSE_MAKEFILE 1) +ENDIF () + +SET (CMAKE_C_FLAGS "-Wall -Wextra -Werror -Wno-unused-parameter") +SET (CMAKE_C_FLAGS_DEBUG "-g -O0 -DDEBUG") +SET (CMAKE_C_FLAGS_RELEASE "-g -O2 -DNDEBUG") +SET (CMAKE_EXE_LINKER_FLAGS "-Wl,--no-undefined") + +SET (SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) +SET (UTILS_DIR ${SOURCE_DIR}/utils) +SET (FCGISRV_DIR ${SOURCE_DIR}/fcgisrv) +SET (MODULES_DIR ${SOURCE_DIR}/modules) + +INCLUDE_DIRECTORIES (${SOURCE_DIR}) + +# PKG-CONFIG +FIND_PACKAGE (PkgConfig REQUIRED) + +# GLIB2 +MESSAGE ("") +MESSAGE ("Try to find the glib-2.0..") +PKG_CHECK_MODULES (GLIB2 REQUIRED glib-2.0) + +IF (NOT GLIB2_FOUND) + MESSAGE (SEND_ERROR "Can not find glib-2.0") +ELSE () + MESSAGE ("glib-2.0 is found: ") + MESSAGE ("GLIB2_LIB_INCLUDE_DIR : " ${GLIB2_LIB_INCLUDE_DIR}) + MESSAGE ("GLIB2_INCLUDE_DIR : " ${GLIB2_INCLUDE_DIR}) + MESSAGE ("GLIB2_LIBRARY_DIR : " ${GLIB2_LIBRARY_DIR}) + MESSAGE ("GLIB2_INCLUDE_DIRS : " ${GLIB2_INCLUDE_DIRS}) + MESSAGE ("GLIB2_LIBRARY_DIRS : " ${GLIB2_LIBRARY_DIRS}) + MESSAGE ("GLIB2_LIBRARIES : " ${GLIB2_LIBRARIES}) +ENDIF (NOT GLIB2_FOUND) +MESSAGE ("") + +# set glib2 path vars +INCLUDE_DIRECTORIES (${GLIB2_INCLUDE_DIRS}) + +SET (DEP_LIBS + -lpthread + -lfcgi + -lglib-2.0 + -ldl +) + +# version.h +CONFIGURE_FILE (${SOURCE_DIR}/version.h.in ${SOURCE_DIR}/version.h) + +# config.h +IF (NOT DEFINED CONFIG_NAME) + SET (CONFIG_NAME /etc/ervu-esia-module.conf) +ENDIF () + +MESSAGE ("CONFIG_NAME: " ${CONFIG_NAME}) +MESSAGE ("") + +CONFIGURE_FILE (${SOURCE_DIR}/config.h.in ${SOURCE_DIR}/config.h) + +SET (CRYPTOPRO_INCLUDE_DIRS + /opt/cprocsp/include/cpcsp + /opt/cprocsp/include/pki +) +MESSAGE("CRYPTOPRO_INCLUDE_DIRS : " ${CRYPTOPRO_INCLUDE_DIRS}) + +ADD_DEFINITIONS(-DUNIX) +INCLUDE_DIRECTORIES ("${CRYPTOPRO_INCLUDE_DIRS}") + +FILE (GLOB_RECURSE HEADERS "${SOURCE_DIR}/*.h") + +ADD_EXECUTABLE (${PROJECT_NAME} + ${HEADERS} + ${SOURCE_DIR}/main.c + ${SOURCE_DIR}/main_conf.c + ${SOURCE_DIR}/master.c + ${SOURCE_DIR}/service_manager.c + ${SOURCE_DIR}/worker.c + ${UTILS_DIR}/base64.c + ${UTILS_DIR}/capi.c + ${UTILS_DIR}/conf_file_context.c + ${UTILS_DIR}/cryptopro.c + ${UTILS_DIR}/gconf_file.c + ${UTILS_DIR}/library.c + ${UTILS_DIR}/logger.c + ${UTILS_DIR}/str_t.c + ${FCGISRV_DIR}/fcgi_map.c + ${FCGISRV_DIR}/fcgi_server.c + ${FCGISRV_DIR}/fcgi_thread.c + ${FCGISRV_DIR}/fcgi_utils.c + ${FCGISRV_DIR}/fcgi_worker.c + ${MODULES_DIR}/service_sign.c +) + +TARGET_LINK_LIBRARIES (${PROJECT_NAME} ${DEP_LIBS}) diff --git a/README.md b/README.md new file mode 100644 index 0000000..596baa6 --- /dev/null +++ b/README.md @@ -0,0 +1,106 @@ +## Краткое описание + +Модуль предназначен для подписи данных. + +Приложение принимает POST-запрос по протоколу FastCGI (Content-Type: text/plain). +Подписывает строку, полученную в теле запроса. +В ответе возвращает подпись в формате urlSafeBase64 (Content-Type: text/plain). + +Пример выполнения запроса: +``` +$ curl -v http://127.0.0.1:8080/sign -H "Content-Type: text/plain" -d "test" +* Trying 127.0.0.1:8080... +* Connected to 127.0.0.1 (127.0.0.1) port 8080 +> POST /sign HTTP/1.1 +> Host: 127.0.0.1:8080 +> User-Agent: curl/8.4.0 +> Accept: */* +> Content-Type: text/plain +> Content-Length: 4 +> +< HTTP/1.1 200 OK +< Server: nginx/1.24.0 +< Date: Fri, 16 Aug 2024 07:33:13 GMT +< Content-Type: text/plain +< Transfer-Encoding: chunked +< Connection: keep-alive +< +urlSafeBase64_of_signed_string_test +``` + +## Сборка и установка + +### Подготовка + +1. Установить КриптоПро. + +2. Установить сертификат для пользователя, под которым будет запускаться данное приложение. + +3. Установить nginx. + +4. В настройках nginx добавить перенаправление на данное приложение: +``` +location = /sign { + fastcgi_pass localhost:9009; + include fastcgi_params; +} +``` + +### Cборка из исходников + +Для **сборки** необходимы следующие библиотеки: +- glib2-devel +- libfcgi-devel +- lsb-cprocsp-base +- lsb-cprocsp-rdr-64 +- lsb-cprocsp-kc1-64 +- lsb-cprocsp-capilite-64 +- cprocsp-curl-64 +- lsb-cprocsp-ca-certs +- cprocsp-pki-cades-64 +- lsb-cprocsp-devel + +Для **установки** необходимы следующие библиотеки: +- glib2 +- libfcgi +- lsb-cprocsp-base +- lsb-cprocsp-rdr-64 +- lsb-cprocsp-kc1-64 +- lsb-cprocsp-capilite-64 +- cprocsp-curl-64 +- lsb-cprocsp-ca-certs +- cprocsp-pki-cades-64 + +Для сборки и установки приложения из исходников, выполните следующие команды: +``` +$ mkdir .build && cd .build +$ cmake .. +$ make -j$(nproc) +``` + +Чтобы задать произвольное имя конфигурационного файла, при сборке приложения необходимо задать параметр CONFIG_NAME. +Пример: +``` +cmake -DCONFIG_NAME=/opt/ervu-esia-module.conf +``` + +По умолчанию, имя конфигурационного файла /etc/ervu-esia-module.conf. + +### Настройка + +Приложение настраивается в конфигурационном файле, заданном на этапе сборки (по умолчанию, /etc/ervu-esia-module.conf). + +- В секции **\[main\]** задать количество воркеров +worker_processes = 10 *\# значение по умолчанию: 10* + +- В секции **\[fcgi\]** задать настройки fcgi-сервера: +fcgi_listen_port = 9009 *\# значение по умолчанию: 9009, должно совпадать со значением в nginx.conf* +fcgi_listen_host = 127.0.0.1 *\# значение по умолчанию: 127.0.0.1, должно совпадать со значением в nginx.conf* +fcgi_thread_pool_size = 1 *\# значение по умолчанию: 1* + +- В секции **\[sign\]** задать настройки модуля подписания: +location = /sign *\# значение по умолчанию: /sign, должно совпадать со значением в nginx.conf* +cp_file = libcapi20.so *\# путь до файла библиотеки криптопровайдера* +signer_subject = signer@example.ru *\# email, ИНН, СНИЛС или любая другая строка из свойства контейнера «Субъект»* +pin = \*\*\*\* *\# пароль от контейнера* + diff --git a/conf/ervu-esia-module.conf b/conf/ervu-esia-module.conf new file mode 100644 index 0000000..6019ba5 --- /dev/null +++ b/conf/ervu-esia-module.conf @@ -0,0 +1,14 @@ +[main] +#worker_processes = 10 + +[fcgi] +fcgi_listen_port = 9009 +#fcgi_listen_host = 127.0.0.1 +#fcgi_thread_pool_size = 1 + +[sign] +#location = /sign +cp_file = libcapi20.so +signer_subject = signer@example.ru +pin = **** + diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..3b2dff4 --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,6 @@ +#ifndef CONFIG_H_INCLUDED +#define CONFIG_H_INCLUDED + +#define CONF_NAME "@CONFIG_NAME@" + +#endif // CONFIG_H_INCLUDED diff --git a/src/fcgisrv/fcgi_map.c b/src/fcgisrv/fcgi_map.c new file mode 100644 index 0000000..fef04c5 --- /dev/null +++ b/src/fcgisrv/fcgi_map.c @@ -0,0 +1,103 @@ +#include "fcgi_server_internal.h" + +#include + + +static int +fcgi_map_item_test(fcgi_handler_map_item_t* item, char* path) +{ + assert(item != NULL); + assert(path != NULL); + + LOG_DEBUG("fcgi_map_item_test. item->path: %s; path: %s", item->path, path); + return strcmp(item->path, path); +} + + +static int +fcgi_map_item_set(fcgi_handler_map_item_t* item, const char* path, const fcgi_handler_t* handler) +{ + assert(item != NULL); + assert(path != NULL); + assert(handler != NULL); + + item->path = strdup(path); + if (item->path == NULL) { + LOG_ERROR("fcgi_map_item_set. strdup failed"); + return -1; + } + + item->handler = *handler; + return 0; +} + + +static void +fcgi_map_item_clear(fcgi_handler_map_item_t* item) +{ + if (item == NULL) { + return; + } + + free(item->path); + memset(item, 0, sizeof(fcgi_handler_map_item_t)); +} + + +int +fcgi_handler_map_set(fcgi_handler_map_t* map, char* path, fcgi_handler_t* handler) +{ + size_t i; + + assert(map != NULL); + assert(path != NULL); + assert(handler != NULL); + + LOG_DEBUG("fcgi_handler_map_set. map->num = %zu", map->num); + LOG_DEBUG("fcgi_handler_map_set. path = %s", path); + + for (i = 0; i < map->num; ++i) { + if (!fcgi_map_item_test(&map->locations[i], path)) { + fcgi_map_item_clear(&map->locations[i]); + break; + } + } + + if (i >= FCGI_MAX_HANDLERS_NUM) { + LOG_ERROR("fcgi_handler_map_set. exceeded FCGI_MAX_HANDLERS_NUM(%d)", + FCGI_MAX_HANDLERS_NUM); + return -1; + } + + if (fcgi_map_item_set(&map->locations[i], path, handler) < 0) { + LOG_ERROR("fcgi_handler_map_set. fcgi_map_item_set failed"); + return -1; + } + + map->num++; + + return 0; +} + + +fcgi_handler_t* +fcgi_handler_map_get(fcgi_handler_map_t* map, char* path) +{ + fcgi_handler_t* handler = NULL; + size_t i; + + assert(map != NULL); + assert(path != NULL); + + LOG_DEBUG("fcgi_handler_map_get. path: %s", path); + LOG_DEBUG("fcgi_handler_map_get. map->num = %zu", map->num); + + for (i = 0; i < map->num; ++i) { + if (!fcgi_map_item_test(&map->locations[i], path)) { + handler = &map->locations[i].handler; + break; + } + } + + return handler; +} diff --git a/src/fcgisrv/fcgi_server.c b/src/fcgisrv/fcgi_server.c new file mode 100644 index 0000000..f5a15d1 --- /dev/null +++ b/src/fcgisrv/fcgi_server.c @@ -0,0 +1,305 @@ +#include "fcgi_server_internal.h" + +#include "utils/conf_file_context.h" + +#include +#include +#include +#include +#include + +#include + + +#define FCGI_CONF_SECTION "fcgi" + +#define FCGI_CONF_KEY_LISTEN_HOST "fcgi_listen_host" +#define FCGI_CONF_KEY_LISTEN_PORT "fcgi_listen_port" +#define FCGI_CONF_KEY_POOL_SIZE "fcgi_thread_pool_size" + +/* default configuration values: */ +static const char *FCGI_CONF_DEFAULT_LISTEN_HOST = "127.0.0.1"; +static const int FCGI_CONF_DEFAULT_POOL_SIZE = 1; + +#define FCGI_PENDING_CONN_QUEUE_MAX_LENGTH 16 +#define FCGI_DEFAULT_TCP_LISTEN_PORT 9009 +#define FCGI_DEFAULT_TCP_LISTEN_ADDRESS "127.0.0.1" + +static pthread_mutex_t server_mutex = PTHREAD_MUTEX_INITIALIZER; + + +int +fcgi_load_conf(const char* filename, fcgi_conf_t* conf) +{ + assert(filename != NULL && "fcgi_load_conf"); + assert(conf != NULL && "fcgi_load_conf"); + + memset(conf, 0, sizeof(fcgi_conf_t)); + + conf_file_field_t fields[] = { + { + FCGI_CONF_SECTION, + FCGI_CONF_KEY_LISTEN_HOST, + &conf->listen.host, + CONF_FILE_VALUE_STRING, + CONF_FILE_VALUE_HOSTNAME, + &FCGI_CONF_DEFAULT_LISTEN_HOST + }, + { + FCGI_CONF_SECTION, + FCGI_CONF_KEY_LISTEN_PORT, + &conf->listen.port, + CONF_FILE_VALUE_INTEGER, + CONF_FILE_VALUE_TCP_PORT, + NULL + }, + { + FCGI_CONF_SECTION, + FCGI_CONF_KEY_POOL_SIZE, + &conf->thread_pool_size, + CONF_FILE_VALUE_INTEGER, + CONF_FILE_VALUE_POSITIVE_INT, + &FCGI_CONF_DEFAULT_POOL_SIZE + }, + }; + + if (conf_file_load_from_file(filename, fields, sizeof(fields) / sizeof(conf_file_field_t))) { + goto error; + } + + LOG_DEBUG("fcgi_conf->listen: %s:%d", conf->listen.host, conf->listen.port); + + return 0; + +error: + fcgi_clear_conf(conf); + LOG_ERROR("fcgi_load_conf exit with error"); + return -1; +} + + +void +fcgi_clear_conf(fcgi_conf_t* conf) +{ + if (conf != NULL) { + free(conf->listen.host); + memset(conf, 0, sizeof(fcgi_conf_t)); + } + + return; +} + + +static int +fcgi_start_listen(const fcgi_tcp_t* tcp_conf, fcgi_listen_t* listen_obj) +{ + int port; + char* host; + struct sockaddr_in st_addr; + int rc = -1; + int on; + + LOG_TRACE("fcgi_start_listen enter"); + + assert(tcp_conf != NULL && "fcgi_start_listen"); + assert(listen_obj != NULL && "fcgi_start_listen"); + + rc = pthread_mutex_init(&listen_obj->accept_mutex, NULL); + if (rc != 0) { + LOG_ERROR("Could not initialize accept_mutex for listen socket. Desc: %s", + strerror(rc)); + goto error; + } + listen_obj->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (listen_obj->fd == -1) { + LOG_ERROR("Could not create socket file descriptor"); + goto error; + } + + LOG_DEBUG("fcgi_start_listen: listen_obj->fd = %d", listen_obj->fd); + + memset(&st_addr, 0, sizeof(struct sockaddr_in)); + + port = tcp_conf->port != 0 ? tcp_conf->port : FCGI_DEFAULT_TCP_LISTEN_PORT; + host = tcp_conf->host != NULL ? tcp_conf->host : FCGI_DEFAULT_TCP_LISTEN_ADDRESS; // "0.0.0.0" htonl(INADDR_ANY) + + st_addr.sin_family = PF_INET; + st_addr.sin_port = htons(port); + + if (inet_pton(AF_INET, host, &st_addr.sin_addr) != 1) { + LOG_ERROR("Could not parse bind-address '%s'. Desc: %s", host, strerror(errno)); + goto error; + } + + on = 1; + if (setsockopt(listen_obj->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) { + LOG_WARN("Could not set REUSEADDR. Desc: %s", strerror(errno)); + } + + if (bind(listen_obj->fd, (struct sockaddr*)&st_addr, sizeof(struct sockaddr_in)) != 0) { + LOG_ERROR("Could not bind socket to %s:%d. Desc: %s", host, port, strerror(errno)); + goto error; + } + + if (listen(listen_obj->fd, FCGI_PENDING_CONN_QUEUE_MAX_LENGTH) != 0) { + LOG_ERROR("Could not start listening on %s:%d. Desc: %s", + host, port, strerror(errno)); + goto error; + } + + LOG_DEBUG("fcgi_start_listen exit with success. host: %s, port: %d", host, port); + return 0; + +error: + LOG_ERROR("fcgi_start_listen exit with error"); + return -1; +} + + +static int +fcgi_stop_listen(const fcgi_listen_t* listen) +{ + LOG_TRACE("fcgi_stop_listen enter"); + + assert(listen != NULL && "fcgi_stop_listen"); + + int rc = 0; + + if (shutdown(listen->fd, SHUT_RDWR) != 0) { + LOG_ERROR("fcgi_stop_listen. shutdown(%d, SHUT_RDWR) failed. Desc: %s", + listen->fd, strerror(errno)); + rc = -1; + } + + if (close(listen->fd) != 0) { + LOG_ERROR("fcgi_stop_listen. close(%d) failed. Desc: %s", + listen->fd, strerror(errno)); + rc = -1; + } + + LOG_TRACE("fcgi_stop_listen exit"); + return rc; +} + + +HFcgi +fcgi_create_server(const fcgi_conf_t* conf) +{ + LOG_TRACE("fcgi_create_server enter"); + + assert(conf != NULL && "fcgi_create_server"); + + fcgi_server_t* hfcgi = NULL; + + if (FCGX_Init() != 0) { + LOG_ERROR("Could not init libfcgi"); + goto error; + } + + hfcgi = calloc(1, sizeof(fcgi_server_t)); + + if (hfcgi == NULL) { + LOG_ERROR("Could not allocate memory for HFcgi handle"); + goto error; + } + + hfcgi->mutex = &server_mutex; + + if (fcgi_start_listen(&conf->listen, &hfcgi->listen) != 0) { + LOG_ERROR("fcgi_start_listen failed"); + goto error; + } + + hfcgi->thread_pool_size = conf->thread_pool_size; + + LOG_TRACE("fcgi_create_server exit with success"); + return hfcgi; + +error: + free(hfcgi); + LOG_ERROR("fcgi_create_server exit with error"); + return NULL; +} + + +int +fcgi_register_handler(HFcgi hfcgi, char* path, fcgi_handler_t* handler) +{ + return fcgi_handler_map_set(&hfcgi->handler_map, path, handler); +} + + +int +fcgi_run_server(HFcgi hfcgi) +{ + LOG_TRACE("fcgi_run_server enter"); + + if (hfcgi == NULL) { + goto error; + } + + if (fcgi_server_thread_pool_create(&hfcgi->thread_pool, + hfcgi->thread_pool_size, + hfcgi) != 0) { + LOG_ERROR("fcgi_server_thread_pool_create failed"); + goto error; + } + + LOG_TRACE("fcgi_run_server exit with success"); + return 0; + +error: + LOG_ERROR("fcgi_run_server exit with error"); + return -1; +} + + +int +fcgi_stop_server(HFcgi hfcgi) +{ + int rc = 0; + + LOG_TRACE("fcgi_stop_server enter"); + + if (hfcgi == NULL) { + goto error; + } + + pthread_mutex_lock(hfcgi->mutex); + hfcgi->is_stopping = 1; + pthread_mutex_unlock(hfcgi->mutex); + + FCGX_ShutdownPending(); + + if (fcgi_stop_listen(&hfcgi->listen) != 0) { + LOG_ERROR("fcgi_stop_server. fcgi_stop_listen failed"); + rc = -1; + } + + fcgi_server_thread_pool_dispose(&hfcgi->thread_pool); + + hfcgi->is_stopping = 0; + + if (rc == -1) { + goto error; + } + + LOG_TRACE("fcgi_stop_server exit with success"); + return 0; + +error: + LOG_ERROR("fcgi_stop_server exit with error"); + return -1; +} + + +void +fcgi_dispose_server(HFcgi hfcgi) +{ + LOG_TRACE("fcgi_dispose_server enter"); + + free(hfcgi); + + LOG_TRACE("fcgi_dispose_server exit"); +} diff --git a/src/fcgisrv/fcgi_server.h b/src/fcgisrv/fcgi_server.h new file mode 100644 index 0000000..3a2abf0 --- /dev/null +++ b/src/fcgisrv/fcgi_server.h @@ -0,0 +1,73 @@ +#ifndef FCGI_SERVER_H_INCLUDED +#define FCGI_SERVER_H_INCLUDED + +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef struct fcgi_server_s* HFcgi; + + +typedef enum fcgi_handler_status_e { + HANDLER_ERROR = -1, + HANDLER_SUCCESS = 0, + HANDLER_HTTP_OK = 200, + HANDLER_REDIRECT_TO_LOGIN = 302, + HANDLER_HTTP_BAD_REQUEST = 400, + HANDLER_HTTP_UNAUTHORIZED = 401, + HANDLER_HTTP_NOT_FOUND = 404, + HANDLER_HTTP_METHOD_NOT_ALLOWED = 405, + HANDLER_HTTP_NOT_ACCEPTABLE = 406, + HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE = 413, + HANDLER_HTTP_INTERNAL_SERVER_ERROR = 500, +} fcgi_handler_status_t; + + +typedef fcgi_handler_status_t (*handler_func)(FCGX_Request* request, void* ctx); + + +typedef struct fcgi_handler_s { + handler_func execute; + void *ctx; + +} fcgi_handler_t; + + +typedef struct fcgi_tcp_s { + char *host; + int port; + +} fcgi_tcp_t; + + +typedef struct fcgi_conf_s { + fcgi_tcp_t listen; + size_t thread_pool_size; + +} fcgi_conf_t; + + +int fcgi_load_conf(const char* filename, fcgi_conf_t* conf); +void fcgi_clear_conf(fcgi_conf_t* conf); + +HFcgi fcgi_create_server(const fcgi_conf_t* conf); +void fcgi_dispose_server(HFcgi); + +int fcgi_register_handler(HFcgi hfcgi, char* path, fcgi_handler_t* handler); + +int fcgi_run_server(HFcgi); +int fcgi_stop_server(HFcgi); + + +#ifdef __cplusplus +} +#endif + + +#endif /* FCGI_SERVER_H_INCLUDED */ diff --git a/src/fcgisrv/fcgi_server_internal.h b/src/fcgisrv/fcgi_server_internal.h new file mode 100644 index 0000000..227f110 --- /dev/null +++ b/src/fcgisrv/fcgi_server_internal.h @@ -0,0 +1,70 @@ +#ifndef FCGI_SERVER_INTERNAL_H_INCLUDED +#define FCGI_SERVER_INTERNAL_H_INCLUDED + + +#include "fcgi_utils.h" + +#include + +#include + +#define FCGI_MAX_HANDLERS_NUM 10 + + +extern fcgi_handler_t fcgi_default_handler; + +typedef unsigned long fcgi_hash_t; + +typedef struct fcgi_handler_map_item_s { + fcgi_hash_t hash; + char* path; + fcgi_handler_t handler; + +} fcgi_handler_map_item_t; + +typedef struct fcgi_handler_map_s { + fcgi_handler_map_item_t locations[FCGI_MAX_HANDLERS_NUM]; + size_t num; + +} fcgi_handler_map_t; + +typedef struct fcgi_listen_s { + int fd; + pthread_mutex_t accept_mutex; + +} fcgi_listen_t; + +typedef struct fcgi_thread_s { + pthread_t descriptor; + int ret_code; + atomic_flag run; + void* ctx; + +} fcgi_thread_t; + +typedef struct fcgi_thread_pool_s { + size_t size; + fcgi_thread_t* threads; + +} fcgi_thread_pool_t; + +typedef struct fcgi_server_s { + fcgi_listen_t listen; + fcgi_handler_map_t handler_map; + fcgi_thread_pool_t thread_pool; + size_t thread_pool_size; + pthread_mutex_t *mutex; + int is_stopping; + +} fcgi_server_t; + + +int fcgi_worker_main(fcgi_thread_t* thread); + +int fcgi_server_thread_pool_create(fcgi_thread_pool_t* thread_pool, size_t size, HFcgi hfcgi); +void fcgi_server_thread_pool_dispose(fcgi_thread_pool_t* thread_pool); + +int fcgi_handler_map_set(fcgi_handler_map_t* map, char* path, fcgi_handler_t* handler); +fcgi_handler_t* fcgi_handler_map_get(fcgi_handler_map_t* map, char* path); + +#endif // FCGI_SERVER_INTERNAL_H_INCLUDED diff --git a/src/fcgisrv/fcgi_thread.c b/src/fcgisrv/fcgi_thread.c new file mode 100644 index 0000000..5e48f19 --- /dev/null +++ b/src/fcgisrv/fcgi_thread.c @@ -0,0 +1,178 @@ +#include "fcgi_server_internal.h" + +#include + +typedef int (*fcgi_thread_main_t)(fcgi_thread_t* thread); + + +typedef struct pthread_ctx_s { + fcgi_thread_main_t main; + fcgi_thread_t* main_ctx; + +} pthread_ctx_t; + + +static +void* pthread_main(void* _ctx) +{ + LOG_TRACE("pthread_main enter"); + + assert(_ctx != NULL && "pthread_main"); + + pthread_ctx_t* pthread_ctx_ptr = (pthread_ctx_t*)_ctx; + + int ret_code = pthread_ctx_ptr->main(pthread_ctx_ptr->main_ctx); + + free(pthread_ctx_ptr); + + LOG_TRACE("pthread_main exit with code %d", ret_code); + + pthread_exit((void*)(intptr_t)ret_code); + + return (void*)(intptr_t)ret_code; +} + + +static +int fcgi_thread_start(fcgi_thread_t* thread, fcgi_thread_main_t main, void* ctx) +{ + LOG_TRACE("fcgi_thread_start enter"); + + assert(thread != NULL && "fcgi_thread_start: fcgi_thread_t* thread == NULL"); + assert(main != NULL && "fcgi_thread_start: fcgi_thread_main_t main == NULL"); + /* ctx may be NULL */ + + pthread_ctx_t* pthread_ctx_ptr = (pthread_ctx_t*)malloc(sizeof(pthread_ctx_t)); + if (pthread_ctx_ptr == NULL) { + LOG_ERROR("Could not allocate memory for pthread_ctx_t"); + goto error; + } + + pthread_ctx_ptr->main = main; + pthread_ctx_ptr->main_ctx = thread; + atomic_flag_test_and_set(&thread->run); + thread->ctx = ctx; + + int rc = pthread_create(&thread->descriptor, NULL, pthread_main, (void*)pthread_ctx_ptr); + if (rc != 0) { + LOG_ERROR("pthread_create failed. Desc: %s", strerror(rc)); + goto error; + } + + LOG_TRACE("fcgi_thread_start exit successfully"); + return 0; + +error: + LOG_ERROR("fcgi_thread_start exit with error"); + return -1; +} + + +static +int fcgi_thread_stop(fcgi_thread_t* thread) +{ + LOG_TRACE("fcgi_thread_stop enter"); + + assert(thread != NULL && "fcgi_thread_stop: fcgi_thread_t* thread == NULL"); + + atomic_flag_clear(&thread->run); + + int rc = pthread_join(thread->descriptor, (void**)&thread->ret_code); + + if (rc != 0) { + LOG_ERROR("pthread_join failed. Desc: %s", strerror(rc)); + goto error; + } + + LOG_TRACE("fcgi_thread_stop exit successfully"); + return 0; + +error: + LOG_ERROR("fcgi_thread_stop exit with error"); + return -1; +} + + +static +void fcgi_threads_stop(fcgi_thread_t* threads, size_t size) +{ + assert(threads != NULL && "fcgi_threads_stop: fcgi_thread_t* threads array == NULL"); + assert(size != 0 && "fcgi_threads_stop: size_t size (of threads array) == 0"); + + size_t i = size; + + while (i--) { + atomic_flag_clear(&threads[i].run); + } + + i = size; + + while (i--) { + fcgi_thread_stop(&threads[i]); + } +} + + +static +int fcgi_threads_start(fcgi_thread_t* threads, size_t size, fcgi_thread_main_t main, void* ctx) +{ + assert(threads != NULL && "fcgi_threads_start: fcgi_thread_t* threads array == NULL"); + assert(size != 0 && "fcgi_threads_start: size_t size (of threads array) == 0"); + assert(main != NULL && "fcgi_threads_start: fcgi_thread_main_t main == NULL"); + /* ctx may be NULL */ + + size_t i; + + for (i = 0; i < size; i++) { + if (fcgi_thread_start(&threads[i], main, ctx) != 0) { + LOG_ERROR("fcgi_thread_start failed"); + goto error; + } + } + + return 0; + +error: + fcgi_threads_stop(threads, i); + return -1; +} + + +int fcgi_server_thread_pool_create(fcgi_thread_pool_t* thread_pool, size_t size, HFcgi hfcgi) +{ + assert(thread_pool != NULL && "fcgi_server_thread_pool_create: fcgi_thread_pool_t* thread_pool == NULL"); + assert(size != 0 && "fcgi_server_thread_pool_create: size_t size (of threads array) == 0"); + assert(hfcgi != NULL && "fcgi_server_thread_pool_create: HFcgi hfcgi == NULL"); + + thread_pool->threads = (fcgi_thread_t*)malloc(size * sizeof(fcgi_thread_t)); + if (thread_pool->threads == NULL) { + LOG_ERROR("Could not allocate memory of %zu for %zu thread objects", + size * sizeof(fcgi_thread_t), size); + goto error; + } + + if (fcgi_threads_start(thread_pool->threads, size, fcgi_worker_main, (void*)hfcgi) != 0) { + LOG_ERROR("fcgi_threads_start failed"); + goto error; + } + + thread_pool->size = size; + + return 0; + +error: + free(thread_pool->threads); + LOG_ERROR("fcgi_server_thread_pool_create exit with error"); + return -1; +} + + +void fcgi_server_thread_pool_dispose(fcgi_thread_pool_t* thread_pool) +{ + assert(thread_pool != NULL && "fcgi_server_thread_pool_dispose: fcgi_thread_pool_t* thread_pool == NULL"); + + if (thread_pool->threads == NULL) return; + + fcgi_threads_stop(thread_pool->threads, thread_pool->size); + free(thread_pool->threads); +} diff --git a/src/fcgisrv/fcgi_utils.c b/src/fcgisrv/fcgi_utils.c new file mode 100644 index 0000000..ae25c28 --- /dev/null +++ b/src/fcgisrv/fcgi_utils.c @@ -0,0 +1,79 @@ +#include "fcgi_server_internal.h" + +#include "utils/logger.h" + +#include + +int +fcgi_get_content_length(const FCGX_Request* request) +{ + int content_length = 0; + char* p = FCGX_GetParam(FCGI_PARAM_NAME_CONTENT_LENGTH, request->envp); + if (p != NULL) { + content_length = atoi(p); + } + + return content_length; +} + + +char* +fcgi_request_get_param(const FCGX_Request* request, const char* name, /*out*/ bool* err) +{ + assert(request != NULL); + assert(name != NULL); + assert(err != NULL); + + char* param_value = FCGX_GetParam(name, request->envp); + if (param_value == NULL) { + LOG_ERROR("Could not get fcgi parameter '%s'", name); + goto error; + } + + LOG_DEBUG("fcgi_request_get_param. name: %s, value: %s", name, param_value); + if (err) { + *err = false; + } + return param_value; + +error: + LOG_ERROR("fcgi_request_get_param exit with error"); + if (err) { + *err = true; + } + return NULL; +} + + +fcgi_handler_status_t +fcgi_printf_str(const FCGX_Request* request, const char* response, int response_len) +{ + assert(request != NULL); + assert(response != NULL); + + //int n = FCGX_FPrintF(request->out, response); + int n = FCGX_PutStr(response, response_len, request->out); + if (n < 0) { + LOG_ERROR("FCGX_PutStr failed"); + return HANDLER_ERROR; + } + if (n != response_len) { + LOG_ERROR("response_len (%d) differs from the number of characters written (%d)", + response_len, n); + return HANDLER_ERROR; + } + + return HANDLER_SUCCESS; +} + + +fcgi_handler_status_t +fcgi_printf_header(const FCGX_Request* request, const char* name, const char* value, int value_len) +{ + if (FCGX_FPrintF(request->out, "%s: %.*s"CRLF, name, value_len, value) == -1) { + LOG_ERROR("FCGX_FPrintF failed"); + return HANDLER_ERROR; + } + + return HANDLER_SUCCESS; +} diff --git a/src/fcgisrv/fcgi_utils.h b/src/fcgisrv/fcgi_utils.h new file mode 100644 index 0000000..2202237 --- /dev/null +++ b/src/fcgisrv/fcgi_utils.h @@ -0,0 +1,189 @@ +#ifndef FCGI_UTILS_H_INCLUDED +#define FCGI_UTILS_H_INCLUDED + + +#include "fcgi_server.h" +#include "utils/logger.h" + +#include "stdbool.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +/************************** fastCGI parameters: *******************************/ + + +#define FCGI_PARAM_NAME_REMOTE_ADDR "REMOTE_ADDR" +#define FCGI_PARAM_NAME_HTTP_COOKIE "HTTP_COOKIE" +#define FCGI_PARAM_NAME_REQUEST_URI "REQUEST_URI" +#define FCGI_PARAM_NAME_CONTENT_LENGTH "CONTENT_LENGTH" +#define FCGI_PARAM_NAME_CONTENT_TYPE "CONTENT_TYPE" +#define FCGI_PARAM_NAME_REQUEST_METHOD "REQUEST_METHOD" +#define FCGI_PARAM_NAME_SERVER_ADDR "SERVER_ADDR" +#define FCGI_PARAM_NAME_SERVER_PORT "SERVER_PORT" +#define FCGI_PARAM_NAME_SERVER_NAME "SERVER_NAME" +#define FCGI_PARAM_NAME_DOCUMENT_URI "DOCUMENT_URI" +#define FCGI_PARAM_NAME_QUERY_STRING "QUERY_STRING" +#define FCGI_PARAM_NAME_REQUEST_SCHEME "REQUEST_SCHEME" +#define FCGI_PARAM_NAME_HOST "HTTP_HOST" + +/******************************************************************************/ + + +static inline void +fcgi_request_print_envp(const FCGX_Request* request) +{ + if (request == NULL) return; + + char **envp = request->envp; + if (envp == NULL) return; + + int i = 0; + for (; envp[i] != NULL; i++) { + LOG_DEBUG("%d: %s", i, envp[i]); + } +} + + +/** + * @note just pointer is copied + */ +char* fcgi_request_get_param(const FCGX_Request* request, const char* name, bool* err); + + +int +fcgi_get_content_length(const FCGX_Request* request); + + +fcgi_handler_status_t +fcgi_printf_str(const FCGX_Request* request, const char* response, int response_len); + + +fcgi_handler_status_t +fcgi_printf_header(const FCGX_Request* request, const char* name, const char* value, int value_len); + + +/************************** fastCGI handlers: *********************************/ + + +#define CRLF "\r\n" + +#define FCGI_400_RESPONSE_FORMAT \ + "Status: 400 Bad Request" CRLF\ + "Content-type: text/plain" CRLF\ + CRLF\ + "400 Bad Request" CRLF + +#define FCGI_401_RESPONSE_FORMAT \ + "Status: 401 Unauthorized" CRLF\ + "Content-type: text/plain" CRLF\ + CRLF\ + "401 Unauthorized" CRLF + +#define FCGI_404_RESPONSE_FORMAT \ + "Status: 404 Not Found" CRLF\ + "Content-type: text/plain" CRLF\ + CRLF\ + "404 Not Found" CRLF + +#define FCGI_405_RESPONSE_FORMAT \ + "Status: 405 Method Not Allowed" CRLF\ + "Content-type: text/plain" CRLF\ + CRLF\ + "405 Method Not Allowed" CRLF + +#define FCGI_406_RESPONSE_FORMAT \ + "Status: 406 Not Acceptable" CRLF\ + "Content-type: text/plain" CRLF\ + CRLF\ + "406 Not Acceptable" CRLF + +#define FCGI_413_RESPONSE_FORMAT \ + "Status: 413 Request Entity Too Large " CRLF\ + "Content-type: text/plain" CRLF\ + CRLF\ + "413 Request Entity Too Large " CRLF + +#define FCGI_500_RESPONSE_FORMAT \ + "Status: 500 Internal Server Error" CRLF\ + "Content-type: text/plain" CRLF\ + CRLF\ + "500 Internal Server Error" CRLF + + + +#define FCGI_CHECK_PRINTF_STATUS(request, format, code) \ + (HANDLER_ERROR != fcgi_printf_str(request, format, sizeof(format) - 1)) ? \ + code : HANDLER_ERROR + + +typedef fcgi_handler_status_t (*fcgi_request_handler_pt)(const FCGX_Request* request, void* ctx); +typedef fcgi_request_handler_pt (*FCGI_REQUEST_FINALIZE_HANDLER)(int rc); + + +static inline fcgi_handler_status_t +fcgi_400_bad_request_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) +{ + LOG_INFO("response status: '400 Bad Request'"); + return FCGI_CHECK_PRINTF_STATUS(request, FCGI_400_RESPONSE_FORMAT, 400); +} + + +static inline fcgi_handler_status_t +fcgi_401_unauthorized_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) +{ + LOG_INFO("response status: '401 Unauthorized'"); + return FCGI_CHECK_PRINTF_STATUS(request, FCGI_401_RESPONSE_FORMAT, 401); +} + + +static inline fcgi_handler_status_t +fcgi_404_not_found_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) +{ + LOG_INFO("response status: '404 Not Found'"); + return FCGI_CHECK_PRINTF_STATUS(request, FCGI_404_RESPONSE_FORMAT, 404); +} + + +static inline fcgi_handler_status_t +fcgi_405_method_not_allowed_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) +{ + LOG_INFO("response status: '405 Method Not Allowed'"); + return FCGI_CHECK_PRINTF_STATUS(request, FCGI_405_RESPONSE_FORMAT, 405); +} + + +static inline fcgi_handler_status_t +fcgi_406_not_acceptable_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) +{ + LOG_INFO("response status: '406 Not Acceptable'"); + return FCGI_CHECK_PRINTF_STATUS(request, FCGI_406_RESPONSE_FORMAT, 406); +} + + +static inline fcgi_handler_status_t +fcgi_413_request_entity_too_large_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) +{ + LOG_INFO("response status: '413 Request Entity Too Large'"); + return FCGI_CHECK_PRINTF_STATUS(request, FCGI_413_RESPONSE_FORMAT, 413); +} + + +static inline fcgi_handler_status_t +fcgi_500_internal_server_error_handler(const FCGX_Request* request, void* ctx __attribute__((unused))) +{ + LOG_ERROR("response status: '500 Internal Server Error'"); + return FCGI_CHECK_PRINTF_STATUS(request, FCGI_500_RESPONSE_FORMAT, 500); +} + + +/******************************************************************************/ + + +#ifdef __cplusplus +} +#endif + +#endif /* FCGI_UTILS_H_INCLUDED */ diff --git a/src/fcgisrv/fcgi_worker.c b/src/fcgisrv/fcgi_worker.c new file mode 100644 index 0000000..1b5c84b --- /dev/null +++ b/src/fcgisrv/fcgi_worker.c @@ -0,0 +1,139 @@ +#include "fcgi_server_internal.h" + +#include + +#define FCGI_DEFAULT_RESPONSE_FORMAT \ + "Content-type: text/plain" CRLF\ + CRLF\ + "FCGI_SERVER" CRLF\ + "Hello from FCGI server!" CRLF\ + "Request number %d" CRLF + + +fcgi_handler_status_t fcgi_default_handler_func(FCGX_Request* request, void* ctx); + +fcgi_handler_t fcgi_default_handler = { fcgi_default_handler_func, NULL }; + + +fcgi_handler_status_t +fcgi_default_handler_func(FCGX_Request* request, void* ctx) +{ + assert(request != NULL && "fcgi_default_handler_func: FCGX_Request* request == NULL"); + /* ctx may be NULL */ + + static int counter = 0; + + LOG_DEBUG(FCGI_DEFAULT_RESPONSE_FORMAT, ++counter); + FCGX_FPrintF(request->out, FCGI_DEFAULT_RESPONSE_FORMAT, ++counter); + + /* print envp */ + size_t i; + for (i = 0; request->envp[i] != NULL; ++i) { + LOG_DEBUG("envp[%zu]: %s" CRLF, i, request->envp[i]); + FCGX_FPrintF(request->out, "envp[%d]: %s" CRLF, i, request->envp[i]); + } + + return HANDLER_SUCCESS; +} + + +static fcgi_handler_t* +fcgi_route_request(HFcgi hfcgi, FCGX_Request* request) +{ + assert(hfcgi != NULL); + assert(request != NULL); + + /* do request routing; map request-path to handler */ + /* evaluate hash of request path */ + char* path = FCGX_GetParam(FCGI_PARAM_NAME_DOCUMENT_URI, request->envp); + if (path == NULL) { + LOG_ERROR("Could not get DOCUMENT_URI of request"); + return NULL; + } + + return fcgi_handler_map_get(&hfcgi->handler_map, path); +} + + +static fcgi_handler_status_t +fcgi_handle_request(HFcgi hfcgi, FCGX_Request* request) +{ + LOG_TRACE("fcgi_handle_request"); + + assert(hfcgi != NULL && "fcgi_handle_request: HFcgi hfcgi == NULL"); + assert(request != NULL && "fcgi_handle_request: FCGX_Request* request == NULL"); + + fcgi_handler_status_t status; + fcgi_handler_t* handler = fcgi_route_request(hfcgi, request); + + if (handler == NULL) { + LOG_WARN("appropriate fcgi handler not found"); + LOG_WARN("Response status: '404 Not Found'"); + status = fcgi_404_not_found_handler(request, (void*)hfcgi); + goto done; + } + + /* finally - execute handler*/ + status = handler->execute(request, handler->ctx); + +done: + return status; +} + + +int +fcgi_worker_main(fcgi_thread_t* thread) +{ + LOG_TRACE("fcgi_worker_main enter"); + + assert(thread != NULL && "fcgi_worker_main: fcgi_thread_t* thread == NULL"); + + fcgi_handler_status_t status = HANDLER_SUCCESS; + HFcgi hfcgi = (HFcgi)thread->ctx; + + FCGX_Request request; + + LOG_DEBUG("fcgi_worker_main: hfcgi->listen.fd = %d", hfcgi->listen.fd); + + if (FCGX_InitRequest(&request, hfcgi->listen.fd, 0) != 0) { + LOG_ERROR("Could not init request object"); + goto error; + } + + /** init_handler_(); */ + + while (atomic_flag_test_and_set(&thread->run)) { + pthread_mutex_lock(&hfcgi->listen.accept_mutex); + int rc = FCGX_Accept_r(&request); + pthread_mutex_unlock(&hfcgi->listen.accept_mutex); + + if (rc != 0) { + pthread_mutex_lock(hfcgi->mutex); + if (!hfcgi->is_stopping) { + LOG_WARN("FCGX_Accept_r failed. Desc: %d", rc); + } + pthread_mutex_unlock(hfcgi->mutex); + //goto error; + continue; + } + + LOG_DEBUG("Request accepted:"); + fcgi_request_print_envp(&request); + + status = fcgi_handle_request(hfcgi, &request); + + FCGX_Finish_r(&request); + + if (status == HANDLER_ERROR) { + LOG_ERROR("fcgi_handle_request returned HANDLER_ERROR"); + //goto error; + } + } + + LOG_TRACE("fcgi_worker_main exit with success"); + return 0; + +error: + LOG_ERROR("fcgi_worker_main exit with error"); + return -1; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..282f5c9 --- /dev/null +++ b/src/main.c @@ -0,0 +1,101 @@ +#include "master.h" +#include "config.h" +#include "version.h" + +#include "utils/logger.h" + +#include + +static int get_app_options(int argc, char* argv[]); +static void show_usage(); +static void show_version(); + +static int +get_app_options(int argc, char* argv[]) +{ + int option_idx = 0; + + static char short_opts[] = "hv"; + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {NULL, 0, NULL, 0 } + }; + + while (1) { + int c = getopt_long(argc, argv, short_opts, long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'h': + show_usage(); + exit(EXIT_SUCCESS); + break; + case 'v': + show_version(); + exit(EXIT_SUCCESS); + break; + + default: + fprintf(stderr, "get_app_options. unknown option: %s\n", optarg); + goto print_and_exit_with_error; + } /* switch (c) */ + } /* while (true) */ + + if (optind < argc) { + fprintf(stderr, "non-option arguments: "); + while (optind < argc) { + fprintf(stderr, "%s ", argv[optind++]); + } + fprintf(stderr, "\n"); + goto print_and_exit_with_error; + } + + /* options successfully parsed */ + return 0; + +print_and_exit_with_error: + show_usage(); + exit(EXIT_FAILURE); + return -1; +} + + +static void +show_usage() +{ + printf("Usage: ervu-esia-module [-hv]\n" + "\n" + "Options:\n" + " -h, --help : Show this help, then exit\n" + " -v, --version : Show version, then exit\n"); +} + + +static void +show_version() +{ + printf("Version: " ESIA_MODULE_VERSION "\n"); +} + + +int +main(int argc, char* argv[]) +{ + int rc = -1; + service_manager_conf_t conf; + + get_app_options(argc, argv); + + if (service_manager_load_conf(CONF_NAME, &conf)) { + LOG_ERROR("service_manager_load_conf failed"); + goto end; + } + + rc = master_main(&conf); + +end: + service_manager_clear_conf(&conf); + return rc; +} diff --git a/src/main_conf.c b/src/main_conf.c new file mode 100644 index 0000000..6bfcd56 --- /dev/null +++ b/src/main_conf.c @@ -0,0 +1,75 @@ +#include "main_conf.h" + +#include +#include + +#define MAIN_CONF_SECTION "main" +#define MAIN_CONF_KEY_WORKER_PROCESSES "worker_processes" + +/* default configuration values: */ +static const int FCGI_CONF_DEFAULT_WORKER_PROCESSES = 10; + +static char* +copy_filename(const char *filename) +{ + char *str = strdup(filename); + if (NULL == str) { + LOG_ERROR("strdup failed"); + } + return str; +} + + +int +main_conf_load(main_conf_t* conf, const char *filename, const conf_file_context_t conf_file) +{ + LOG_TRACE("main_conf_load enter"); + + assert(filename != NULL); + assert(conf_file != NULL); + assert(conf != NULL); + + memset(conf, 0, sizeof(main_conf_t)); + + conf_file_field_t fields[] = { + { + MAIN_CONF_SECTION, + MAIN_CONF_KEY_WORKER_PROCESSES, + &(conf->worker_processes), + CONF_FILE_VALUE_INTEGER, + CONF_FILE_VALUE_POSITIVE_INT, + &FCGI_CONF_DEFAULT_WORKER_PROCESSES + }, + }; + + if (conf_file_load_values(conf_file, fields, sizeof(fields) / sizeof(conf_file_field_t))) { + goto error; + } + + conf->conf_file = copy_filename(filename); + if (NULL == conf->conf_file) { + LOG_ERROR("could not copy config filename, %s", filename); + goto error; + } + + LOG_INFO("conf_file = %s", conf->conf_file); + LOG_TRACE("main_conf_load exit with success"); + return 0; + +error: + main_conf_clear(conf); + LOG_ERROR("main_conf_load exit with error"); + return -1; +} + + +void +main_conf_clear(main_conf_t* conf) +{ + if (conf == NULL) { + return; + } + + free(conf->conf_file); + memset(conf, 0, sizeof(main_conf_t)); +} diff --git a/src/main_conf.h b/src/main_conf.h new file mode 100644 index 0000000..2e32634 --- /dev/null +++ b/src/main_conf.h @@ -0,0 +1,15 @@ +#ifndef MAIN_CONF_H_INCLUDED +#define MAIN_CONF_H_INCLUDED + +#include "utils/conf_file_context.h" + +typedef struct main_conf_s { + int worker_processes; + char *conf_file; +} main_conf_t; + + +int main_conf_load(main_conf_t* config, const char *filename, const conf_file_context_t conf_file); +void main_conf_clear(main_conf_t* config); + +#endif // MAIN_CONF_H_INCLUDED diff --git a/src/master.c b/src/master.c new file mode 100644 index 0000000..9d65428 --- /dev/null +++ b/src/master.c @@ -0,0 +1,220 @@ +#include "master.h" +#include "worker.h" + +#include +#include +#include +#include + +typedef struct fcgi_worker_info_s { + pid_t pid; + int port; + +} fcgi_worker_info_t; + +static fcgi_worker_info_t *worker_info = NULL; + +static int spawn_worker(const service_manager_conf_t* conf); +static void kill_workers(service_manager_conf_t* conf, int sig); + +static inline int +worker_info_get_index_by_pid(int worker_processes, pid_t pid) +{ + int i = 0; + for (i = 0; i < worker_processes; ++i) { + if (pid == worker_info[i].pid) { + return i; + } + } + LOG_WARN("worker_info: Could not get index, %d", pid); + return -1; +} + + +static void +worker_info_init(service_manager_conf_t *conf) +{ + LOG_TRACE("worker_info_init enter"); + + assert(conf != NULL && "worker_info_init: conf is NULL"); + + const int listen_port = conf->fcgi_cf.listen.port; + + int i = 0; + for (i = 0; i < conf->main_cf.worker_processes; ++i) { + // создаём потомка + worker_info[i].port = listen_port + i; + conf->fcgi_cf.listen.port = worker_info[i].port; + worker_info[i].pid = spawn_worker(conf); + } + + LOG_TRACE("worker_info_init exit"); +} + + +static void +fork_worker(service_manager_conf_t* conf, pid_t pid) +{ + LOG_TRACE("fork_worker enter"); + + assert(conf != NULL && "fork_worker: conf is NULL"); + + int i = worker_info_get_index_by_pid(conf->main_cf.worker_processes, pid); + if (i != -1) { + conf->fcgi_cf.listen.port = worker_info[i].port; + worker_info[i].pid = spawn_worker(conf); + LOG_DEBUG("fork a new child pid %d", worker_info[i].pid); + } + + LOG_TRACE("fork_worker exit"); +} + + +int +master_main(service_manager_conf_t* conf) +{ + LOG_TRACE("master_main enter"); + + int status; + int need_start = 1; + sigset_t sigset; + siginfo_t siginfo; + + assert(conf != NULL); + + worker_info = calloc(conf->main_cf.worker_processes, sizeof(fcgi_worker_info_t)); + if (worker_info == NULL) { + LOG_ERROR("Could not allocate memory for worker_info"); + LOG_ERROR("master_main exit with error"); + return -1; + } + + // настраиваем сигналы которые будем обрабатывать + sigemptyset(&sigset); + + // сигнал остановки процесса пользователем + sigaddset(&sigset, SIGQUIT); + + // сигнал для остановки процесса пользователем с терминала + sigaddset(&sigset, SIGINT); + + // сигнал запроса завершения процесса + sigaddset(&sigset, SIGTERM); + + // сигнал посылаемый при изменении статуса дочернего процесс + sigaddset(&sigset, SIGCHLD); + + sigprocmask(SIG_BLOCK, &sigset, NULL); + + worker_info_init(conf); + + // бесконечный цикл работы + for (;;) { + // если необходимо создать потомка + if (need_start) { + pid_t pid; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + /* look for exiting child's pid */ + fork_worker(conf, pid); + } + } + + need_start = 1; + + // ожидаем поступление сигнала + sigwaitinfo(&sigset, &siginfo); + + LOG_DEBUG("[MASTER] signal received: %s", strsignal(siginfo.si_signo)); + + // если пришел сигнал от потомка + if (siginfo.si_signo == SIGCHLD) { + // получаем статус завершение + wait(&status); + + // преобразуем статус в нормальный вид + status = WEXITSTATUS(status); + + // если потомок завершил работу с кодом говорящем о том, что нет нужны дальше работать + if (status == CHILD_NEED_TERMINATE) { + // запишем в лог сообщени об этом + LOG_INFO("[MASTER] Child stopped"); + + // убьем потомков + kill_workers(conf, SIGTERM); + + status = 0; + + // прервем цикл + break; + + } else if (status == CHILD_NEED_WORK) { // если требуется перезапустить потомка + // запишем в лог данное событие + LOG_INFO("[MASTER] Child restart"); + fork_worker(conf, siginfo.si_pid); + } + } else { // если пришел какой-либо другой ожидаемый сигнал + // запишем в лог информацию о пришедшем сигнале + LOG_INFO("[MASTER] Signal %s", strsignal(siginfo.si_signo)); + + // убьем потомков + kill_workers(conf, SIGTERM); + + status = 0; + break; + } + } + + // запишем в лог, что мы остановились + LOG_INFO("[MASTER] Stopped"); + + free(worker_info); + + LOG_TRACE("master_main exit with status '%d'", status); + + return status; +} + +static pid_t +spawn_worker(const service_manager_conf_t* conf) +{ + assert(conf != NULL); + + pid_t pid = fork(); + + if (pid == -1) { // если произошла ошибка + // запишем в лог сообщение об этом + LOG_ERROR("[MASTER] fork() failed; (%s)", strerror(errno)); + + } else if (!pid) { // если мы потомок + // запустим функцию отвечающую за работу демона + int rc = worker_main(conf); + + // завершим процесс + exit(rc); + + } else { // если мы родитель + // данный код выполняется в родителе + LOG_DEBUG("[MASTER] fork() succeded; worker pid: %d", pid); + } + + return pid; +} + + +static void +kill_workers(service_manager_conf_t* conf, int sig) +{ + LOG_TRACE("kill_workers enter"); + + assert(conf != NULL); + + int i; + for (i = 0; i < conf->main_cf.worker_processes; i++) { + // убьем потомка + kill(worker_info[i].pid, sig); + LOG_TRACE("worker#%d (pid: %d) killed", i, worker_info[i].pid); + } + + LOG_TRACE("kill_workers exit"); +} diff --git a/src/master.h b/src/master.h new file mode 100644 index 0000000..b1e53a5 --- /dev/null +++ b/src/master.h @@ -0,0 +1,9 @@ +#ifndef MASTER_H_INCLUDED +#define MASTER_H_INCLUDED + +#include "service_manager.h" + + +int master_main(service_manager_conf_t* conf); + +#endif // MASTER_H_INCLUDED diff --git a/src/modules/service_sign.c b/src/modules/service_sign.c new file mode 100644 index 0000000..e368834 --- /dev/null +++ b/src/modules/service_sign.c @@ -0,0 +1,375 @@ +#include "service_sign.h" + +#include "fcgisrv/fcgi_utils.h" + +#include "utils/cryptopro.h" + +#define SIGN_CONF_SECTION "sign" +#define SIGN_CONF_KEY_LOCATION "location" +#define SIGN_CONF_KEY_CP_FILE "cp_file" +#define SIGN_CONF_KEY_SIGNER "signer_subject" +#define SIGN_CONF_KEY_PIN "pin" + +#define FCGI_OK_RESPONSE_FORMAT \ + "Content-type: text/plain" CRLF\ + CRLF\ + "%.*s" CRLF + +static const str_t SIGN_CONF_DEFAULT_LOCATION = str_t_const("/sign"); + +static const int CLIENT_MAX_BODY_SIZE = 4096; +static const str_t ACCEPTABLE_CONTENT_TYPE = str_t_const("text/plain"); + +typedef struct sign_service_s { + const sign_conf_t *conf; + + cryptopro_context_t cryptopro_ctx; + +} sign_service_t; + +typedef struct fcgi_sign_request_s { + char *content; + int content_length; + + str_t signed_content; + +} fcgi_sign_request_t; + +static void fcgi_sign_request_clear(fcgi_sign_request_t *req_info); + +static fcgi_request_handler_pt fcgi_request_finalize_handler(fcgi_handler_status_t status); + +static fcgi_handler_status_t check_content_length(int content_length); + +static fcgi_handler_status_t check_content_type(const FCGX_Request* request); + +static fcgi_handler_status_t fcgi_request_load_data(const FCGX_Request* request, + int content_length, char** content); + +static int sign_content(const sign_service_t *hsign, fcgi_sign_request_t *req_info); + +int +sign_conf_load(sign_conf_t *conf, const conf_file_context_t conf_file) +{ + LOG_TRACE("sign_conf_load enter"); + + memset(conf, 0, sizeof(sign_conf_t)); + + conf_file_field_t fields[] = { + { + SIGN_CONF_SECTION, + SIGN_CONF_KEY_LOCATION, + &conf->location, + CONF_FILE_VALUE_STRT, + CONF_FILE_VALUE_LOCATION, + &SIGN_CONF_DEFAULT_LOCATION + }, + { + SIGN_CONF_SECTION, + SIGN_CONF_KEY_CP_FILE, + &(conf->cp_file), + CONF_FILE_VALUE_STRING, + CONF_FILE_VALUE_NONE, + NULL + }, + { + SIGN_CONF_SECTION, + SIGN_CONF_KEY_SIGNER, + &(conf->signer), + CONF_FILE_VALUE_STRING, + CONF_FILE_VALUE_NONE, + NULL + }, + { + SIGN_CONF_SECTION, + SIGN_CONF_KEY_PIN, + &(conf->pin), + CONF_FILE_VALUE_STRT, + CONF_FILE_VALUE_PSWD, + NULL + } + }; + + if (conf_file_load_values(conf_file, fields, sizeof(fields) / sizeof(conf_file_field_t))) { + goto error; + } + + LOG_TRACE("sign_conf_load exit"); + return 0; + +error: + sign_conf_clear(conf); + LOG_ERROR("sign_conf_load exit with error"); + return -1; +} + +void +sign_conf_clear(sign_conf_t *conf) +{ + LOG_TRACE("sign_conf_clear"); + + if (conf == NULL) return; + + if (conf->pin.data != 0) { + explicit_bzero(conf->pin.data, conf->pin.len); + } + str_t_clear(&conf->pin); + + str_t_clear(&conf->location); + free(conf->cp_file); + free(conf->signer); + + memset(conf, 0, sizeof(sign_conf_t)); +} + +HSign +sign_service_create(const sign_conf_t *conf) +{ + LOG_TRACE("sign_service_create enter"); + + sign_service_t *hsign = (sign_service_t*) calloc(1, sizeof(sign_service_t)); + if (hsign == NULL) { + LOG_ERROR("Could not allocate memory for HSign"); + goto error; + } + + hsign->conf = conf; + + if (!cryptopro_init(conf->cp_file)) { + LOG_ERROR("Could not init Cryptographic Provider"); + goto error; + } + + cryptopro_context_set(&hsign->cryptopro_ctx, conf->signer, &conf->pin); + + LOG_TRACE("sign_service_create exit"); + return (HSign)hsign; + +error: + sign_service_free((HSign)hsign); + LOG_ERROR("sign_service_create exit with error"); + return NULL; +} + +void +sign_service_free(HSign hsign) +{ + sign_service_t *ctx = (sign_service_t *) hsign; + + if (ctx == NULL) return; + + free(ctx); +} + +fcgi_handler_status_t +fcgi_sign_handler(FCGX_Request* request, void* ctx) +{ + fcgi_handler_status_t status = HANDLER_ERROR; + const sign_service_t *hsign = (const sign_service_t *) ctx; + fcgi_sign_request_t req_info = {0}; + + LOG_TRACE("fcgi_sign_handler enter"); + + if (request == NULL) { + LOG_ERROR("fcgi_sign_handler exit with error. Desc: request is NULL"); + goto exit; + } + + req_info.content_length = fcgi_get_content_length(request); + + status = check_content_length(req_info.content_length); + if (status != HANDLER_SUCCESS) { + goto exit; + } + + status = check_content_type(request); + if (status != HANDLER_SUCCESS) { + goto exit; + } + + status = fcgi_request_load_data(request, req_info.content_length, &req_info.content); + if (status != HANDLER_SUCCESS) { + goto exit; + } + + if (sign_content(hsign, &req_info)) { + status = HANDLER_ERROR; + goto exit; + } + + // status = HANDLER_SUCCESS; + +exit: + status = fcgi_request_finalize_handler(status)(request, &req_info); + + fcgi_sign_request_clear(&req_info); + + LOG_TRACE("fcgi_sign_handler exit"); + return status; +} + +static void +fcgi_sign_request_clear(fcgi_sign_request_t *req_info) +{ + LOG_TRACE("fcgi_sign_request_clear"); + + free(req_info->content); + str_t_clear(&(req_info->signed_content)); +} + +static fcgi_handler_status_t +fcgi_ok_handler(const FCGX_Request* request, void *ctx) +{ + LOG_TRACE("fcgi_ok_handler"); + + const fcgi_sign_request_t *req_info = (fcgi_sign_request_t*) ctx; + + LOG_DEBUG("response status: " FCGI_OK_RESPONSE_FORMAT, + (int) req_info->signed_content.len, req_info->signed_content.data); + + if (FCGX_FPrintF(request->out, FCGI_OK_RESPONSE_FORMAT, + (int) req_info->signed_content.len, req_info->signed_content.data) < 0) { + LOG_ERROR("FCGX_FPrintF() failed"); + return HANDLER_ERROR; + } + + return HANDLER_SUCCESS; +} + +static fcgi_request_handler_pt +fcgi_request_finalize_handler(fcgi_handler_status_t status) +{ + fcgi_request_handler_pt handler; + + switch (status) { + case HANDLER_SUCCESS: + case HANDLER_HTTP_OK: + handler = fcgi_ok_handler; + break; + + case HANDLER_HTTP_BAD_REQUEST: + handler = fcgi_400_bad_request_handler; + break; + + case HANDLER_HTTP_NOT_ACCEPTABLE: + handler = fcgi_406_not_acceptable_handler; + break; + + case HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE: + handler = fcgi_413_request_entity_too_large_handler; + break; + + case HANDLER_ERROR: + default: + handler = fcgi_500_internal_server_error_handler; + break; + } + + return handler; +} + +static fcgi_handler_status_t +check_content_length(int content_length) +{ + LOG_TRACE("check_content_length"); + + if (content_length > 0 && content_length <= CLIENT_MAX_BODY_SIZE) { + LOG_DEBUG("Content-Length is ok"); + return HANDLER_SUCCESS; + } + + if (content_length == 0) { + LOG_DEBUG("Content-Length == 0, so there is nothing to sign"); + return HANDLER_HTTP_OK; + } + + if (content_length < 0) { + LOG_WARN("Content-Length < 0 (%d)", content_length); + return HANDLER_HTTP_BAD_REQUEST; + } + + if (content_length > CLIENT_MAX_BODY_SIZE) { + LOG_WARN("Content-Length (%d) is too large (> %d)", + content_length, + CLIENT_MAX_BODY_SIZE); + return HANDLER_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + return HANDLER_ERROR; +} + +static fcgi_handler_status_t +check_content_type(const FCGX_Request* request) +{ + LOG_TRACE("check_content_type"); + + const char* content_type = FCGX_GetParam(FCGI_PARAM_NAME_CONTENT_TYPE, request->envp); + if (content_type == NULL) { + LOG_ERROR("Could not get FCGI param: "FCGI_PARAM_NAME_CONTENT_TYPE); + return HANDLER_HTTP_BAD_REQUEST; + } + + if (strncmp(content_type, ACCEPTABLE_CONTENT_TYPE.data, ACCEPTABLE_CONTENT_TYPE.len) == 0) { + LOG_DEBUG("Content-Type is ok"); + return HANDLER_SUCCESS; + } + + LOG_WARN("Content-Type is not acceptable, '%s'", content_type); + + return HANDLER_HTTP_NOT_ACCEPTABLE; +} + +static fcgi_handler_status_t +fcgi_request_load_data(const FCGX_Request* request, int content_length, char** content) +{ + LOG_TRACE("fcgi_request_load_data enter"); + + char* data = malloc(content_length + 1); + if (data == NULL) { + LOG_ERROR("Could not allocate memory for request body (%d bytes)", content_length + 1); + return HANDLER_ERROR; + } + + int actual_content_length = FCGX_GetStr(data, content_length, request->in); + + if (actual_content_length != content_length) { + LOG_ERROR("fcgi_request_load_data: " + "actual_content_length(%d) != content_length(%d); content: '%.*s'", + actual_content_length, content_length, + actual_content_length, data); + free(data); + return HANDLER_HTTP_BAD_REQUEST; + } + + data[content_length] = '\0'; + *content = data; + + LOG_DEBUG("content_length: %d", content_length); + LOG_DEBUG("content: '%s'", *content); + + LOG_TRACE("fcgi_request_load_data exit"); + + return HANDLER_SUCCESS; +} + +static int +sign_content(const sign_service_t *hsign, fcgi_sign_request_t *req_info) +{ + LOG_TRACE("sign_content enter"); + + str_t content = { + .data = req_info->content, + .len = req_info->content_length + }; + + if (cryptopro_sign(&hsign->cryptopro_ctx, &content, &req_info->signed_content)) { + goto error; + } + + LOG_TRACE("sign_content exit"); + return 0; + +error: + LOG_ERROR("sign_content exit with error"); + return -1; +} \ No newline at end of file diff --git a/src/modules/service_sign.h b/src/modules/service_sign.h new file mode 100644 index 0000000..9f999d0 --- /dev/null +++ b/src/modules/service_sign.h @@ -0,0 +1,29 @@ +#ifndef SERVICE_SIGN_H_INCLUDED +#define SERVICE_SIGN_H_INCLUDED + +#include "fcgisrv/fcgi_server.h" + +#include "utils/conf_file_context.h" +#include "utils/str_t.h" + +typedef struct sign_service_t* HSign; + +typedef struct sign_conf_s { + str_t location; + + char *cp_file; /* файл криптопровайдера */ + + char *signer; /* субъект контейнера */ + str_t pin; /* pin-код от контейнера */ + +} sign_conf_t; + +int sign_conf_load(sign_conf_t *conf, const conf_file_context_t conf_file); +void sign_conf_clear(sign_conf_t *conf); + +HSign sign_service_create(const sign_conf_t *conf); +void sign_service_free(HSign hsign); + +fcgi_handler_status_t fcgi_sign_handler(FCGX_Request* request, void* ctx); + +#endif // SERVICE_SIGN_H_INCLUDED \ No newline at end of file diff --git a/src/service_manager.c b/src/service_manager.c new file mode 100644 index 0000000..5de04de --- /dev/null +++ b/src/service_manager.c @@ -0,0 +1,245 @@ +#include "service_manager.h" + +#include + + +static void +service_manager_handle_conf_free(service_handle_conf_t *handle_conf) +{ + if (handle_conf != NULL) { + str_t_clear(&handle_conf->location); + handle_conf->conf_free(handle_conf->conf); + free(handle_conf); + } +} + + +static void* +service_manager_handle_conf_load(const conf_file_context_t conf_file, service_handle_conf_t *handle_conf) +{ + assert(conf_file != NULL); + assert(handle_conf != NULL); + + void* cfg = handle_conf->conf_load(conf_file, &handle_conf->location); + if (cfg == NULL) { + LOG_ERROR("service_manager_handle_conf_load - conf_load failed"); + } + return cfg; +} + + +static int +service_manager_handle_conf_merge(main_conf_t *main_cf, service_handle_conf_t *handle_conf) +{ + assert(main_cf != NULL); + assert(main_cf != NULL); + + int rc = handle_conf->conf_merge(main_cf, handle_conf->conf); + if (rc) { + LOG_ERROR("service_manager_handle_conf_merge - conf_merge failed"); + } + return rc; +} + + +int +register_service(service_manager_t* services, + const service_manager_conf_t* services_cf, + handler_func handler, + void* ctx, + const str_t* location) +{ + LOG_TRACE("register_service enter"); + + char *path = NULL; + + if (location == NULL) { + LOG_ERROR("location is NULL"); + goto error; + } + if (str_t_is_null(*location)) { + LOG_ERROR("location is empty"); + goto error; + } + + LOG_DEBUG("location: %.*s", (int)location->len, location->data); + + fcgi_handler_t fcgi_handler = { .execute = handler, .ctx = ctx }; + + if (location->data[0] != '/') { + LOG_ERROR("location must start with '/'"); + goto error; + } + + path = str_t_to_string(location); + if (path == NULL) { + LOG_ERROR("Could not copy location: %.*s", (int)location->len, location->data); + goto error; + } + + if (fcgi_register_handler(services->hfcgi, path, &fcgi_handler) != 0) { + LOG_ERROR("Could not register handler by path: %s", path); + goto error; + } + + free(path); + + LOG_INFO("service registered, %.*s", (int) location->len, location->data); + return 0; + +error: + LOG_ERROR("register_service exit with error"); + free(path); + return -1; +} + + +service_handle_conf_t * +service_manager_handle_conf_create(const conf_file_context_t conf_file, + main_conf_t *main_cf, + SERVICE_CONF_INIT_PROC handle_conf_init) +{ + LOG_TRACE("service_manager_handle_conf_create enter"); + + service_handle_conf_t *hc = calloc(1, sizeof(service_handle_conf_t)); + if (hc == NULL) { + LOG_ERROR("calloc failed"); + goto error; + } + + handle_conf_init(hc); + hc->conf = service_manager_handle_conf_load(conf_file, hc); + if (hc->conf == NULL) { + LOG_ERROR("could not load config"); + goto error; + } + if (service_manager_handle_conf_merge(main_cf, hc)) { + LOG_ERROR("could not merge config"); + goto error; + } + LOG_TRACE("service_manager_handle_conf_create exit with success"); + return hc; + +error: + service_manager_handle_conf_free(hc); + LOG_ERROR("service_manager_handle_conf_create exit with error"); + return NULL; +} + + +int +service_manager_load_conf(const char* config_file_name, service_manager_conf_t* services_cf) +{ + conf_file_context_t conf_file = NULL; + + LOG_TRACE("service_manager_load_conf enter"); + + assert(config_file_name != NULL); + assert(services_cf != NULL); + + memset(services_cf, 0, sizeof(service_manager_conf_t)); + + conf_file = conf_file_open_file(config_file_name); + if (conf_file == NULL) { + goto error; + } + + if (main_conf_load(&services_cf->main_cf, config_file_name, conf_file)) { + LOG_ERROR("main_conf_load failed"); + goto error; + } + + if (fcgi_load_conf(config_file_name, &services_cf->fcgi_cf)) { + LOG_ERROR("FastCGI server configuraton loading failed"); + goto error; + } + + if (sign_conf_load(&services_cf->sign_cf, conf_file)) { + LOG_ERROR("Sign service configuraton loading failed"); + goto error; + } + + conf_file_close_file(conf_file); + + LOG_TRACE("service_manager_load_conf exit with success"); + + return 0; + +error: + service_manager_clear_conf(services_cf); + conf_file_close_file(conf_file); + LOG_ERROR("service_manager_load_conf exit with error"); + return -1; +} + + +void +service_manager_clear_conf(service_manager_conf_t* services_cf) +{ + assert(services_cf != NULL); + + sign_conf_clear(&services_cf->sign_cf); + fcgi_clear_conf(&services_cf->fcgi_cf); + main_conf_clear(&services_cf->main_cf); +} + + +int +deinit_services(service_manager_t* services) +{ + LOG_TRACE("deinit_services enter"); + + assert(services != NULL); + + fcgi_stop_server(services->hfcgi); + fcgi_dispose_server(services->hfcgi); + + sign_service_free(services->hsign); + + LOG_TRACE("deinit_services exit"); + + return 0; +} + + +int +init_services(service_manager_t* services, const service_manager_conf_t* services_cf) +{ + LOG_TRACE("init_services enter"); + + assert(services != NULL); + assert(services_cf != NULL); + + /* create fcgi service */ + services->hfcgi = fcgi_create_server(&services_cf->fcgi_cf); + if (services->hfcgi == NULL) { + LOG_ERROR("Could not create FastCGI server."); + goto error; + } + + services->hsign = sign_service_create(&services_cf->sign_cf); + if (services->hsign == NULL) { + goto error; + } + + if (register_service(services, services_cf, fcgi_sign_handler, services->hsign, + &services_cf->sign_cf.location)) { + LOG_ERROR("Could not register 'sign service'"); + goto error; + } + + /* run fastcgi server */ + if (fcgi_run_server(services->hfcgi) != 0) { + LOG_ERROR("Could not run FastCGI server"); + goto error; + } + + LOG_TRACE("init_services exit with success"); + + return 0; + +error: + deinit_services(services); + LOG_ERROR("init_services exit with error"); + return -1; +} diff --git a/src/service_manager.h b/src/service_manager.h new file mode 100644 index 0000000..b56c9a2 --- /dev/null +++ b/src/service_manager.h @@ -0,0 +1,76 @@ +#ifndef SERVICE_MANAGER_H_INCLUDED +#define SERVICE_MANAGER_H_INCLUDED + +#include "main_conf.h" + +#include "utils/str_t.h" + +#include "fcgisrv/fcgi_server.h" + +#include "modules/service_sign.h" + +struct service_s; + +struct service_handle_conf_s; + +typedef struct service_s service_t; + +typedef struct service_handle_conf_s service_handle_conf_t; + +typedef void* service_handle_t; + +typedef void(*SERVICE_CONF_FREE_PROC)(void *conf); +typedef void*(*SERVICE_CONF_LOAD_PROC)(const conf_file_context_t conf_file, str_t* location); +typedef int(*SERVICE_CONF_MERGE_PROC)(main_conf_t *main_conf, void *conf); +typedef int(*SERVICE_CONF_INIT_PROC)(service_handle_conf_t *conf); + +typedef void(*SERVICE_HANDLE_FREE_PROC)(service_handle_t handle); + + +struct service_handle_conf_s { + str_t location; + void* conf; + SERVICE_CONF_FREE_PROC conf_free; + SERVICE_CONF_LOAD_PROC conf_load; + SERVICE_CONF_MERGE_PROC conf_merge; +}; + +struct service_s { + service_handle_t handle; + handler_func handler; + SERVICE_HANDLE_FREE_PROC handle_free; +}; + + +typedef struct service_manager_conf_s { + fcgi_conf_t fcgi_cf; + main_conf_t main_cf; + sign_conf_t sign_cf; + +} service_manager_conf_t; + +typedef struct service_manager_s { + HFcgi hfcgi; + HSign hsign; + +} service_manager_t; + + +int +register_service(service_manager_t* services, + const service_manager_conf_t* services_cf, + handler_func handler, + void* ctx, + const str_t* location); + +service_handle_conf_t * +service_manager_handle_conf_create(const conf_file_context_t conf_file, + main_conf_t *main_cf, + SERVICE_CONF_INIT_PROC handle_conf_init); +int service_manager_load_conf(const char* config_file_name, service_manager_conf_t* services_cf); +void service_manager_clear_conf(service_manager_conf_t* services_cf); + +int init_services(service_manager_t* services, const service_manager_conf_t* services_cf); +int deinit_services(service_manager_t* services); + +#endif // SERVICE_MANAGER_H_INCLUDED diff --git a/src/utils/base64.c b/src/utils/base64.c new file mode 100644 index 0000000..cd4de4f --- /dev/null +++ b/src/utils/base64.c @@ -0,0 +1,82 @@ +#include "base64.h" + +#include +#include + +static void +init_encode_base64_url(unsigned char **table) +{ + static unsigned char basis64_url[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; + + assert(table != NULL); + + *table = basis64_url; +} + +void +encode_base64_url(str_t *dst, const str_t *src) +{ + size_t n; + uint8_t a; + uint8_t b; + uint8_t c; + uint8_t d; + const uint8_t *p; + + p = (const uint8_t *) src->data; + + n = src->len / 3; + + unsigned char *basis64_url; + init_encode_base64_url(&basis64_url); + + if (src->len == (n * 3 + 1)) { + if (src->data != NULL && dst->data != NULL) { + a = (p[n * 3] & 0xFC) >> 2; + b = (p[n * 3] & 0x03) << 4; + + dst->data[n * 4] = basis64_url[a]; + dst->data[n * 4 + 1] = basis64_url[b]; + dst->data[n * 4 + 2] = '\0'; + } + + dst->len = n * 4 + 2; + } else if (src->len == (n * 3 + 2)) { + if (src->data != NULL && dst->data != NULL) { + a = (p[n * 3] & 0xFC) >> 2; + b = ((p[n * 3] & 0x03) << 4) | ((p[n * 3 + 1] & 0xF0) >> 4); + c = (p[n * 3 + 1] & 0x0F) << 2; + + dst->data[n * 4] = basis64_url[a]; + dst->data[n * 4 + 1] = basis64_url[b]; + dst->data[n * 4 + 2] = basis64_url[c]; + dst->data[n * 4 + 3] = '\0'; + } + + dst->len = n * 4 + 3; + } else { + if (dst->data != NULL) { + dst->data[n * 4] = '\0'; + } + dst->len = n * 4; + } + + if (src->data != NULL && dst->data != NULL) { + while(n-- > 0) { + a = (p[n * 3] & 0xFC) >> 2; + b = ((p[n * 3] & 0x03) << 4) | ((p[n * 3 + 1] & 0xF0) >> 4); + c = ((p[n * 3 + 1] & 0x0F) << 2) | ((p[n * 3 + 2] & 0xC0) >> 6); + d = p[n * 3 + 2] & 0x3F; + + dst->data[n * 4] = basis64_url[a]; + dst->data[n * 4 + 1] = basis64_url[b]; + dst->data[n * 4 + 2] = basis64_url[c]; + dst->data[n * 4 + 3] = basis64_url[d]; + } + } +} \ No newline at end of file diff --git a/src/utils/base64.h b/src/utils/base64.h new file mode 100644 index 0000000..55b199d --- /dev/null +++ b/src/utils/base64.h @@ -0,0 +1,10 @@ +#ifndef BASE64_H +#define BASE64_H + +#include "str_t.h" + +#define base64_encoded_length(len) (((len + 2) / 3) * 4) + +void encode_base64_url(str_t *dst, const str_t *src); + +#endif // BASE64_H \ No newline at end of file diff --git a/src/utils/capi.c b/src/utils/capi.c new file mode 100644 index 0000000..7ecf64b --- /dev/null +++ b/src/utils/capi.c @@ -0,0 +1,37 @@ +#include "capi.h" + +#include "library.h" + +#include + +#define LIBRARY_RESOLVE(fn, lib, fn_name) fn = lib->resolve(lib, fn_name); if (fn == NULL) return false + + +bool +capi_function_list_init(library_t *lib, capi_function_list_t *fl) +{ + assert(lib != NULL); + assert(fl != NULL); + + LIBRARY_RESOLVE(fl->CertCloseStore, lib, "CertCloseStore"); + LIBRARY_RESOLVE(fl->CertFindCertificateInStore, lib, "CertFindCertificateInStore"); + LIBRARY_RESOLVE(fl->CertFreeCertificateContext, lib, "CertFreeCertificateContext"); + LIBRARY_RESOLVE(fl->CertGetCertificateContextProperty, lib, "CertGetCertificateContextProperty"); + LIBRARY_RESOLVE(fl->CertOpenStore, lib, "CertOpenStore"); + LIBRARY_RESOLVE(fl->CertSetCertificateContextProperty, lib, "CertSetCertificateContextProperty"); + LIBRARY_RESOLVE(fl->CryptAcquireCertificatePrivateKey, lib, "CryptAcquireCertificatePrivateKey"); + LIBRARY_RESOLVE(fl->CryptCreateHash, lib, "CryptCreateHash"); + LIBRARY_RESOLVE(fl->CryptDestroyHash, lib, "CryptDestroyHash"); + LIBRARY_RESOLVE(fl->CryptHashData, lib, "CryptHashData"); + LIBRARY_RESOLVE(fl->CryptReleaseContext, lib, "CryptReleaseContext"); + LIBRARY_RESOLVE(fl->CryptSignMessage, lib, "CryptSignMessage"); + LIBRARY_RESOLVE(fl->GetLastError, lib, "GetLastError"); + +#ifdef UNICODE + LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashW"); +#else + LIBRARY_RESOLVE(fl->CryptSignHash, lib, "CryptSignHashA"); +#endif // !UNICODE + + return true; +} diff --git a/src/utils/capi.h b/src/utils/capi.h new file mode 100644 index 0000000..9277014 --- /dev/null +++ b/src/utils/capi.h @@ -0,0 +1,154 @@ +#ifndef CAPI_H_INCLUDED +#define CAPI_H_INCLUDED + +#define SIZEOF_VOID_P (UINTPTR_MAX+0 > 0xffffffffUL ? 8 : 4) + +typedef struct library_s library_t; + +#include + +#include + +#include + +#define DECLARE_FN(api, type, name, args) typedef WINAPI type (*name ## _FN) args + +DECLARE_FN(WINBASEAPI, DWORD, GET_LAST_ERROR, (void)); +DECLARE_FN(WINADVAPI, BOOL, CRYPT_RELEASE_CONTEXT, (HCRYPTPROV hProv, DWORD dwFlags)); +DECLARE_FN(WINCRYPT32API, + BOOL, + CRYPT_SIGN_MESSAGE, + (IN PCRYPT_SIGN_MESSAGE_PARA pSignPara, + IN BOOL fDetachedSignature, + IN DWORD cToBeSigned, + IN const BYTE *rgpbToBeSigned[], + IN DWORD rgcbToBeSigned[], + OUT BYTE *pbSignedBlob, + IN OUT DWORD *pcbSignedBlob)); + +DECLARE_FN(WINCRYPT32API, + BOOL, + CERT_FREE_CERTIFICATE_CONTEXT, + (IN PCCERT_CONTEXT pCertContext)); + +DECLARE_FN(WINCRYPT32API, + BOOL, + CERT_GET_CERTIFICATE_CONTEXT_PROPERTY, + (IN PCCERT_CONTEXT pCertContext, + IN DWORD dwPropId, + OUT void *pvData, + IN OUT DWORD *pcbData)); + +DECLARE_FN(WINCRYPT32API, + BOOL, + CERT_SET_CERTIFICATE_CONTEXT_PROPERTY, + (IN PCCERT_CONTEXT pCertContext, + IN DWORD dwPropId, + IN DWORD dwFlags, + IN const void *pvData)); + +DECLARE_FN(WINCRYPT32API, + PCCERT_CONTEXT, + CERT_FIND_CERTIFICATE_IN_STORE, + (IN HCERTSTORE hCertStore, + IN DWORD dwCertEncodingType, + IN DWORD dwFindFlags, + IN DWORD dwFindType, + IN const void *pvFindPara, + IN PCCERT_CONTEXT pPrevCertContext)); + +DECLARE_FN(WINCRYPT32API, + HCERTSTORE, + CERT_OPEN_STORE, + (IN LPCSTR lpszStoreProvider, + IN DWORD dwEncodingType, + IN HCRYPTPROV hCryptProv, + IN DWORD dwFlags, + IN const void *pvPara)); + +DECLARE_FN(WINCRYPT32API, + BOOL, + CERT_CLOSE_STORE, + (IN HCERTSTORE hCertStore, + DWORD dwFlags)); + +DECLARE_FN(WINCRYPT32API, + BOOL, + CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY, + (IN PCCERT_CONTEXT pCert, + IN DWORD dwFlags, + IN void *pvReserved, + OUT HCRYPTPROV *phCryptProv, + OUT OPTIONAL DWORD *pdwKeySpec, + OUT OPTIONAL BOOL *pfCallerFreeProv)); + +DECLARE_FN(WINADVAPI, + BOOL, + CRYPT_CREATE_HASH, + (HCRYPTPROV hProv, + ALG_ID Algid, + HCRYPTKEY hKey, + DWORD dwFlags, + HCRYPTHASH *phHash)); + +DECLARE_FN(WINADVAPI, + BOOL, + CRYPT_HASH_DATA, + (HCRYPTHASH hHash, + CONST BYTE *pbData, + DWORD dwDataLen, + DWORD dwFlags)); + +DECLARE_FN (WINADVAPI, + BOOL, + CRYPT_DESTROY_HASH, + (HCRYPTHASH hHash)); + +DECLARE_FN(WINADVAPI, + BOOL, + CRYPT_SIGN_HASH_A, + (HCRYPTHASH hHash, + DWORD dwKeySpec, + LPCSTR szDescription, + DWORD dwFlags, + BYTE *pbSignature, + DWORD *pdwSigLen)); + +DECLARE_FN(WINADVAPI, + BOOL, + CRYPT_SIGN_HASH_W, + (HCRYPTHASH hHash, + DWORD dwKeySpec, + LPCWSTR szDescription, + DWORD dwFlags, + BYTE *pbSignature, + DWORD *pdwSigLen)); + +#ifdef UNICODE +#define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_W_FN +#else +#define CRYPT_SIGN_HASH_FN CRYPT_SIGN_HASH_A_FN +#endif // !UNICODE + + +typedef struct { + CRYPT_SIGN_MESSAGE_FN CryptSignMessage; + CERT_FREE_CERTIFICATE_CONTEXT_FN CertFreeCertificateContext; + CERT_OPEN_STORE_FN CertOpenStore; + CERT_CLOSE_STORE_FN CertCloseStore; + GET_LAST_ERROR_FN GetLastError; + CRYPT_ACQUIRE_CERTIFICATE_PRIVATE_KEY_FN CryptAcquireCertificatePrivateKey; + CRYPT_CREATE_HASH_FN CryptCreateHash; + CRYPT_HASH_DATA_FN CryptHashData; + CRYPT_SIGN_HASH_FN CryptSignHash; + CRYPT_DESTROY_HASH_FN CryptDestroyHash; + CRYPT_RELEASE_CONTEXT_FN CryptReleaseContext; + CERT_FIND_CERTIFICATE_IN_STORE_FN CertFindCertificateInStore; + CERT_GET_CERTIFICATE_CONTEXT_PROPERTY_FN CertGetCertificateContextProperty; + CERT_SET_CERTIFICATE_CONTEXT_PROPERTY_FN CertSetCertificateContextProperty; +} capi_function_list_t; + + +bool capi_function_list_init(library_t *lib, capi_function_list_t *fl); + +#endif // CAPI_H_INCLUDED diff --git a/src/utils/conf_file_context.c b/src/utils/conf_file_context.c new file mode 100644 index 0000000..92b0d02 --- /dev/null +++ b/src/utils/conf_file_context.c @@ -0,0 +1,700 @@ +#include "conf_file_context.h" +#include "gconf_file.h" +#include "types.h" + +#include + +#define KEY_FILE_DEFAULT_LIST_SEPARATOR ' ' + + +typedef GKeyFile conf_file_context_s; + +#define is_allowed_tcp_port(port_num) (port_num >= 0 && port_num < 65536) + +/** Checks if character @c is allowed in location. + * @return 1 - true, 0 - false + */ +static int +allowed_location_char(char c) +{ + if (c >= '0' && c <= '9') return 1; /* true */ + + unsigned char ch = (unsigned char) (c | 0x20); /* lower */ + if (ch >= 'a' && ch <= 'z') return 1; /* true */ + + switch (c) { + /* pct-encoding */ + case '%': + + /* unreserved */ + case '-': case '.': case '_': case '~': + + /* sub-delims */ + case '!': case '$': case '&': case '\'': + case '(': case ')': case '*': case '+': + case ',': case ';': case '=': + + /* gen-delims (allowed) */ + case '/': case ':': case '@': + + return 1; /* true */ + + /* gen-delims (not allowed) */ + case '?': case '#': case '[': case ']': + default: + + return 0; /* false */ + } +} + +/** Checks if character @c is allowed in uri. + * @return 1 - true, 0 - false + */ +static int +allowed_uri_char(char c) +{ + if (c >= '0' && c <= '9') return 1; /* true */ + + unsigned char ch = (unsigned char) (c | 0x20); /* lower */ + if (ch >= 'a' && ch <= 'z') return 1; /* true */ + + switch (c) { + /* pct-encoding */ + case '%': + + /* unreserved */ + case '-': case '.': case '_': case '~': + + /* sub-delims */ + case '!': case '$': case '&': case '\'': + case '(': case ')': case '*': case '+': + case ',': case ';': case '=': + + /* gen-delims */ + case '/': case ':': case '@': + case '?': case '#': case '[': case ']': + + return 1; /* true */ + + default: + + return 0; /* false */ + } +} + +/** Checks if character @c is allowed in host name. + * @return 1 - true, 0 - false + */ +static int +allowed_hostname_char(char c) +{ + if (c >= '0' && c <= '9') return 1; /* true */ + + unsigned char ch = (unsigned char) (c | 0x20); /* lower */ + if (ch >= 'a' && ch <= 'z') return 1; /* true */ + + switch (c) { + /* pct-encoding */ + case '%': + + /* unreserved */ + case '-': case '.': case '_': case '~': + + /* sub-delims */ + case '!': case '$': case '&': case '\'': + case '(': case ')': case '*': case '+': + case ',': case ';': case '=': + + /* gen-delims (allowed) */ + case ':': case '[': case ']': + + return 1; /* true */ + + /* gen-delims (not allowed) */ + case '/': case '@': case '?': case '#': + default: + + return 0; /* false */ + } +} + +/** Checks the syntax of str_t @location: it is allowed to consist of only + * rfc-3986 defined characters + * @return 1 - true, 0 - false + */ +static int +allowed_location_strt(const str_t* location) +{ + assert(location != NULL && !str_t_is_null(*location)); + + size_t i; + for (i = 0; i < location->len; i++) { + if (!allowed_location_char(location->data[i])) { + LOG_ERROR("location '%.*s' is not valid URI-path: '%c' character is not allowed", + (int)location->len, location->data, location->data[i]); + return 0; /* false */ + } + } + + return 1; /* true */ +} + +/** Checks the syntax of NULL-terminated string @location: it is allowed + * to consist of only rfc-3986 defined characters. + * @return 1 - true, 0 - false + */ +static int +allowed_location_string(const char* location) +{ + assert(location != NULL); + + const char* loc; + for (loc = location; *loc != '\0'; loc++) { + if (!allowed_location_char(*loc)) { + LOG_ERROR("location '%s' is not valid URI-path: '%c' character is not allowed", + location, *loc); + return 0; /* false */ + } + } + + return 1; /* true */ +} + +/** Checks the syntax of str_t @uri: it is allowed to consist of only rfc-3986 + * defined characters + * @return 1 - true, 0 - false + */ +static int +allowed_uri_strt(const str_t* uri) +{ + assert(uri != NULL && !str_t_is_null(*uri)); + + size_t i; + for (i = 0; i < uri->len; i++) { + if (!allowed_uri_char(uri->data[i])) { + LOG_ERROR("uri '%.*s' is not valid URI: '%c' character is not allowed", + (int)uri->len, uri->data, uri->data[i]); + return 0; /* false */ + } + } + + return 1; /* true */ +} + +/** Checks the syntax of NULL-terminated string @uri: it is allowed + * to consist of only rfc-3986 defined characters. + * @return 1 - true, 0 - false + */ +static int +allowed_uri_string(const char* uri) +{ + assert(uri != NULL); + + const char* u; + for (u = uri; *u != '\0'; u++) { + if (!allowed_uri_char(*u)) { + LOG_ERROR("uri '%s' is not valid URI-path: '%c' character is not allowed", + uri, *u); + return 0; /* false */ + } + } + + return 1; /* true */ +} + +/** Checks the syntax of NULL-terminated string @host: it is allowed + * to consist of only rfc-3986 defined characters. And limit hostname length is + * HOST_NAME_MAX + * @return 1 - true, 0 - false + */ +static int +allowed_hostname(const char* host) +{ + assert(host != NULL); + + size_t len = 0; + const char* h = host; + + for (; *h != '\0' && len <= HOST_NAME_MAX ; h++, len++) { + if (!allowed_hostname_char(*h)) { + LOG_ERROR("Hostname '%s' is not valid: '%c' character is not allowed", + host, *h); + return 0; + } + } + + if (len == 0 || len > HOST_NAME_MAX) { + LOG_ERROR("Hostname length (%zu) is not allowed: == 0 or > HOST_NAME_MAX(%d)", + len, HOST_NAME_MAX); + return 0; /*false*/ + } + + return 1; /*true*/ +} + + +static inline void +conf_file_set_boolean_value(const conf_file_field_t *field, int value) +{ + *((int*)field->value) = value; + LOG_DEBUG("%s::%s = %s", field->section, field->key, value == TRUE ? "true" : "false"); +} + + +static inline void +conf_file_set_integer_value(const conf_file_field_t *field, int value) +{ + *((int*)field->value) = value; + LOG_DEBUG("%s::%s = %d", field->section, field->key, value); +} + + +static inline void +conf_file_set_integer_list_value(const conf_file_field_t *field, const int_list_t *value) +{ + *((int_list_t*)field->value) = *value; + + LOG_DEBUG("%s::%s = {", field->section, field->key);; + int_list_print(value); + LOG_DEBUG("}"); +} + + +static inline void +conf_file_set_string_value(const conf_file_field_t *field, char *value) +{ + *(char**)field->value = value; + + if (field->check != CONF_FILE_VALUE_PSWD) { + if (value != NULL) { + LOG_DEBUG("%s::%s = %s", field->section, field->key, value); + } else { + LOG_DEBUG("%s::%s = NULL", field->section, field->key); + } + } else { + LOG_DEBUG("%s::%s = ******", field->section, field->key); + } +} + + +static inline void +conf_file_set_string_list_value(const conf_file_field_t *field, const string_list_t *value) +{ + *((string_list_t*)field->value) = *value; + + LOG_DEBUG("%s::%s = {", field->section, field->key); + string_list_print(value); + LOG_DEBUG("}"); +} + + +static inline void +conf_file_set_strt_value(const conf_file_field_t *field, const str_t *value) +{ + *(str_t*)field->value = *value; + + if (field->check != CONF_FILE_VALUE_PSWD) { + LOG_DEBUG("%s::%s = %.*s", field->section, field->key, (int)value->len, value->data); + } else { + LOG_DEBUG("%s::%s = ******", field->section, field->key); + } +} + + +static int +conf_file_set_default_value(const conf_file_field_t *field) +{ + assert(field != NULL); + + switch (field->type) { + case CONF_FILE_VALUE_BOOLEAN: + + conf_file_set_boolean_value(field, *((int*)field->default_value)); + + break; + + case CONF_FILE_VALUE_INTEGER: + + conf_file_set_integer_value(field, *((int*)field->default_value)); + + break; + + case CONF_FILE_VALUE_INTEGER_LIST: + { + int_list_t value = {0}; + if (int_list_copy(&value, field->default_value)) { + goto error; + } + + conf_file_set_integer_list_value(field, &value); + } + + break; + + case CONF_FILE_VALUE_STRING: + { + char *value = NULL; + char *default_value = *((char**)field->default_value); + if (default_value != NULL) { + value = strdup(default_value); + if (value == NULL) { + LOG_ERROR("Could not copy default value for '%s::%s'", + field->section, field->key); + goto error; + } + } + conf_file_set_string_value(field, value); + } + + break; + + case CONF_FILE_VALUE_STRING_LIST: + { + string_list_t value = {0}; + if (string_list_copy(&value, field->default_value)) { + goto error; + } + + conf_file_set_string_list_value(field, &value); + } + + break; + + case CONF_FILE_VALUE_STRT: + { + str_t value = {0}; + if (str_t_copy(&value, field->default_value)) { + goto error; + } + + conf_file_set_strt_value(field, &value); + } + + break; + + default: + goto error; + } + + return 0; + +error: + LOG_ERROR("conf_file_set_default_value exit with error"); + return -1; +} + + +static int +conf_file_load_value(GKeyFile* ini_file, const conf_file_field_t *field, GError** gerror) +{ + assert(ini_file != NULL); + assert(field != NULL && field->value != NULL); + assert(field->key != NULL && field->section != NULL); + assert(gerror != NULL && *gerror == NULL); + + switch (field->type) { + case CONF_FILE_VALUE_BOOLEAN: + { + gboolean value = g_key_file_get_boolean(ini_file, field->section, field->key, gerror); + conf_file_set_boolean_value(field, (int)value); + } + break; + + case CONF_FILE_VALUE_INTEGER: + { + gint value = g_key_file_get_integer(ini_file, field->section, field->key, gerror); + conf_file_set_integer_value(field, (int)value); + } + break; + + case CONF_FILE_VALUE_INTEGER_LIST: + { + int_list_t value = {0}; + value.list = g_key_file_get_integer_list(ini_file, field->section, field->key, &(value.size), gerror); + if (value.list == NULL) break; + + conf_file_set_integer_list_value(field, &value); + } + break; + + case CONF_FILE_VALUE_STRING: + { + char *value = gconf_file_get_string(ini_file, field->section, field->key, gerror); + if (value == NULL) break; + + conf_file_set_string_value(field, value); + } + break; + + case CONF_FILE_VALUE_STRING_LIST: + { + string_list_t value = {0}; + value.list = gconf_file_get_string_list(ini_file, field->section, field->key, &(value.size), gerror); + if (value.list == NULL) break; + + conf_file_set_string_list_value(field, &value); + } + break; + + case CONF_FILE_VALUE_STRT: + { + str_t value = {0}; + if (gconf_file_get_strt(ini_file, field->section, field->key, gerror, &value)) { + break; + } + if (str_t_is_null(value)) break; + + conf_file_set_strt_value(field, &value); + } + break; + + default: + goto error; + } + + if (*gerror != NULL) { + if (field->default_value != NULL && (*gerror)->code != G_KEY_FILE_ERROR_INVALID_VALUE) { + LOG_DEBUG("Conf field '%s::%s' is optional. Skip error (%s)", + field->section, field->key, (*gerror)->message); + g_error_free(*gerror); + *gerror = NULL; + + LOG_DEBUG("Set default value for field '%s::%s'", field->section, field->key); + if (conf_file_set_default_value(field)) { + goto error; + } + } else { + goto error; + } + } + + return 0; + +error: + LOG_ERROR("conf_file_load_value '%s':'%s' exit with error", field->section, field->key); + return -1; +} + + +static int +conf_file_check_value(const conf_file_field_t *field) +{ + assert(field != NULL); + + switch (field->check) { + case CONF_FILE_VALUE_NONE: + case CONF_FILE_VALUE_PSWD: + /* do nothing */ + break; + + case CONF_FILE_VALUE_HOSTNAME: + if (field->type == CONF_FILE_VALUE_STRING) { + char* host = *(char**)field->value; + if (!allowed_hostname(host)) { + LOG_ERROR("%s:%s '%s' is not allowed hostname", + field->section, field->key, host); + goto error; + } + } + break; + + case CONF_FILE_VALUE_LOCATION: + if (field->type == CONF_FILE_VALUE_STRING) { + char *location = *(char**)field->value; + if (!allowed_location_string(location)) { + LOG_ERROR("%s:%s '%s' is not allowed location", + field->section, field->key, location); + goto error; + } + + } else if (field->type == CONF_FILE_VALUE_STRT) { + str_t *location = (str_t*)field->value; + if (!allowed_location_strt(location)) { + LOG_ERROR("%s:%s '%.*s' is not allowed location", field->section, + field->key, (int)location->len, location->data); + goto error; + } + } + break; + + case CONF_FILE_VALUE_TCP_PORT: + if (field->type == CONF_FILE_VALUE_INTEGER) { + int port = *(int*)field->value; + if (!is_allowed_tcp_port(port)) { + LOG_ERROR("%s:%s '%d' not allowed tcp port", + field->section, field->key, port); + goto error; + } + } + break; + + case CONF_FILE_VALUE_POSITIVE_INT: + if (field->type == CONF_FILE_VALUE_INTEGER) { + int value = *(int*)field->value; + if (value < 0) { + LOG_ERROR("%s:%s is not valid: %d (expected value >= 0)", + field->section, field->key, value); + goto error; + } + } + break; + + case CONF_FILE_VALUE_URI: + if (field->type == CONF_FILE_VALUE_STRING) { + char *uri = *(char**)field->value; + if (uri != NULL) { + if (!allowed_uri_string(uri)) { + LOG_ERROR("%s:%s '%s' is not allowed uri", + field->section, field->key, uri); + goto error; + } + } else if (field->default_value == NULL) { + LOG_ERROR("%s:%s 'NULL' is not allowed uri", + field->section, field->key); + goto error; + } + } else if (field->type == CONF_FILE_VALUE_STRT) { + str_t *uri = (str_t*)field->value; + if (!str_t_is_null(*uri)) { + if (!allowed_uri_strt(uri)) { + LOG_ERROR("%s:%s '%.*s' is not allowed uri", field->section, + field->key, (int)uri->len, uri->data); + goto error; + } + } else if (field->default_value == NULL) { + LOG_ERROR("%s:%s 'NULL' is not allowed uri", + field->section, field->key); + goto error; + } + } + break; + + default: + break; + } + + return 0; + +error: + return -1; +} + + +int +conf_file_load_from_file(const char *filename, conf_file_field_t* fields, int fields_count) +{ + LOG_TRACE("conf_file_load_from_file enter. Config filename: %s", filename); + + conf_file_context_t ctx = conf_file_open_file(filename); + if (ctx == NULL) { + goto error; + } + + if (conf_file_load_values(ctx, fields, fields_count)) { + goto error; + } + + conf_file_close_file(ctx); + + LOG_TRACE("conf_file_load_from_file exit"); + return 0; + +error: + LOG_ERROR("Load configuration from file '%s' FAILED", filename); + + conf_file_close_file(ctx); + + LOG_ERROR("conf_file_load_from_file exit with error"); + return -1; +} + + +conf_file_context_t +conf_file_open_file(const char *filename) +{ + GKeyFile* ini_file = NULL; + GError* gerror = NULL; + + LOG_TRACE("conf_file_open_file enter. Config filename: %s", filename); + + assert(filename != NULL); + + ini_file = g_key_file_new(); + if (ini_file == NULL) { + LOG_ERROR("conf_file_open_file failed. Desc: could not create key file instance"); + goto error; + } + + g_key_file_set_list_separator(ini_file, KEY_FILE_DEFAULT_LIST_SEPARATOR); + + if (!g_key_file_load_from_file(ini_file, filename, G_KEY_FILE_KEEP_COMMENTS, + &gerror)) { + goto error; + } + + return (conf_file_context_t)ini_file; + +error: + LOG_ERROR("Open configuration file '%s' FAILED", filename); + + if (gerror != NULL) { + LOG_ERROR("Desc: %s", gerror->message); + g_error_free(gerror); + } + + if (ini_file != NULL) { + g_key_file_free(ini_file); + } + + LOG_ERROR("conf_file_open_file exit with error"); + return NULL; +} + + +void +conf_file_close_file(conf_file_context_t ctx) +{ + if (ctx) { + GKeyFile* ini_file = (GKeyFile*) ctx; + g_key_file_free(ini_file); + } +} + + +int +conf_file_load_values(const conf_file_context_t ctx, const conf_file_field_t* fields, int fields_count) +{ + GKeyFile* ini_file = (GKeyFile*) ctx; + GError* gerror = NULL; + int i = 0; + + for (; i < fields_count; i++) { + if (conf_file_load_value(ini_file, &fields[i], &gerror)) { + goto error; + } + + if (conf_file_check_value(&fields[i])) { + goto error; + } + } + + return 0; + +error: + LOG_ERROR("Load configuration FAILED"); + if (gerror != NULL) { + LOG_ERROR("Desc: %s", gerror->message); + g_error_free(gerror); + } + LOG_ERROR("conf_file_load_values exit with error"); + return -1; +} + + +int +conf_file_has_section(const conf_file_context_t ctx, const char *section) +{ + GKeyFile* ini_file = (GKeyFile*) ctx; + + return g_key_file_has_group(ini_file, section) ? 0 : 1; +} diff --git a/src/utils/conf_file_context.h b/src/utils/conf_file_context.h new file mode 100644 index 0000000..46b014a --- /dev/null +++ b/src/utils/conf_file_context.h @@ -0,0 +1,72 @@ +#ifndef CONF_FILE_CONTEXT_H_INCLUDED +#define CONF_FILE_CONTEXT_H_INCLUDED + +#include "logger.h" + +typedef struct conf_file_context_s* conf_file_context_t; + + +typedef enum conf_file_value_type_e { + CONF_FILE_VALUE_BOOLEAN, + CONF_FILE_VALUE_INTEGER, + CONF_FILE_VALUE_INTEGER_LIST, + CONF_FILE_VALUE_STRING, + CONF_FILE_VALUE_STRING_LIST, + CONF_FILE_VALUE_STRT, + +} conf_file_value_type_t; + + +typedef enum conf_file_value_check_e { + CONF_FILE_VALUE_NONE, + CONF_FILE_VALUE_HOSTNAME, + CONF_FILE_VALUE_LOCATION, + CONF_FILE_VALUE_POSITIVE_INT, + CONF_FILE_VALUE_TCP_PORT, + CONF_FILE_VALUE_URI, + CONF_FILE_VALUE_PSWD + +} conf_file_value_check_t; + + +typedef struct conf_field_s { + const char *section; + const char *key; + void *value; + conf_file_value_type_t type; + conf_file_value_check_t check; + const void *default_value; + +} conf_file_field_t; + + +int conf_file_load_from_file(const char *filename, conf_file_field_t* fields, int fields_count); + + +conf_file_context_t conf_file_open_file(const char *filename); +void conf_file_close_file(conf_file_context_t ctx); + +int conf_file_load_values(const conf_file_context_t ctx, const conf_file_field_t* fields, int fields_count); + + +/* Return value: + * 0 - on success if conf file has the section + * 1 - if conf file does not have the section + */ +int conf_file_has_section(const conf_file_context_t ctx, const char *section); + + +static inline const char* +conf_file_select_existing_section(const conf_file_context_t ctx, const char *section, const char *default_section) +{ + switch (conf_file_has_section(ctx, section)) { + case 0: + return section; + case 1: + LOG_DEBUG("section '%s' does not exist. Use default section: '%s'", section, default_section); + return default_section; + } + return NULL; +} + +#endif // CONF_FILE_CONTEXT_H_INCLUDED diff --git a/src/utils/cryptopro.c b/src/utils/cryptopro.c new file mode 100644 index 0000000..a3b54d3 --- /dev/null +++ b/src/utils/cryptopro.c @@ -0,0 +1,295 @@ +#include "cryptopro.h" + +#include "base64.h" +#include "capi.h" +#include "library.h" +#include "logger.h" + +static capi_function_list_t cp_function_list; +static library_t libcapi; + +static int sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign); + +static int reverse_sign(const str_t *sign, /*out*/ str_t *sign_reversed); + +static HCERTSTORE cert_open_store(); + +static PCCERT_CONTEXT get_signer_cert(const cryptopro_context_t *ctx, HCERTSTORE hStoreHandle); + +static int set_pin(PCCERT_CONTEXT pSignerCert, const str_t *pin); + +bool +cryptopro_init(const char* cp_file) +{ + static bool is_initialized = false; + + if (!is_initialized) { + library_init(&libcapi, cp_file); + if (!capi_function_list_init(&libcapi, &cp_function_list)) { + LOG_ERROR("capi_function_list_init failed"); + return false; + } + is_initialized = true; + LOG_INFO("Cryptographic Provider initialized"); + } + return true; +} + +int +cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign) +{ + str_t signed_data = str_t_null; + str_t sign_reversed = str_t_null; + + LOG_TRACE("cryptopro_sign enter"); + + assert(ctx != NULL); + assert(ctx->signer != NULL); + assert(ctx->pin != NULL); + assert(data != NULL && !str_t_is_null(*data)); + assert(sign != NULL); + + if (sign_hash_data(ctx, data, &signed_data)) { + goto error; + } + + if (reverse_sign(&signed_data, &sign_reversed)) { + goto error; + } + + sign->len = base64_encoded_length(sign_reversed.len); + sign->data = calloc(1, sign->len + 1); + if (sign->data == NULL) { + LOG_ERROR("Could not allocate memory for sign (%zu bytes)", sign->len + 1); + goto error; + } + + encode_base64_url(sign, &sign_reversed); + + str_t_clear(&signed_data); + str_t_clear(&sign_reversed); + + LOG_TRACE("cryptopro_sign exit"); + return 0; + +error: + str_t_clear(&signed_data); + str_t_clear(&sign_reversed); + LOG_ERROR("cryptopro_sign exit with error"); + return -1; +} + +static int +sign_hash_data(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign) +{ + int rc = -1; + HCERTSTORE hStoreHandle; + PCCERT_CONTEXT pSignerCert = NULL; + BOOL bReleaseContext = FALSE; + HCRYPTPROV hCryptProv; + HCRYPTHASH hash = 0; + BYTE *pbSignedMessageBlob; + DWORD cbSignedMessageBlob; + + hStoreHandle = cert_open_store(); + if (hStoreHandle == NULL) { + goto exit; + } + + pSignerCert = get_signer_cert(ctx, hStoreHandle); + if (pSignerCert == NULL) { + goto exit; + } + + if (!cp_function_list.CryptAcquireCertificatePrivateKey( + pSignerCert, + CRYPT_ACQUIRE_SILENT_FLAG, + NULL, + &hCryptProv, + NULL, + &bReleaseContext)) { + LOG_ERROR("Cannot acquire the certificate private key"); + goto exit; + } + + if (!cp_function_list.CryptCreateHash(hCryptProv, CALG_GR3411_2012_256, 0, 0, &hash)) { + LOG_ERROR("CryptCreateHash() failed"); + goto exit; + } + + if (!cp_function_list.CryptHashData(hash, (BYTE *)data->data, data->len, 0)) { + LOG_ERROR("CryptHashData() failed"); + goto exit; + } + + if (!cp_function_list.CryptSignHash(hash, AT_KEYEXCHANGE, NULL, 0, NULL, &cbSignedMessageBlob)) { + LOG_ERROR("CryptSignHash() failed"); + goto exit; + } + + pbSignedMessageBlob = malloc(cbSignedMessageBlob * sizeof(BYTE)); + + if (!cp_function_list.CryptSignHash(hash, AT_KEYEXCHANGE, NULL, 0, pbSignedMessageBlob, &cbSignedMessageBlob)) { + LOG_ERROR("CryptSignHash() failed"); + free(pbSignedMessageBlob); + goto exit; + } + + sign->data = (char*)pbSignedMessageBlob; + sign->len = (size_t)cbSignedMessageBlob; + + rc = 0; + +exit: + if (hash) { + cp_function_list.CryptDestroyHash(hash); + } + + if (bReleaseContext) { + cp_function_list.CryptReleaseContext(hCryptProv, 0); + } + + if (pSignerCert) { + cp_function_list.CertFreeCertificateContext(pSignerCert); + } + + if (hStoreHandle) { + if (!cp_function_list.CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG)) { + LOG_ERROR("CertCloseStore() failed"); + } + } + + if (rc) { + LOG_ERROR("sign_hash_data exit with error. Last error code: 0x%08x", cp_function_list.GetLastError()); + } + return rc; +} + +static int +reverse_sign(const str_t *sign, /*out*/ str_t *sign_reversed) +{ + size_t i; + + sign_reversed->data = malloc(sign->len); + if (sign_reversed->data == NULL) { + LOG_ERROR("Could not allocate memory for reversed sign (%zd bytes)", sign->len); + return -1; + } + + for (i = 0; i < sign->len; i++) { + sign_reversed->data[i] = sign->data[sign->len - i - 1]; + } + + sign_reversed->len = sign->len; + + return 0; +} + +static HCERTSTORE +cert_open_store() +{ + HCERTSTORE hStoreHandle; + + LOG_TRACE("cert_open_store enter"); + + hStoreHandle = cp_function_list.CertOpenStore(CERT_STORE_PROV_SYSTEM, + 0, + 0, + CERT_SYSTEM_STORE_CURRENT_USER, + L"MY"); + + if (hStoreHandle == NULL) { + LOG_ERROR("The store could not be opened"); + return NULL; + } + + LOG_DEBUG("The MY store is open"); + + return hStoreHandle; +} + +static PCCERT_CONTEXT +get_signer_cert(const cryptopro_context_t *ctx, HCERTSTORE hStoreHandle) +{ + PCCERT_CONTEXT pSignerCert; + + pSignerCert = cp_function_list.CertFindCertificateInStore(hStoreHandle, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_STR, + ctx->signer, + NULL); + if (pSignerCert == NULL) { + LOG_ERROR("Could not find certificate in store"); + goto error; + } + + if (set_pin(pSignerCert, ctx->pin)) { + goto error; + } + + LOG_DEBUG("The signer's certificate was found"); + return pSignerCert; + +error: + if (pSignerCert) { + cp_function_list.CertFreeCertificateContext(pSignerCert); + } + LOG_ERROR("get_signer_cert exit with error"); + return NULL; +} + +static int +set_pin(PCCERT_CONTEXT pSignerCert, const str_t *pin) +{ + DWORD dwSize; + if (!cp_function_list.CertGetCertificateContextProperty( + pSignerCert, + CERT_KEY_PROV_INFO_PROP_ID, + NULL, + &dwSize)) { + LOG_ERROR("Error getting key property"); + LOG_ERROR("set_pin exit with error. Last error code: 0x%08x", cp_function_list.GetLastError()); + return -1; + } + + unsigned char pKeyInfoBuffer[dwSize]; + CRYPT_KEY_PROV_INFO* pKeyInfo = (CRYPT_KEY_PROV_INFO*) pKeyInfoBuffer; + + if (!cp_function_list.CertGetCertificateContextProperty( + pSignerCert, + CERT_KEY_PROV_INFO_PROP_ID, + pKeyInfo, + &dwSize)){ + LOG_ERROR("The second call to the function failed"); + goto error; + } + + DWORD keyType = (pKeyInfo->dwKeySpec == AT_SIGNATURE) ? PP_SIGNATURE_PIN + : PP_KEYEXCHANGE_PIN; + + CRYPT_KEY_PROV_PARAM keyProvParam; + memset(&keyProvParam, 0, sizeof(CRYPT_KEY_PROV_PARAM)); + keyProvParam.dwFlags = 0; + keyProvParam.dwParam = keyType; + keyProvParam.cbData = (DWORD)(pin->len + 1); + keyProvParam.pbData = (BYTE*) pin->data; + + pKeyInfo->cProvParam = 1; + pKeyInfo->rgProvParam = &keyProvParam; + + if (!cp_function_list.CertSetCertificateContextProperty( + pSignerCert, + CERT_KEY_PROV_INFO_PROP_ID, + 0, + pKeyInfo)) { + LOG_ERROR("Error setting key property"); + goto error; + } + + return 0; + +error: + LOG_ERROR("set_pin exit with error. Last error code: 0x%08x", cp_function_list.GetLastError()); + return -1; +} \ No newline at end of file diff --git a/src/utils/cryptopro.h b/src/utils/cryptopro.h new file mode 100644 index 0000000..a580216 --- /dev/null +++ b/src/utils/cryptopro.h @@ -0,0 +1,29 @@ +#ifndef CRYPTOPRO_H_INCLUDED +#define CRYPTOPRO_H_INCLUDED + +#include "str_t.h" + +#include +#include + + +typedef struct cryptopro_context_s { + const char *signer; + const str_t *pin; +} cryptopro_context_t; + + +static inline void +cryptopro_context_set(cryptopro_context_t *ctx, const char *signer, const str_t *pin) +{ + assert(ctx != NULL); + + ctx->signer = signer; + ctx->pin = pin; +} + +bool cryptopro_init(const char* cp_file); + +int cryptopro_sign(const cryptopro_context_t *ctx, const str_t *data, /*out*/ str_t *sign); + +#endif // CRYPTOPRO_H_INCLUDED diff --git a/src/utils/gconf_file.c b/src/utils/gconf_file.c new file mode 100644 index 0000000..ecd57f7 --- /dev/null +++ b/src/utils/gconf_file.c @@ -0,0 +1,244 @@ +#include "gconf_file.h" +#include "logger.h" + +#include + +/** Returns the length of the gchar string @str + */ +static inline size_t gstr_byte_len(const gchar* str) +{ + return strlen((char*) str); +} + +/* + * Copies the gchar string pointed by src into the content pointed by dst, including + * the terminating null character (and stopping at that point). + * Uses to avoid dependence from glib. + * @src: gchar string to be copied + * @dst: pointer to destination of type str_t where content is to be copied + * Return value: 0 - success, -1 - error + */ +static int +copy_gchar2strt(const gchar* src, str_t* dst) +{ + assert(src != NULL); + assert(dst != NULL); + + size_t len = gstr_byte_len(src); + char* data = (char*) malloc(len + 1); + if (data == NULL) { + LOG_ERROR("gchar2strt exit with error: could not allocate memory for dst"); + return -1; + } + + dst->data = data; + dst->len = len; + memcpy(dst->data, src, dst->len + 1); + dst->data[dst->len] = '\0'; + return 0; +} + + +/* + * Copies the gchar string pointed by src into the content pointed by return value, + * including the terminating null character (and stopping at that point). + * Uses to avoid dependence from glib. + * @src: gchar string to be copied + * Return value: pointer to destination where content is to be copied + */ +static char* +copy_gchar2char(const gchar* src) +{ + assert(src != NULL); + + // char* tmp_end = strchr(src, '\0'); + char* dst = NULL; + size_t len = gstr_byte_len(src); + dst = (char*) malloc(len + 1); + if (dst == NULL) { + LOG_ERROR("gchar2char exit with error: could not allocate memory for dst"); + return NULL; + } + memcpy(dst, src, len + 1); + return dst; +} + + +/* Returns the string value from GKeyFile associated with key under section and converted to str_t + * @ini_file: GKeyFile + * @section: section name in config file + * @key: key name + * @gerror: return location for a GError, or NULL + * @conf_field: str_t which will contain string + * Return value: 0 - success, -1 - error. + */ +int +gconf_file_get_strt(GKeyFile* ini_file, const char* section, const char* key, + GError** gerror, str_t* conf_field) +{ + assert(ini_file != NULL); + assert(section != NULL); + assert(key != NULL); + assert(gerror != NULL); + assert(conf_field != NULL); + + gchar* g_tmp = g_key_file_get_string(ini_file, section, key, gerror); + if (g_tmp == NULL) { + goto error; + } + if (copy_gchar2strt(g_tmp, conf_field) < 0) { + g_free(g_tmp); + LOG_ERROR("Could not copy %s:%s", section, key); + goto error; + } + g_free(g_tmp); + return 0; + +error: + LOG_TRACE("gconf_file_get_strt for key '%s::%s' exit with error", section, key); + return -1; +} + + +int +gconf_file_get_locale_strt(GKeyFile* ini_file, const char* section, const char* key, + const char* locale, GError** gerror, str_t* conf_field) +{ + assert(ini_file != NULL); + assert(section != NULL); + assert(key != NULL); + assert(gerror != NULL); + assert(conf_field != NULL); + /* locale may be NULL */ + + gchar* g_tmp = g_key_file_get_locale_string(ini_file, section, key, locale, gerror); + if (g_tmp == NULL) { + goto error; + } + if (copy_gchar2strt(g_tmp, conf_field) < 0) { + g_free(g_tmp); + LOG_ERROR("Could not copy %s:%s", section, key); + goto error; + } + g_free(g_tmp); + return 0; + +error: + LOG_TRACE("gconf_file_get_locale_strt for key '%s::%s' exit with error", section, key); + return -1; +} + + +/* @ini_file: GKeyFile + * @section: section name in config file + * @key: key name + * @gerror: return location for a GError, or NULL + * Return value: a newly allocated null-terminated string or NULL + * if the specified key cannot be found. + */ +char* +gconf_file_get_string(GKeyFile* ini_file, const char* section, const char* key, + GError** gerror) +{ + assert(ini_file != NULL); + assert(section != NULL); + assert(key != NULL); + assert(gerror != NULL); + + char* conf_field = NULL; + gchar* g_tmp = g_key_file_get_string(ini_file, section, key, gerror); + if (g_tmp == NULL) { + goto error; + } + conf_field = copy_gchar2char(g_tmp); + g_free(g_tmp); + + return conf_field; + +error: + LOG_TRACE("gconf_file_get_string for key '%s::%s' exit with error", section, key); + return NULL; +} + + +static inline char** +char_p_array_calloc(size_t array_length) +{ + char** char_p_array = (char**)calloc(array_length + 1, sizeof(char*)); // +1 for null-terminated array + + return char_p_array; +} + + +void +char_p_array_free(char** char_p_array) +{ + if (char_p_array == NULL) { + return; + } + + size_t i; + for (i = 0 ; char_p_array[i] != NULL; i++) { + free(char_p_array[i]); + } + + free(char_p_array); + return; +} + + +static inline char** +copy_array_gchar2char(gchar** gchar_p_array, size_t array_length) +{ + char** char_p_array = char_p_array_calloc(array_length); + if (char_p_array == NULL) { + LOG_ERROR("copy_array_gchar2char exit with error: could not allocate " + "memory for char_p_array"); + goto error; + } + + size_t i; + for (i = 0 ; i < array_length; ++i) { + char* p = copy_gchar2char(gchar_p_array[i]); + if (p == NULL) { + goto error; + } + char_p_array[i] = p; + } + + return char_p_array; + +error: + char_p_array_free(char_p_array); + return NULL; +} + + +char** +gconf_file_get_string_list(GKeyFile* ini_file, const char* section, + const char* key, size_t* length, GError** gerror) +{ + assert(ini_file != NULL); + assert(section != NULL); + assert(key != NULL); + assert(gerror != NULL); + + gsize array_length = 0; + gchar** gchar_p_array = g_key_file_get_string_list(ini_file, section, key, + &array_length, gerror); + if (gchar_p_array == NULL) { + goto error; + } + + char** char_p_array = copy_array_gchar2char(gchar_p_array, array_length); + if (char_p_array != NULL) { + *length = array_length; + } + g_strfreev(gchar_p_array); + + return char_p_array; + +error: + LOG_TRACE("gconf_file_get_string_list for key '%s::%s' exit with error", section, key); + return NULL; +} diff --git a/src/utils/gconf_file.h b/src/utils/gconf_file.h new file mode 100644 index 0000000..4f8ddb9 --- /dev/null +++ b/src/utils/gconf_file.h @@ -0,0 +1,68 @@ +#ifndef GCONF_FILE_H_INCLUDED +#define GCONF_FILE_H_INCLUDED + +#include "str_t.h" + +#include + + +/* + * utils for working with GKeyFile + */ + +/* + * Returns the string value from GKeyFile associated with key under section and converted to str_t + * @ini_file: GKeyFile + * @section: section name in config file + * @key: key name + * @gerror: return location for a GError, or NULL + * @conf_field: str_t which will contain string + * Return value: 0 - success, -1 - error. + */ + +int gconf_file_get_strt(GKeyFile* ini_file, const char* section, const char* key, GError** gerror, str_t* conf_field); + + + /* + * Returns the locale string value from GKeyFile associated with key under + * section and converted to str_t + * @ini_file: GKeyFile + * @section: section name in config file + * @key: key name + * @gerror: return location for a GError, or NULL + * @conf_field: str_t which will contain string + * Return value: 0 - success, -1 - error. + */ + +int +gconf_file_get_locale_strt(GKeyFile* ini_file, + const char* section, + const char* key, + const char* locale, + GError** gerror, + str_t* conf_field); + +/* + * Returns the string value from GKeyFile associated with key under section + * @ini_file: GKeyFile + * @section: section name in config file + * @key: key name + * @gerror: return location for a GError, or NULL + * Return value: a newly allocated null-terminated string or NULL + * if the specified key cannot be found. + */ + +char* gconf_file_get_string(GKeyFile* ini_file, const char* section, const char* key, GError** gerror); + + +char** gconf_file_get_string_list(GKeyFile* ini_file, + const char* section, + const char* key, + size_t* length, + GError** gerror); + + +void char_p_array_free(char** char_p_array); + + +#endif // GCONF_FILE_H_INCLUDED diff --git a/src/utils/library.c b/src/utils/library.c new file mode 100644 index 0000000..1b82f5d --- /dev/null +++ b/src/utils/library.c @@ -0,0 +1,63 @@ +#include "library.h" + +#include "logger.h" + +#include +#include + +static int +library_load(library_t *self) +{ + self->m_handle = dlopen(self->filename, RTLD_LAZY); /* RTLD_NOW */ + if (self->m_handle == NULL) { + LOG_ERROR("dlopen failed. Desc: %s", dlerror()); + goto error; + } + self->is_loaded = 1; + return 0; +error: + LOG_ERROR("library_load exit with error"); + return -1; +} + +static void* +library_resolve(library_t *self, const char *symbol) +{ + if (!self->m_handle && library_load(self) == -1) { + return NULL; + } + + void *p = dlsym(self->m_handle, symbol); + char *err = dlerror(); + if (err) { + LOG_ERROR("dlsym failed. Desc: %s", err); + return NULL; + } + + return p; +} + +static int +library_unload(library_t *self) +{ + int rv = dlclose(self->m_handle); + self->m_handle = NULL; + self->is_loaded = 0; + if (rv != 0) { + LOG_ERROR("dlclose failed. Desc: %s", dlerror()); + return -1; + } + + return 0; +} + +void +library_init(library_t *inst, const char *filename) +{ + inst->m_handle = NULL; + inst->is_loaded = 0; + inst->filename = filename; + inst->load = library_load; + inst->resolve = library_resolve; + inst->unload = library_unload; +} diff --git a/src/utils/library.h b/src/utils/library.h new file mode 100644 index 0000000..5da9204 --- /dev/null +++ b/src/utils/library.h @@ -0,0 +1,18 @@ +#ifndef LIBRARY_H_INCLUDED +#define LIBRARY_H_INCLUDED + +typedef struct library_s library_t; + +struct library_s { + const char *filename; + void *m_handle; + int is_loaded; + + int (*load)(library_t *self); + void*(*resolve)(library_t *self, const char *symbol); + int(*unload)(library_t *self); +}; + +void library_init(library_t *inst, const char *filename); + +#endif // LIBRARY_H_INCLUDED diff --git a/src/utils/logger.c b/src/utils/logger.c new file mode 100644 index 0000000..bcf8851 --- /dev/null +++ b/src/utils/logger.c @@ -0,0 +1,15 @@ +#include "logger.h" + +#include + +char * +nowsec(void) +{ + static _Thread_local char strbuf[MAXSTRFTIME]; + time_t now = time(NULL); + struct tm lt = {0}; + + strftime(strbuf, MAXSTRFTIME, "%Y-%m-%d %H:%M:%S", localtime_r(&now, <)); + + return strbuf; +} diff --git a/src/utils/logger.h b/src/utils/logger.h new file mode 100644 index 0000000..1c2575d --- /dev/null +++ b/src/utils/logger.h @@ -0,0 +1,111 @@ +/********************************************* +Simple stderr logger based on variadic macros +*********************************************/ + +#ifndef LOGGER_H +#define LOGGER_H + +#include +#include +#include + +#define MAXSTRFTIME 128 + +char* nowsec(); + +/* Logger configuration */ + +#define LOG_ERROR_ENABLE +#define LOG_WARN_ENABLE +#define LOG_INFO_ENABLE + +#if defined(DEBUG) || defined(_DEBUG) +# define LOG_DEBUG_ENABLE +# define LOG_TRACE_ENABLE +#endif + +/* ******************** */ + + +#define LOG_TRAILER "\n" + +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + + +#define TAG_ERROR "ERROR: " +#define TAG_WARN "WARN: " +#define TAG_INFO "INFO: " +#define TAG_DEBUG "DEBUG: " +#define TAG_TRACE "TRACE: " + + +#if defined(LOG_ERROR_ENABLE) + +#if defined(DEBUG) || defined(_DEBUG) +# define LOG_ERROR(format, ...) fprintf(stderr, "%s [%d] " TAG_ERROR "%s:%*u " format LOG_TRAILER, nowsec(), (int)getpid(), __FILENAME__, (int)(strlen(__FILENAME__)-24), __LINE__, ##__VA_ARGS__) +#else +# define LOG_ERROR(format, ...) fprintf(stderr, "%s [%d] " TAG_ERROR format LOG_TRAILER, nowsec(), (int)getpid(), ##__VA_ARGS__) +#endif /* DEBUG || _DEBUG*/ + +#else +# define LOG_ERROR(format, ...) +#endif /*LOG_ERROR_ENABLE*/ + + + +#if defined(LOG_WARN_ENABLE) + +#if defined(DEBUG) || defined(_DEBUG) +# define LOG_WARN(format, ...) fprintf(stderr, "%s [%d] " TAG_WARN "%s:%*u " format LOG_TRAILER, nowsec(), (int)getpid(), __FILENAME__, (int)(strlen(__FILENAME__)-24), __LINE__, ##__VA_ARGS__) +#else +# define LOG_WARN(format, ...) fprintf(stderr, "%s [%d] " TAG_WARN format LOG_TRAILER, nowsec(), (int)getpid(), ##__VA_ARGS__) +#endif /* DEBUG || _DEBUG*/ + +#else +# define LOG_WARN(format, ...) +#endif /*LOG_WARN_ENABLE*/ + + + +#if defined(LOG_INFO_ENABLE) + +#if defined(DEBUG) || defined(_DEBUG) +# define LOG_INFO(format, ...) fprintf(stderr, "%s [%d] " TAG_INFO "%s:%*u " format LOG_TRAILER, nowsec(), (int)getpid(), __FILENAME__, (int)(strlen(__FILENAME__)-24), __LINE__, ##__VA_ARGS__) +#else +# define LOG_INFO(format, ...) fprintf(stderr, "%s [%d] " TAG_INFO format LOG_TRAILER, nowsec(), (int)getpid(), ##__VA_ARGS__) +#endif /* DEBUG || _DEBUG*/ + +#else +# define LOG_INFO(format, ...) +#endif /*LOG_INFO_ENABLE*/ + + + +#if defined(LOG_DEBUG_ENABLE) + +#if defined(DEBUG) || defined(_DEBUG) +# define LOG_DEBUG(format, ...) fprintf(stderr, "%s [%d] " TAG_DEBUG "%s:%*u " format LOG_TRAILER, nowsec(), (int)getpid(), __FILENAME__, (int)(strlen(__FILENAME__)-24), __LINE__, ##__VA_ARGS__) +#else +# define LOG_DEBUG(format, ...) fprintf(stderr, "%s [%d] " TAG_DEBUG format LOG_TRAILER, nowsec(), (int)getpid(), ##__VA_ARGS__) +#endif /* DEBUG || _DEBUG*/ + +#else +# define LOG_DEBUG(format, ...) +#endif /*LOG_DEBUG_ENABLE*/ + + + +#if defined(LOG_TRACE_ENABLE) + +#if defined(DEBUG) || defined(_DEBUG) +# define LOG_TRACE(format, ...) fprintf(stderr, "%s [%d] " TAG_TRACE "%s:%*u " format LOG_TRAILER, nowsec(), (int)getpid(), __FILENAME__, (int)(strlen(__FILENAME__)-24), __LINE__, ##__VA_ARGS__) +#else +# define LOG_TRACE(format, ...) fprintf(stderr, "%s [%d] " TAG_TRACE format LOG_TRAILER, nowsec(), (int)getpid(), ##__VA_ARGS__) +#endif /* DEBUG || _DEBUG*/ + +#else +# define LOG_TRACE(format, ...) +#endif /*LOG_TRACE_ENABLE*/ + + +#endif /*LOGGER_H*/ diff --git a/src/utils/str_t.c b/src/utils/str_t.c new file mode 100644 index 0000000..ac47a7c --- /dev/null +++ b/src/utils/str_t.c @@ -0,0 +1,51 @@ +#include "str_t.h" +#include "logger.h" + +#include + + +int +str_t_copy(str_t* dst, const str_t* src) +{ + assert(dst != NULL); + assert(src != NULL); + + str_t_nullify(dst); + + if (str_t_is_null(*src)) { + return 0; + } + + assert(src->data != NULL); + + dst->data = malloc(src->len); + if (dst->data == NULL) { + LOG_ERROR("Could not allocate memory (%zu bytes)", src->len); + goto error; + } + memcpy(dst->data, src->data, src->len); + dst->len = src->len; + + return 0; + +error: + LOG_ERROR("str_t_copy exit with error"); + return -1; +} + + +char* +str_t_to_string(const str_t* str) +{ + assert(str != NULL && !str_t_is_null(*str)); + + char* string = malloc(str->len + 1); + if (string == NULL) { + LOG_ERROR("Could not allocate %zu bytes", str->len + 1); + return NULL; + } + memcpy(string, str->data, str->len); + string[str->len] = '\0'; + + return string; +} diff --git a/src/utils/str_t.h b/src/utils/str_t.h new file mode 100644 index 0000000..6b28404 --- /dev/null +++ b/src/utils/str_t.h @@ -0,0 +1,95 @@ +#ifndef STR_T_H_INCLUDED +#define STR_T_H_INCLUDED + +#include +#include + +typedef struct str_s { + char* data; + size_t len; + +} str_t; + +#define str_t_null { .data = NULL, .len = 0 } +#define str_t_init(_data, _len) { .data = _data, .len = _len } +#define str_t_const(c_str) { .data = (char*)c_str, .len = sizeof(c_str)-1 } + +/* +str_t_is_null(str) +checks if str_t at null-state. +IMPORTANT: assumed that str is passed by value, not by pointer! +Because macro does not want to bother about pointer value. +Userspace is responsible for str_t-pointers. +IMPORTANT: it is IMPROPERLY to check like this: (str.len == 0 && str.data == NULL). +Because in that case the expression: !str_t_is_null(str) would give a wrong answer. +*/ +#define str_t_is_null(str) ((str).len == 0 || (str).data == NULL) + +#define str_t_is_null_terminated(str) ((str).data ? (str).data[(str).len] == '\0' : 1) + +#define str_t_set_const(str, c_str) { (str).data = c_str; (str).len = sizeof(c_str)-1; } + +static inline void +str_t_nullify(str_t* str) +{ + str->data = NULL; + str->len = 0; +} + +static inline void +str_t_set(str_t* str, char* c_str, size_t c_str_len) +{ + str->data = c_str; + if (c_str_len == 0) { + str->len = strlen(c_str); + } else { + str->len = c_str_len; + } +} + +static inline void +str_t_clear(str_t* str) +{ + if (str != NULL) { + free(str->data); + str_t_nullify(str); + } +} + +static inline void +str_t_free(str_t* str) +{ + str_t_clear(str); + free(str); +} + +typedef struct const_str_s { + const char* data; + size_t len; + +} const_str_t; + +#define const_str_t_init(_data, _len) { .data = _data, .len = _len } + +/* + * поля структуры заполяются нулями (память не освобождается) + */ +static inline void +const_str_t_null(const_str_t* str) +{ + str->data = NULL; + str->len = 0; +} + +static inline void +const_str_t_set(const_str_t* str, const char* c_str) +{ + str->data = c_str; + str->len = strlen(c_str); +} + +int str_t_copy(str_t* dst, const str_t* src); + +char* str_t_to_string(const str_t* str); + +#endif // STR_T_H_INCLUDED diff --git a/src/utils/types.h b/src/utils/types.h new file mode 100644 index 0000000..e2e700a --- /dev/null +++ b/src/utils/types.h @@ -0,0 +1,176 @@ +#ifndef TYPES_H_INCLUDED +#define TYPES_H_INCLUDED + +#include "logger.h" + +#include +#include + +/******************************************************************************/ +/****************************** int_list_t *********************************/ +/******************************************************************************/ + + +typedef struct int_list_s { + int *list; + size_t size; +} int_list_t; + + +static inline void +int_list_clear(int_list_t *list) +{ + if (list == NULL) return; + + free(list->list); + + memset(list, 0, sizeof(int_list_t)); +} + + +static inline void +int_list_print(const int_list_t *list __attribute__((unused))) +{ +#if defined(LOG_DEBUG_ENABLE) + if (list == NULL) return; + + size_t i = 0; + for (; i < list->size; i++) { + LOG_DEBUG("\t\t%d", list->list[i]); + } + +#endif // defined +} + + +static inline int +int_list_copy(int_list_t *dst, const int_list_t *src) +{ + assert(dst != NULL); + assert(src != NULL); + assert(src->list != NULL); + + size_t size = src->size * sizeof(int); + + dst->list = calloc(src->size, sizeof(int)); + if (dst->list == NULL) { + LOG_ERROR("Could not allocate memory (%zu bytes)", size); + goto error; + } + memcpy(dst->list, src->list, size); + dst->size = src->size; + + return 0; + +error: + LOG_ERROR("int_list_copy exit with error"); + return -1; +} + + +/******************************************************************************/ +/*************************** string_list_t *********************************/ +/******************************************************************************/ + + +typedef struct string_list_s { + char **list; + size_t size; +} string_list_t; + + +static inline string_list_t* +string_list_create(size_t size) +{ + string_list_t *list = (string_list_t*) calloc(1, sizeof(string_list_t)); + if (list == NULL) { + LOG_ERROR("Could not allocate memory for string_list_t (size: %zu)", + sizeof(string_list_t)); + goto error; + } + if (size == 0) { + return list; + } + + list->size = size; + list->list = (char**) calloc(1, size * sizeof(char*)); + if (list->list == NULL) { + LOG_ERROR("Could not allocate memory for string_list_t->list (size: %zu)", + size * sizeof(char*)); + goto error; + } + + return list; + +error: + free(list); + LOG_ERROR("string_list_create exit with error"); + return NULL; +} + + +static inline void +string_list_clear(string_list_t *list) +{ + if (list == NULL) return; + + size_t i = 0; + for (; i < list->size; i++) { + free(list->list[i]); + } + + free(list->list); + + memset(list, 0, sizeof(string_list_t)); +} + + +static inline void +string_list_print(const string_list_t *list __attribute__((unused))) +{ +#if defined(LOG_DEBUG_ENABLE) + if (list == NULL) return; + + size_t i = 0; + for (; i < list->size; i++) { + LOG_DEBUG("\t\t%s", list->list[i]); + } + +#endif // defined +} + + +static inline int +string_list_copy(string_list_t *dst, const string_list_t *src) +{ + assert(dst != NULL); + assert(src != NULL); + + memset(dst, 0, sizeof(string_list_t)); + + if (src->size == 0 && src->list == NULL) { + return 0; + } + + assert(src->size != 0); + assert(src->list != NULL); + + size_t size = src->size * sizeof(char*); + + dst->list = calloc(src->size, sizeof(char*)); + if (dst->list == NULL) { + LOG_ERROR("Could not allocate memory (%zu bytes)", size); + goto error; + } + memcpy(dst->list, src->list, size); + dst->size = src->size; + + return 0; + +error: + LOG_ERROR("string_list_copy exit with error"); + return -1; +} + + +#endif /* TYPES_H_INCLUDED */ diff --git a/src/version.h.in b/src/version.h.in new file mode 100644 index 0000000..90dd995 --- /dev/null +++ b/src/version.h.in @@ -0,0 +1,6 @@ +#ifndef VERSION_H_INCLUDED +#define VERSION_H_INCLUDED + +#define ESIA_MODULE_VERSION "@PROJECT_VERSION@" + +#endif // VERSION_H_INCLUDED diff --git a/src/worker.c b/src/worker.c new file mode 100644 index 0000000..18f08a3 --- /dev/null +++ b/src/worker.c @@ -0,0 +1,137 @@ +#if !defined(_GNU_SOURCE) + #define _GNU_SOURCE +#endif + +#include "worker.h" + +#include +#include +#include +#include + +#define BACKTRACE_DEPTH 16 + +static service_manager_t services; + + +static void signal_error(int sig, siginfo_t *si, void *ptr); + + +int +worker_main(const service_manager_conf_t* conf) +{ + struct sigaction sigact; + sigset_t sigset; + int signo; + int status; + + + // сигналы об ошибках в программе будут обрататывать более тщательно + // указываем что хотим получать расширенную информацию об ошибках + sigact.sa_flags = SA_SIGINFO; + // задаем функцию обработчик сигналов + sigact.sa_sigaction = signal_error; + + sigemptyset(&sigact.sa_mask); + + // установим наш обработчик на сигналы + + sigaction(SIGFPE, &sigact, 0); // ошибка FPU + sigaction(SIGILL, &sigact, 0); // ошибочная инструкция + sigaction(SIGSEGV, &sigact, 0); // ошибка доступа к памяти + sigaction(SIGBUS, &sigact, 0); // ошибка шины, при обращении к физической памяти + sigaction(SIGABRT, &sigact, 0); // cигнал о прекращении, посланный abort(3) + + sigemptyset(&sigset); + + // блокируем сигналы которые будем ожидать + // сигнал остановки процесса пользователем + sigaddset(&sigset, SIGQUIT); + + // сигнал для остановки процесса пользователем с терминала + sigaddset(&sigset, SIGINT); + + // сигнал запроса завершения процесса + sigaddset(&sigset, SIGTERM); + + sigprocmask(SIG_BLOCK, &sigset, NULL); + + // запишем в лог, что наш демон стартовал + LOG_INFO("[WORKER] Started"); + + // запускаем все рабочие потоки + status = init_services(&services, conf); + + if (status) { + LOG_ERROR("[WORKER] Create work thread failed"); + } else { + // цикл ожидания сообщений + for (;;) { + // ждем указанных сообщений + sigwait(&sigset, &signo); + + LOG_INFO("[WORKER] signal received: %s", strsignal(signo)); + + // выйдим из цикла + break; + } + + // остановим все рабочеи потоки и корректно закроем всё что надо + deinit_services(&services); + } + + LOG_INFO("[WORKER] Stopped"); + + // вернем код не требующим перезапуска + return CHILD_NEED_TERMINATE; +} + + +// функция обработки сигналов +static void +signal_error(int sig, siginfo_t *si, void *ptr) +{ + void* ErrorAddr; + void* Trace[16]; + int x; + int TraceSize; + char** Messages; + + // запишем в лог что за сигнал пришел + LOG_INFO("Signal: %s, Addr: %p", strsignal(sig), si->si_addr); + + + #if __WORDSIZE == 64 // если дело имеем с 64 битной ОС + // получим адрес инструкции которая вызвала ошибку + ErrorAddr = (void*)((ucontext_t*)ptr)->uc_mcontext.gregs[REG_RIP]; + #else + // получим адрес инструкции которая вызвала ошибку + ErrorAddr = (void*)((ucontext_t*)ptr)->uc_mcontext.gregs[REG_EIP]; + #endif + + // произведем backtrace чтобы получить весь стек вызовов + TraceSize = backtrace(Trace, BACKTRACE_DEPTH); + Trace[1] = ErrorAddr; + + // получим расшифровку трасировки + Messages = backtrace_symbols(Trace, TraceSize); + if (Messages) { + LOG_INFO("== Backtrace =="); + + // запишем в лог + for (x = 1; x < TraceSize; x++) { + LOG_INFO("%s", Messages[x]); + } + + LOG_INFO("== End Backtrace =="); + free(Messages); + } + + LOG_INFO("Stopped"); + + // остановим все рабочие потоки и корректно закроем всё что надо + deinit_services(&services); + + // завершим процесс с кодом требующим перезапуска + exit(CHILD_NEED_WORK); +} diff --git a/src/worker.h b/src/worker.h new file mode 100644 index 0000000..0d5c2eb --- /dev/null +++ b/src/worker.h @@ -0,0 +1,13 @@ +#ifndef WORKER_H_INCLUDED +#define WORKER_H_INCLUDED + +#include "service_manager.h" + +// константы для кодов завершения процесса +#define CHILD_NEED_WORK 1 +#define CHILD_NEED_TERMINATE 2 + + +int worker_main(const service_manager_conf_t* conf); + +#endif // WORKER_H_INCLUDED