SUPPORT-8455. Реализован модуль подписания

This commit is contained in:
alashkova 2024-08-21 12:47:16 +03:00
commit 4243ebae5e
42 changed files with 4889 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
CMakeFiles/
CMakeLists.txt.user
src/config.h
src/version.h

101
CMakeLists.txt Normal file
View file

@ -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})

106
README.md Normal file
View file

@ -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 = \*\*\*\* *\# пароль от контейнера*

View file

@ -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 = ****

6
src/config.h.in Normal file
View file

@ -0,0 +1,6 @@
#ifndef CONFIG_H_INCLUDED
#define CONFIG_H_INCLUDED
#define CONF_NAME "@CONFIG_NAME@"
#endif // CONFIG_H_INCLUDED

103
src/fcgisrv/fcgi_map.c Normal file
View file

@ -0,0 +1,103 @@
#include "fcgi_server_internal.h"
#include <assert.h>
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;
}

305
src/fcgisrv/fcgi_server.c Normal file
View file

@ -0,0 +1,305 @@
#include "fcgi_server_internal.h"
#include "utils/conf_file_context.h"
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#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");
}

73
src/fcgisrv/fcgi_server.h Normal file
View file

@ -0,0 +1,73 @@
#ifndef FCGI_SERVER_H_INCLUDED
#define FCGI_SERVER_H_INCLUDED
#include <fcgiapp.h>
#include <stdlib.h>
#include <stdio.h>
#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 */

View file

@ -0,0 +1,70 @@
#ifndef FCGI_SERVER_INTERNAL_H_INCLUDED
#define FCGI_SERVER_INTERNAL_H_INCLUDED
#include "fcgi_utils.h"
#include <pthread.h>
#include <stdatomic.h>
#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

178
src/fcgisrv/fcgi_thread.c Normal file
View file

@ -0,0 +1,178 @@
#include "fcgi_server_internal.h"
#include <assert.h>
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);
}

79
src/fcgisrv/fcgi_utils.c Normal file
View file

@ -0,0 +1,79 @@
#include "fcgi_server_internal.h"
#include "utils/logger.h"
#include <assert.h>
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;
}

189
src/fcgisrv/fcgi_utils.h Normal file
View file

@ -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 */

139
src/fcgisrv/fcgi_worker.c Normal file
View file

@ -0,0 +1,139 @@
#include "fcgi_server_internal.h"
#include <assert.h>
#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;
}

101
src/main.c Normal file
View file

@ -0,0 +1,101 @@
#include "master.h"
#include "config.h"
#include "version.h"
#include "utils/logger.h"
#include <getopt.h>
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;
}

75
src/main_conf.c Normal file
View file

@ -0,0 +1,75 @@
#include "main_conf.h"
#include <assert.h>
#include <stdlib.h>
#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));
}

15
src/main_conf.h Normal file
View file

@ -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

220
src/master.c Normal file
View file

@ -0,0 +1,220 @@
#include "master.h"
#include "worker.h"
#include <signal.h>
#include <wait.h>
#include <errno.h>
#include <assert.h>
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");
}

9
src/master.h Normal file
View file

@ -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

375
src/modules/service_sign.c Normal file
View file

@ -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;
}

View file

@ -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

245
src/service_manager.c Normal file
View file

@ -0,0 +1,245 @@
#include "service_manager.h"
#include <assert.h>
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;
}

76
src/service_manager.h Normal file
View file

@ -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

82
src/utils/base64.c Normal file
View file

@ -0,0 +1,82 @@
#include "base64.h"
#include <assert.h>
#include <stdint.h>
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];
}
}
}

10
src/utils/base64.h Normal file
View file

@ -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

37
src/utils/capi.c Normal file
View file

@ -0,0 +1,37 @@
#include "capi.h"
#include "library.h"
#include <assert.h>
#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;
}

154
src/utils/capi.h Normal file
View file

@ -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 <stdarg.h>
#include <cades.h>
#include <stdbool.h>
#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

View file

@ -0,0 +1,700 @@
#include "conf_file_context.h"
#include "gconf_file.h"
#include "types.h"
#include <assert.h>
#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;
}

View file

@ -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

295
src/utils/cryptopro.c Normal file
View file

@ -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;
}

29
src/utils/cryptopro.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef CRYPTOPRO_H_INCLUDED
#define CRYPTOPRO_H_INCLUDED
#include "str_t.h"
#include <assert.h>
#include <stdbool.h>
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

244
src/utils/gconf_file.c Normal file
View file

@ -0,0 +1,244 @@
#include "gconf_file.h"
#include "logger.h"
#include <assert.h>
/** 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;
}

68
src/utils/gconf_file.h Normal file
View file

@ -0,0 +1,68 @@
#ifndef GCONF_FILE_H_INCLUDED
#define GCONF_FILE_H_INCLUDED
#include "str_t.h"
#include <glib.h>
/*
* 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

63
src/utils/library.c Normal file
View file

@ -0,0 +1,63 @@
#include "library.h"
#include "logger.h"
#include <dlfcn.h>
#include <stddef.h>
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;
}

18
src/utils/library.h Normal file
View file

@ -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

15
src/utils/logger.c Normal file
View file

@ -0,0 +1,15 @@
#include "logger.h"
#include <time.h>
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, &lt));
return strbuf;
}

111
src/utils/logger.h Normal file
View file

@ -0,0 +1,111 @@
/*********************************************
Simple stderr logger based on variadic macros
*********************************************/
#ifndef LOGGER_H
#define LOGGER_H
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#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*/

51
src/utils/str_t.c Normal file
View file

@ -0,0 +1,51 @@
#include "str_t.h"
#include "logger.h"
#include <assert.h>
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;
}

95
src/utils/str_t.h Normal file
View file

@ -0,0 +1,95 @@
#ifndef STR_T_H_INCLUDED
#define STR_T_H_INCLUDED
#include <stdlib.h>
#include <string.h>
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

176
src/utils/types.h Normal file
View file

@ -0,0 +1,176 @@
#ifndef TYPES_H_INCLUDED
#define TYPES_H_INCLUDED
#include "logger.h"
#include <assert.h>
#include <stdlib.h>
/******************************************************************************/
/****************************** 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 */

6
src/version.h.in Normal file
View file

@ -0,0 +1,6 @@
#ifndef VERSION_H_INCLUDED
#define VERSION_H_INCLUDED
#define ESIA_MODULE_VERSION "@PROJECT_VERSION@"
#endif // VERSION_H_INCLUDED

137
src/worker.c Normal file
View file

@ -0,0 +1,137 @@
#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
#include "worker.h"
#include <signal.h>
#include <wait.h>
#include <execinfo.h>
#include <sys/ucontext.h>
#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);
}

13
src/worker.h Normal file
View file

@ -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