2024-07-23 11:00:05 +00:00

1212 lines
38 KiB
C

// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// @file ngx_cp_utils.c
#include "ngx_cp_utils.h"
#include <ngx_core.h>
#include <ngx_log.h>
#include <ngx_string.h>
#include <ngx_files.h>
#include <ngx_http.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <time.h>
#include <math.h>
#include "nginx_attachment_util.h"
#include "ngx_cp_initializer.h"
#include "nginx_attachment_common.h"
#include "ngx_cp_metric.h"
#define USERCHECK_TITLE_START "<!-- CHECK_POINT_USERCHECK_TITLE_PLACEHOLDER-->"
#define USERCHECK_BODY_START "<!-- CHECK_POINT_USERCHECK_BODY_PLACEHOLDER-->"
#define USERCHECK_UUID_START "<!-- CHECK_POINT_USERCHECK_UUID_PLACEHOLDER-->"
#define MAX_STATIC_RESOURCES_PATH_LENGTH 256
static ngx_int_t is_configuration_updated = NGX_ERROR;
static ngx_uint_t web_triggers_response_code = NGX_HTTP_FORBIDDEN;
#define RESPONSE_PAGE_PARTS 4
static ngx_str_t web_response_page_parts[RESPONSE_PAGE_PARTS];
static ngx_uint_t web_response_title_size = 0;
static char web_response_title[64]; ///< Web response title static buffer.
static ngx_uint_t web_response_body_size = 0;
static char web_response_body[256]; ///< Web response body static buffer.
static ngx_uint_t web_response_uuid_size = 0;
static char web_response_uuid[64]; ///< Web response body uuid buffer.
static char incident[sizeof(web_response_uuid) + 16]; ///< Web response body uuid buffer with incident Id prefix.
static ngx_uint_t add_event_id = 0;
static ngx_uint_t redirect_location_size = 0; ///< Redirect location size.
static u_char redirect_location[512]; ///< Redirect location buffer.
static const ngx_uint_t max_static_resources_path_length = MAX_STATIC_RESOURCES_PATH_LENGTH;
static char static_resources_path[MAX_STATIC_RESOURCES_PATH_LENGTH];
static ngx_pool_t *memory_pool = NULL;
// no need to add a semicolon since it is already part of the included content
static const char web_response_page_format[] =
#include "ngx_cp_http_usercheck.h"
static int is_ctx_match = 1;
static int dbg_level = DBG_LEVEL_INFO; ///< Default debug level.
static uint32_t cur_session_id = 0; ///< Current session ID.
static uint pid = 0;
ngx_http_cp_sessions_per_minute_limit sessions_per_minute_limit_info = {
.sessions_per_second = {0},
.last_minute_sessions_sum = 0,
.last_session_time = 0,
};
ngx_uint_t current_config_version = 0;
ngx_int_t fail_mode_verdict = NGX_OK; ///< Fail open verdict incase of a timeout.
ngx_int_t fail_mode_hold_verdict = NGX_OK; ///< Fail open verdict incase of a timeout when waiting for wait verdict.
ngx_int_t dbg_is_needed = 0; ///< Debug flag.
ngx_int_t num_of_connection_attempts = 0; ///< Maximum number of attempted connections.
ngx_uint_t fail_open_timeout = 50; ///< Fail open timeout in milliseconds.
ngx_uint_t fail_open_hold_timeout = 150; ///< Fail open wait timeout in milliseconds.
ngx_http_cp_verdict_e sessions_per_minute_limit_verdict = TRAFFIC_VERDICT_ACCEPT;
ngx_uint_t max_sessions_per_minute = 0; ///< Masimum session per minute.
ngx_uint_t req_max_proccessing_ms_time = 3000; ///< Total Request processing timeout in milliseconds.
ngx_uint_t res_max_proccessing_ms_time = 3000; ///< Total Response processing timeout in milliseconds.
ngx_uint_t registration_thread_timeout_msec = 100; ///< Registration timeout in milliseconds.
ngx_uint_t req_header_thread_timeout_msec = 100; ///< Request header processing timeout in milliseconds.
ngx_uint_t req_body_thread_timeout_msec = 150; ///< Request body processing timeout in milliseconds.
ngx_uint_t res_header_thread_timeout_msec = 100; ///< Response header processing timeout in milliseconds.
ngx_uint_t res_body_thread_timeout_msec = 150; ///< Response body processing timeout in milliseconds.
ngx_uint_t waiting_for_verdict_thread_timeout_msec = 150; ///< Wait thread processing timeout in milliseconds.
ngx_http_inspection_mode_e inspection_mode = NON_BLOCKING_THREAD; ///< Default inspection mode.
ngx_uint_t num_of_nginx_ipc_elements = 200; ///< Number of NGINX IPC elements.
ngx_msec_t keep_alive_interval_msec = DEFAULT_KEEP_ALIVE_INTERVAL_MSEC;
ngx_uint_t min_retries_for_verdict = 3; ///< Minimum number of retries for verdict.
ngx_uint_t max_retries_for_verdict = 15; ///< Maximum number of retries for verdict.
ngx_uint_t body_size_trigger = 200000; ///< Request body size in bytes to switch to maximum retries for verdict.
static struct timeval
getCurrTimeFast()
{
struct timeval curr_time;
struct timespec curr_time_mono;
clock_gettime(CLOCK_MONOTONIC_COARSE, &curr_time_mono);
curr_time.tv_sec = curr_time_mono.tv_sec;
curr_time.tv_usec = curr_time_mono.tv_nsec/1000.0;
return curr_time;
}
void
init_list_iterator(ngx_list_t *list, ngx_http_cp_list_iterator *iterator)
{
if (list == NULL) {
write_dbg(DBG_LEVEL_ASSERT, "Failed to initialize list iterator: NULL list pointer");
return;
}
if (iterator == NULL) {
write_dbg(DBG_LEVEL_ASSERT, "Failed to initialize list iterator: NULL iterator pointer");
return;
}
iterator->current_part = &list->part;
iterator->current_part_element_index = 0;
iterator->current_list_element_index = 0;
iterator->list_element_size = list->size;
}
void *
get_list_element(ngx_http_cp_list_iterator *iterator, const size_t index)
{
while (iterator->current_list_element_index < index) {
if (iterator->current_part_element_index >= iterator->current_part->nelts) {
if (iterator->current_part->next == NULL) break;
iterator->current_part = iterator->current_part->next;
iterator->current_part_element_index = 0;
}
iterator->current_part_element_index++;
iterator->current_list_element_index++;
}
if (iterator->current_part->next == NULL && iterator->current_list_element_index < index) return NULL;
return (char *)(iterator->current_part)->elts + iterator->list_element_size * iterator->current_part_element_index;
}
ngx_int_t
free_list_from_pool(ngx_pool_t *memory_pool, ngx_list_t *list)
{
int was_free_successful = 1;
size_t curr_list_part_elem_idx;
int freed_current = 0;
void *data;
ngx_list_part_t *part, *next_part;
size_t num_part_elements;
if (list == NULL) return NGX_OK;
part = &list->part;
if (part == NULL) {
// Free list's head.
ngx_pfree(memory_pool, list);
return NGX_OK;
}
data = part->elts;
num_part_elements = part->nelts;
next_part = part->next;
for (curr_list_part_elem_idx = 0 ;; curr_list_part_elem_idx++) {
if (curr_list_part_elem_idx >= num_part_elements) {
// Free all the elements.
if (part->next == NULL) {
break;
}
part = next_part;
data = part->elts;
num_part_elements = part->nelts;
curr_list_part_elem_idx = 0;
freed_current = 0;
}
if (!freed_current) {
was_free_successful &= ngx_pfree(memory_pool, data) == NGX_OK;
was_free_successful &= ngx_pfree(memory_pool, part) == NGX_OK;
freed_current = 1;
}
}
was_free_successful &= ngx_pfree(memory_pool, list) == NGX_OK;
return was_free_successful ? NGX_OK : NGX_ERROR;
}
///
/// @brief Initiate hash key array.
/// @param[in, out] memory_pool NGINX pool to allocate all the necessary data.
/// @param[in, out] hash_key_array Hash key array to allocate and initiate.
/// @param[in, out] key_list List of keys to add to the initialized hash table.
/// @param[in] initial_data_value_ptr Initial data value pointer.
/// @param[in] initial_data_size Initial data size that will be increased if necessary.
/// @returns ngx_int_t
/// - #NGX_OK.
/// - #NGX_ERROR.
///
ngx_int_t
init_hash_key_array(
ngx_pool_t *memory_pool,
ngx_hash_keys_arrays_t *hash_key_array,
ngx_list_t *key_list,
const void *initial_data_value_ptr,
const size_t initial_data_size
)
{
ngx_int_t add_key_result;
size_t idx;
ngx_list_part_t *list_part = NULL;
ngx_str_t *current_part_elements = NULL;
// Allocates initial_data_value_copy and copies intial data size.
void *initial_data_value_copy = ngx_pnalloc(memory_pool, initial_data_size);
memcpy(initial_data_value_copy, initial_data_value_ptr, initial_data_size);
hash_key_array->pool = memory_pool;
hash_key_array->temp_pool = memory_pool;
ngx_hash_keys_array_init(hash_key_array, NGX_HASH_SMALL);
// Go over the keys and insert them into the hash table.
list_part = &key_list->part;
current_part_elements = list_part->elts;
for (idx = 0; ; idx++) {
if (idx >= list_part->nelts) {
if (list_part->next == NULL) {
break;
}
list_part = list_part->next;
current_part_elements = list_part->elts;
idx = 0;
}
write_dbg(
DBG_LEVEL_TRACE,
"Adding key No. %u (name: %s, length: %u) to the list of hash table keys",
idx,
current_part_elements[idx].data,
current_part_elements[idx].len
);
add_key_result = ngx_hash_add_key(
hash_key_array,
&current_part_elements[idx],
initial_data_value_copy,
NGX_HASH_READONLY_KEY
);
if (add_key_result != NGX_OK) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to add the key %s to the list of hash table keys",
current_part_elements[idx].data
);
return NGX_ERROR;
}
write_dbg(
DBG_LEVEL_TRACE,
"Successfully added the key %s to the list of hash table keys",
current_part_elements[idx].data
);
}
return NGX_OK;
}
ngx_int_t
init_hash_table(
ngx_pool_t *memory_pool,
ngx_hash_init_t *hash_table_initializer,
ngx_hash_t *hash_table,
char *hash_table_name,
ngx_uint_t max_size,
ngx_uint_t bucket_size,
ngx_list_t *keys,
const void *initial_value,
const size_t initial_value_size
)
{
ngx_hash_keys_arrays_t hash_keys_array;
// Initiate hash table keys.
if (init_hash_key_array(memory_pool, &hash_keys_array, keys, initial_value, initial_value_size) != NGX_OK) {
write_dbg(DBG_LEVEL_WARNING, "Failed to initialize the keys of the hash table \"%s\"", hash_table_name);
return NGX_ERROR;
}
hash_table_initializer->hash = hash_table;
hash_table_initializer->key = ngx_hash_key;
hash_table_initializer->max_size = max_size;
hash_table_initializer->bucket_size = bucket_size;
hash_table_initializer->name = hash_table_name;
hash_table_initializer->pool = memory_pool;
hash_table_initializer->temp_pool = memory_pool;
// Initiate the hash table with the keys.
ngx_int_t res = ngx_hash_init(hash_table_initializer, hash_keys_array.keys.elts, hash_keys_array.keys.nelts);
if (res != 0 || hash_table_initializer->hash == NULL) return NGX_ERROR;
return NGX_OK;
}
void
copy_chain_buffers(ngx_chain_t *dest, ngx_chain_t *src)
{
ngx_chain_t *curr_src = src;
ngx_chain_t *curr_dst = dest;
while (curr_src != NULL && curr_dst != NULL) {
ngx_memcpy(curr_dst->buf, curr_src->buf, sizeof(ngx_buf_t));
curr_src = curr_src->next;
curr_dst = curr_dst->next;
}
}
void
prepend_chain_elem(ngx_chain_t *current_elem, ngx_chain_t *new_elem)
{
write_dbg(DBG_LEVEL_DEBUG, "Adding new chain element before current list element");
ngx_buf_t *temp_buffer = current_elem->buf;
current_elem->buf = new_elem->buf;
new_elem->buf = temp_buffer;
new_elem->next = current_elem->next;
current_elem->next = new_elem;
}
void
append_chain_elem(ngx_chain_t *current_elem, ngx_chain_t *new_elem)
{
write_dbg(DBG_LEVEL_DEBUG, "Adding new chain element after current list element");
new_elem->next = current_elem->next;
new_elem->buf->last_buf = current_elem->buf->last_buf;
new_elem->buf->last_in_chain = current_elem->buf->last_in_chain;
current_elem->next = new_elem;
current_elem->buf->last_buf = 0;
current_elem->buf->last_in_chain = 0;
}
ngx_int_t
split_chain_elem(ngx_chain_t *elem, uint16_t split_index, ngx_pool_t *pool)
{
ngx_chain_t *new_chain;
ngx_buf_t *new_buf;
uint16_t original_element_size = elem->buf->last - elem->buf->pos;
write_dbg(
DBG_LEVEL_DEBUG,
"Splitting buffer chain element. Split index: %d, original element size: %d",
split_index,
original_element_size
);
if (split_index <= 0 || split_index > original_element_size) {
write_dbg(DBG_LEVEL_ASSERT, "Cannot split chain element on illegal index: %d", split_index);
return NGX_ERROR;
}
new_buf = ngx_calloc_buf(pool);
if (new_buf == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate new buffer element");
return NGX_ERROR;
}
new_chain = ngx_alloc_chain_link(pool);
if (new_chain == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate new chain element");
ngx_pfree(pool, new_buf);
return NGX_ERROR;
}
new_buf->start = elem->buf->pos + split_index - 1;
new_buf->pos = new_buf->start;
new_buf->end = elem->buf->end;
new_buf->last = elem->buf->last;
new_buf->memory = 1;
elem->buf->end = elem->buf->pos + split_index - 1;
elem->buf->last = elem->buf->end;
new_chain->buf = new_buf;
append_chain_elem(elem, new_chain);
write_dbg(
DBG_LEVEL_DEBUG,
"Successfully split chain element. First half element size: %d, second half element size: %d",
elem->buf->last - elem->buf->pos,
new_chain->buf->last - new_chain->buf->pos
);
return NGX_OK;
}
///
/// @brief Allocates and creates a new NGINX chain element.
/// @param[in] data_size Size of the data to be put in NGINX chain.
/// @param[in, out] data Data to put into the newly allocates NGINX chain.
/// @param[in, out] pool NGINX pool to allocate buffers from.
/// @returns
///
ngx_chain_t *
create_chain_elem(uint32_t data_size, char *data, ngx_pool_t *pool)
{
ngx_buf_t *new_buf;
ngx_chain_t *new_chain;
new_buf = ngx_calloc_buf(pool);
if (new_buf == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate new buffer element");
return NULL;
}
new_chain = ngx_alloc_chain_link(pool);
if (new_chain == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to allocate new chain element");
ngx_pfree(pool, new_buf);
return NULL;
}
// Sets the new chain values.
new_buf->start = (u_char *)data;
new_buf->pos = new_buf->start;
new_buf->last = new_buf->pos + data_size;
new_buf->end = new_buf->last;
new_buf->memory = 1;
new_chain->buf = new_buf;
new_chain->next = NULL;
write_dbg(
DBG_LEVEL_DEBUG,
"Successfully created chain element. Element data: \"%s\", element data size: %d",
new_chain->buf->pos == NULL ? (u_char *)"" : new_chain->buf->pos,
new_chain->buf->last - new_chain->buf->pos
);
return new_chain;
}
void
free_chain(ngx_pool_t *pool, ngx_chain_t *chain)
{
ngx_chain_t *next_chain;
while (chain) {
ngx_pfree(pool, chain->buf->start);
ngx_pfree(pool, chain->buf);
next_chain = chain->next;
ngx_pfree(pool, chain);
chain = next_chain;
}
}
int
is_timeout_reached(struct timeval *timeout)
{
struct timeval curr_time;
curr_time = getCurrTimeFast();
return (timercmp(timeout, &curr_time, <));
}
struct timeval
get_timeout_val_sec(const int delta_time_in_sec)
{
struct timeval time;
time = getCurrTimeFast();
time.tv_sec += delta_time_in_sec;
return time;
}
struct timeval
get_timeout_val_usec(const int delta_time_in_usec)
{
struct timeval time;
time = getCurrTimeFast();
time.tv_usec += delta_time_in_usec;
return time;
}
struct timeval
get_timeout_val_msec(const int delta_time_in_msec)
{
struct timeval time;
time = getCurrTimeFast();
time.tv_sec += delta_time_in_msec / 1000;
time.tv_usec += (delta_time_in_msec % 1000) * 1000;
return time;
}
void
set_custom_response(const ngx_str_t *title, const ngx_str_t *body, const ngx_str_t *uuid, ngx_uint_t response_code)
{
write_dbg(
DBG_LEVEL_TRACE,
"Setting Custom response page:\nresponse_code = %d, title size = %d, body size = %d, uuid = %s, uuid size = %d",
response_code,
title->len,
body->len,
uuid->data,
uuid->len
);
web_triggers_response_code = response_code;
web_response_title_size = title->len;
web_response_body_size = body->len;
web_response_uuid_size = uuid->len;
if (web_response_title_size == 0 || web_response_body_size == 0) return;
// Copies the provided variables into their respective response variables.
memcpy(web_response_title, title->data, web_response_title_size);
memcpy(web_response_body, body->data, web_response_body_size);
if (web_response_uuid_size >= sizeof(web_response_uuid)) {
web_response_uuid_size = sizeof(web_response_uuid) - 1;
}
memcpy(web_response_uuid, uuid->data, web_response_uuid_size);
web_response_uuid[web_response_uuid_size] = 0;
}
void
set_redirect_response(const ngx_str_t *location, const ngx_str_t *uuid, uint add_event_id_to_header)
{
write_dbg(DBG_LEVEL_TRACE, "Setting Redirect response");
web_triggers_response_code = NGX_HTTP_TEMPORARY_REDIRECT;
add_event_id = add_event_id_to_header;
// Sets the redirection location data and the web response uuid.
redirect_location_size = location->len;
memcpy(redirect_location, location->data, redirect_location_size);
web_response_uuid_size = uuid->len;
if (web_response_uuid_size >= sizeof(web_response_uuid)) {
web_response_uuid_size = sizeof(web_response_uuid) - 1;
}
memcpy(web_response_uuid, uuid->data, web_response_uuid_size);
web_response_uuid[web_response_uuid_size] = 0;
}
u_char *
get_redirect_location()
{
return redirect_location;
}
ngx_uint_t
get_redirect_location_size()
{
return redirect_location_size;
}
ngx_uint_t
get_add_event_id()
{
return add_event_id;
}
void
set_response_page_chain_elem(ngx_buf_t **part, ngx_str_t *content, ngx_chain_t *cur_chain, ngx_chain_t **next_chain)
{
(*part)->pos = content->data;
(*part)->last = (*part)->pos + content->len;
(*part)->memory = 1;
(*part)->last_buf = *next_chain == NULL ? 1 : 0;
(*part)->last_in_chain = *next_chain == NULL ? 1 : 0;
cur_chain->buf = *part;
cur_chain->next = *next_chain;
}
ngx_int_t
get_response_page(ngx_http_request_t *request, ngx_chain_t (*out_chain)[7])
{
ngx_int_t idx;
ngx_chain_t *tmp_next;
size_t incident_prefix_size = strlen("Incident Id: ");
ngx_buf_t *buf[7]; // Title prefix -> Title -> Body prefix -> Body -> UUID prefix -> UUID -> UUID suffix
ngx_str_t title = { web_response_title_size, (u_char *)web_response_title };
ngx_str_t body = { web_response_body_size, (u_char *)web_response_body };
ngx_str_t uuid = { web_response_uuid_size + incident_prefix_size, (u_char *)incident };
if (web_response_title_size == 0 || web_response_body_size == 0) return NGX_ERROR_ERR;
memcpy(incident, "Incident Id: ", incident_prefix_size);
memcpy(incident + incident_prefix_size, web_response_uuid, web_response_uuid_size);
for (idx = 0; idx < 7; idx++) {
buf[idx] = ngx_calloc_buf(request->pool);
if (buf[idx] == NULL) {
for (; idx >= 0; idx--) {
ngx_pfree(request->pool, buf[idx]);
}
return NGX_ERROR_ERR;
}
}
tmp_next = *out_chain + 1;
set_response_page_chain_elem(buf, web_response_page_parts, *out_chain, &tmp_next);
tmp_next = *out_chain + 2;
set_response_page_chain_elem(buf + 1, &title, *out_chain + 1, &tmp_next);
tmp_next = *out_chain + 3;
set_response_page_chain_elem(buf + 2, web_response_page_parts + 1, *out_chain + 2, &tmp_next);
tmp_next = *out_chain + 4;
set_response_page_chain_elem(buf + 3, &body, *out_chain + 3, &tmp_next);
tmp_next = *out_chain + 5;
set_response_page_chain_elem(buf + 4, web_response_page_parts + 2, *out_chain + 4, &tmp_next);
tmp_next = *out_chain + 6;
set_response_page_chain_elem(buf + 5, &uuid, *out_chain + 5, &tmp_next);
tmp_next = NULL;
set_response_page_chain_elem(buf + 6, web_response_page_parts + 3, *out_chain + 6, &tmp_next);
return NGX_OK;
}
ngx_uint_t
get_response_page_length(void)
{
ngx_uint_t idx;
ngx_uint_t total_length = 0;
if (web_response_title_size == 0 || web_response_body_size == 0) return 0;
for (idx = 0; idx < RESPONSE_PAGE_PARTS; idx++) {
total_length += web_response_page_parts[idx].len;
}
total_length += web_response_title_size;
total_length += web_response_body_size;
total_length += strlen("Incident Id: ") + web_response_uuid_size;
return total_length;
}
ngx_uint_t
get_response_code(void)
{
return web_triggers_response_code;
}
const char *
get_web_response_uuid(void)
{
return web_response_uuid;
}
ngx_uint_t
get_web_response_uuid_size(void)
{
return web_response_uuid_size;
}
const char *
get_static_resources_path(void)
{
return static_resources_path;
}
ngx_pool_t *
get_memory_pool(void)
{
return memory_pool;
}
void
set_memory_pool(ngx_pool_t *new_memory_pool)
{
memory_pool = new_memory_pool;
}
unsigned int
get_number_of_digits(int num)
{
unsigned int num_of_digits = 0;
do {
num /= 10;
num_of_digits++;
} while (num != 0);
return num_of_digits;
}
ngx_http_cp_verdict_e
get_sessions_per_minute_limit_verdict()
{
return sessions_per_minute_limit_verdict;
}
unsigned int
get_max_sessions_per_minute()
{
return max_sessions_per_minute;
}
ngx_http_cp_sessions_per_minute_limit *
get_periodic_sessions_limit_info()
{
if (sessions_per_minute_limit_info.last_session_time == 0) {
sessions_per_minute_limit_info.last_session_time = (unsigned int)(time(NULL));
sessions_per_minute_limit_verdict = isFailOpenOnSessionLimit() ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP;
max_sessions_per_minute = getMaxSessionsPerMinute();
}
return &sessions_per_minute_limit_info;
}
void
set_cp_ngx_attachment_debug_level(int _dbg_level)
{
dbg_level = _dbg_level;
}
void
set_current_session_id(uint32_t _cur_session_id)
{
cur_session_id = _cur_session_id;
}
void ngx_cdecl
write_dbg_impl(int _dbg_level, const char *func, const char *file, int line_num, const char *fmt, ...)
{
if (_dbg_level < dbg_level || !is_ctx_match) return;
char debug_str[NGX_MAX_ERROR_STR] = {0};
char session_id_str[32] = {0};
va_list args;
time_t ttime;
int millisec;
struct timeval tv;
char time_stamp[64];
char str_uid[140];
time(&ttime);
tv = getCurrTimeFast();
millisec = lrint(tv.tv_usec/1000.0);
if (millisec>=1000) {
// Allow for rounding up to nearest second
millisec -=1000;
tv.tv_sec++;
}
strftime(time_stamp, sizeof(time_stamp), "%FT%T", localtime(&ttime));
if (!pid) pid = getpid();
if (cur_session_id > 0) {
snprintf(session_id_str, sizeof(session_id_str) - 1, "<session id %u> ", cur_session_id);
}
// Prints the debug given all the data and a formatter.
snprintf(
str_uid,
sizeof(str_uid) - 1,
"|%s.%03d: %s@%s:%d [uid %s | pid %u] %s| ",
time_stamp,
millisec,
func,
file,
line_num,
get_unique_id(),
pid,
session_id_str
);
va_start(args, fmt);
vsnprintf(debug_str, sizeof(debug_str) - 1, fmt, args);
va_end(args);
dprintf(ngx_stderr, "%s%s\n", str_uid, debug_str);
}
///
/// @brief Initialize a web response data using the set global variables.
///
static void
initialize_web_response_data()
{
char *title_pos = NULL;
char *body_pos = NULL;
char *uuid_pos = NULL;
// Sets Check Point's attack detection string.
static char default_title[] = "Attack blocked by web application protection";
static char default_body[] = "Check Point's <b>Application Security</b> has detected an attack and blocked it.";
static char default_uuid[] = "";
title_pos = strstr(web_response_page_format, USERCHECK_TITLE_START);
body_pos = strstr(web_response_page_format, USERCHECK_BODY_START);
uuid_pos = strstr(web_response_page_format, USERCHECK_UUID_START);
// Sets web response format and then pages.
web_response_page_parts[0].data = (u_char *)web_response_page_format;
web_response_page_parts[0].len = strlen(web_response_page_format) - strlen(title_pos);
// Sets default title.
web_response_title_size = strlen(default_title);
memcpy(web_response_title, default_title, web_response_title_size);
web_response_page_parts[1].data = (u_char *)title_pos + strlen(USERCHECK_TITLE_START);
web_response_page_parts[1].len = strlen((char *)web_response_page_parts[1].data) - strlen(body_pos);
// Sets default body.
web_response_body_size = strlen(default_body);
memcpy(web_response_body, default_body, web_response_body_size);
web_response_page_parts[2].data = (u_char *)body_pos + strlen(USERCHECK_BODY_START);
web_response_page_parts[2].len = strlen((char *)web_response_page_parts[2].data) - strlen(uuid_pos);
// Sets default uuid.
web_response_uuid_size = strlen(default_uuid);
memcpy(web_response_uuid, default_uuid, web_response_uuid_size);
web_response_page_parts[3].data = (u_char *)uuid_pos + strlen(USERCHECK_UUID_START);
web_response_page_parts[3].len = strlen((char *)web_response_page_parts[3].data);
}
int
is_inspection_required_for_source(const char *src_ip)
{
if (!isIPAddress(src_ip)) {
write_dbg(DBG_LEVEL_WARNING, "Input %s is not an IP adress", src_ip);
return -1;
}
write_dbg(DBG_LEVEL_DEBUG, "Is relevant: %s", src_ip);
return !isSkipSource(src_ip);
}
///
/// @brief Sets a new static resources path.
/// @param[in] new_path A new path to be set as the static resources path.
///
void
set_static_resources_path(const char *new_path)
{
ngx_uint_t new_path_len = strnlen(new_path, max_static_resources_path_length - 2);
ngx_int_t is_path_terminated_with_slash = new_path[new_path_len - 1] == '/';
ngx_memzero(static_resources_path, sizeof(static_resources_path));
snprintf(
static_resources_path,
is_path_terminated_with_slash ? new_path_len + 1 : new_path_len + 2,
is_path_terminated_with_slash ? "%s" : "%s/",
new_path
);
write_dbg(DBG_LEVEL_DEBUG, "Set static resources path to: %s", static_resources_path);
}
void
set_dbg_by_ctx(
char *client_ip,
char *listening_ip,
char *uri_prefix,
char *hostname,
char *method,
unsigned int listening_port)
{
int curr_match_result;
write_dbg(
DBG_LEVEL_TRACE,
"Context for debug of the current request: %s -> %s:%d, URI: %s, hostname: %s, method: %s",
client_ip,
listening_ip,
listening_port,
uri_prefix,
hostname,
method
);
curr_match_result = isDebugContext(client_ip, listening_ip, listening_port, method, hostname, uri_prefix);
write_dbg(DBG_LEVEL_TRACE, "Current debug context match result: %s", curr_match_result ? "match" : "no match");
is_ctx_match = curr_match_result;
}
void
reset_dbg_ctx()
{
is_ctx_match = 1;
}
ngx_int_t
init_general_config(const char *conf_path)
{
int new_dbg_level = DBG_LEVEL_COUNT;
if (access(conf_path, F_OK) != 0) return NGX_ERROR;
if (is_configuration_updated == NGX_OK) return NGX_OK;
// Initiate attachment using the file in the provided conf_path.
if (!initAttachmentConfig(conf_path)) {
write_dbg(DBG_LEVEL_WARNING, "Failed to load the configuration");
return NGX_ERROR;
}
new_dbg_level = getDbgLevel();
if (new_dbg_level >= DBG_LEVEL_COUNT) {
write_dbg(DBG_LEVEL_WARNING, "Illegal debug level received: %d", new_dbg_level);
is_configuration_updated = NGX_ERROR;
return NGX_ERROR;
}
write_dbg(DBG_LEVEL_DEBUG, "Setting debug level to: %d", new_dbg_level);
// Setting a new debug level.
set_cp_ngx_attachment_debug_level(new_dbg_level);
if (web_response_title_size == 0 || web_response_body_size == 0) {
write_dbg(DBG_LEVEL_DEBUG, "Setting web trigger response page");
initialize_web_response_data();
}
// Setting fail open/close.
fail_mode_verdict = isFailOpenMode() == 1 ? NGX_OK : NGX_HTTP_FORBIDDEN;
fail_open_timeout = getFailOpenTimeout();
// Setting fail wait open/close
fail_mode_hold_verdict = isFailOpenHoldMode() == 1 ? NGX_OK : NGX_HTTP_FORBIDDEN;
fail_open_hold_timeout = getFailOpenHoldTimeout();
// Setting attachment's variables.
sessions_per_minute_limit_verdict = isFailOpenOnSessionLimit() ? TRAFFIC_VERDICT_ACCEPT : TRAFFIC_VERDICT_DROP;
max_sessions_per_minute = getMaxSessionsPerMinute();
inspection_mode = getInspectionMode();
req_max_proccessing_ms_time = getReqProccessingTimeout();
res_max_proccessing_ms_time = getResProccessingTimeout();
registration_thread_timeout_msec = getRegistrationThreadTimeout();
req_header_thread_timeout_msec = getReqHeaderThreadTimeout();
req_body_thread_timeout_msec = getReqBodyThreadTimeout();
res_header_thread_timeout_msec = getResHeaderThreadTimeout();
res_body_thread_timeout_msec = getResBodyThreadTimeout();
waiting_for_verdict_thread_timeout_msec = getWaitingForVerdictThreadTimeout();
min_retries_for_verdict = getMinRetriesForVerdict();
max_retries_for_verdict = getMaxRetriesForVerdict();
body_size_trigger = getReqBodySizeTrigger();
num_of_nginx_ipc_elements = getNumOfNginxIpcElements();
keep_alive_interval_msec = (ngx_msec_t) getKeepAliveIntervalMsec();
set_static_resources_path(getStaticResourcesPath());
is_configuration_updated = NGX_OK;
write_dbg(
DBG_LEVEL_INFO,
"Successfully loaded configuration. "
"inspection mode: %d, "
"debug level: %d, "
"failure mode: %s, "
"fail mode timeout: %u msec, "
"failure wait mode: %s, "
"fail mode wait timeout: %u msec, "
"sessions per minute limit verdict: %s, "
"max sessions per minute: %u, "
"req max processing time: %u msec, "
"res max processing time: %u msec, "
"registration thread timeout: %u msec, "
"req header thread timeout: %u msec, "
"req body thread timeout: %u msec, "
"res header thread timeout: %u msec, "
"res body thread timeout: %u msec, "
"wait thread timeout: %u msec, "
"static resources path: %s, "
"num of nginx ipc elements: %u, "
"keep alive interval msec: %u msec"
"min retries for verdict: %u"
"max retries for verdict: %u"
"body size trigger for request: %u",
inspection_mode,
new_dbg_level,
(fail_mode_verdict == NGX_OK ? "fail-open" : "fail-close"),
fail_open_timeout,
(fail_mode_hold_verdict == NGX_OK ? "fail-open" : "fail-close"),
fail_open_hold_timeout,
sessions_per_minute_limit_verdict == TRAFFIC_VERDICT_ACCEPT ? "Accpet" : "Drop",
max_sessions_per_minute,
req_max_proccessing_ms_time,
res_max_proccessing_ms_time,
registration_thread_timeout_msec,
req_header_thread_timeout_msec,
req_body_thread_timeout_msec,
res_header_thread_timeout_msec,
res_body_thread_timeout_msec,
waiting_for_verdict_thread_timeout_msec,
getStaticResourcesPath(),
num_of_nginx_ipc_elements,
keep_alive_interval_msec,
min_retries_for_verdict,
max_retries_for_verdict,
body_size_trigger
);
return NGX_OK;
}
ngx_int_t
reset_attachment_config()
{
write_dbg(DBG_LEVEL_INFO, "Resetting attachment configuration");
is_configuration_updated = NGX_ERROR;
current_config_version++;
return init_general_config(SHARED_ATTACHMENT_CONF_PATH);
}
ngx_int_t
duplicate_ngx_string(ngx_str_t *null_terminated_string, ngx_str_t *original_string, ngx_pool_t *memory_pool)
{
null_terminated_string->len = original_string->len;
null_terminated_string->data = ngx_pcalloc(memory_pool, null_terminated_string->len + 1);
if (null_terminated_string->data == NULL) {
write_dbg(
DBG_LEVEL_WARNING,
"Failed to allocate memory for a copy of the string %.*s",
original_string->len,
original_string->data
);
return NGX_ERROR;
}
ngx_snprintf(null_terminated_string->data, null_terminated_string->len + 1, "%V", original_string);
return NGX_OK;
}
u_char *
reverse_strnchr(u_char *string, const u_char char_to_find, const size_t string_length)
{
u_char *curr_char;
for (curr_char = string + string_length - 1; curr_char >= string; curr_char--) {
if (*curr_char == char_to_find) return curr_char;
}
return NULL;
}
ngx_msec_t
get_keep_alive_interval_msec(void)
{
return keep_alive_interval_msec;
}
///
/// @brief Get the CPU usage time.
/// @param[in, out] total_usage_time timeval struct that's the total cpu usage's time will be saved into.
///
void
get_total_cpu_usage_time(struct timeval *total_usage_time)
{
static struct timeval last_user_usage_time = {0, 0};
static struct timeval last_kernel_usage_time = {0, 0};
struct timeval diff_user_time = {0, 0};
struct timeval diff_kernel_time = {0, 0};
struct rusage usage;
getrusage(RUSAGE_SELF, &usage);
timersub(&usage.ru_utime, &last_user_usage_time, &diff_user_time);
timersub(&usage.ru_stime, &last_kernel_usage_time, &diff_kernel_time);
timeradd(&diff_user_time, &diff_kernel_time, total_usage_time);
last_kernel_usage_time = usage.ru_stime;
last_user_usage_time = usage.ru_utime;
}
void
set_metric_cpu_usage(void)
{
static struct timeval prev_time = {0, 0};
static const unsigned int usecs_in_sec = 1000000;
struct timeval curr_time;
struct timeval diff_time = {0, 0};
struct timeval total_usage_time = {0, 0};
double total_cpu_usage_fraction = 0;
uint64_t cpu_usage_percent = 0;
curr_time = getCurrTimeFast();
timersub(&curr_time, &prev_time, &diff_time);
get_total_cpu_usage_time(&total_usage_time);
total_cpu_usage_fraction = (double)(total_usage_time.tv_sec * usecs_in_sec + total_usage_time.tv_usec) /
(diff_time.tv_sec * usecs_in_sec + diff_time.tv_usec);
prev_time = curr_time;
cpu_usage_percent = total_cpu_usage_fraction * 100;
write_dbg(
DBG_LEVEL_DEBUG,
"Updating CPU max and average metrics. Current value: %u%%. The fraction of CPU time value %f.",
cpu_usage_percent,
total_cpu_usage_fraction
);
updateMetricField(CPU_USAGE, cpu_usage_percent);
}
void
set_metric_memory_usage(void)
{
static const int max_line_length = 32;
unsigned int vm_size = 0;
unsigned int rss_size = 0;
char buff[max_line_length];
FILE *mem_status = fopen("/proc/self/status", "r");
if (mem_status == NULL) {
write_dbg(DBG_LEVEL_WARNING, "Failed to open the status file. File: /proc/self/status");
return;
}
while (fgets(buff, max_line_length, mem_status)) {
if (strstr(buff, "VmSize") != NULL) {
sscanf(buff, "VmSize: %u kB", &vm_size);
}
if (strstr(buff, "VmRSS") != NULL) {
sscanf(buff, "VmRSS: %u kB", &rss_size);
}
}
if (fclose(mem_status) != 0) {
write_dbg(DBG_LEVEL_WARNING, "Failed closing the status file. File: /proc/self/status");
}
write_dbg(
DBG_LEVEL_DEBUG,
"Updating memory max and average metrics. Current values: VM %u kB, RSS: %u kB.",
vm_size,
rss_size
);
updateMetricField(AVERAGE_VM_MEMORY_USAGE, vm_size);
updateMetricField(AVERAGE_RSS_MEMORY_USAGE, rss_size);
updateMetricField(MAX_VM_MEMORY_USAGE, vm_size);
updateMetricField(MAX_RSS_MEMORY_USAGE, rss_size);
}
void
print_buffer(ngx_buf_t *buf, int num_bytes, int _dbg_level)
{
if (_dbg_level < dbg_level || !is_ctx_match) return;
char fmt[64];
unsigned char *pos = buf->pos;
unsigned char *last = buf->last;
int i = 0;
if (num_bytes > 0) {
if (last - pos > num_bytes) {
last = pos + num_bytes;
}
} else if (num_bytes < 0) {
if (last - pos > -num_bytes) {
pos = last + num_bytes;
}
}
for (; pos + 16 < last; pos += 16)
{
write_dbg(
_dbg_level,
"%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
*pos, *(pos+1), *(pos+2), *(pos+3), *(pos+4), *(pos+5), *(pos+6), *(pos+7),
*(pos+8), *(pos+9), *(pos+10), *(pos+11), *(pos+12), *(pos+13), *(pos+14), *(pos+15)
);
}
for(; pos < last; pos++){
sprintf(fmt+i*3, "%02X ", *pos);
i++;
}
if (i > 0) {
fmt[i*3 - 1] = 0;
write_dbg(
_dbg_level,
"%s",
fmt
);
}
}
void
print_buffer_chain(ngx_chain_t *chain, char *msg, int num_bytes, int _dbg_level)
{
if (_dbg_level < dbg_level || !is_ctx_match) return;
for (ngx_chain_t *chain_elem = chain; chain_elem != NULL; chain_elem = chain_elem->next) {
write_dbg(
DBG_LEVEL_WARNING,
"%s chain elem: size: %d, is last buf: %d",
msg,
chain_elem->buf->last - chain_elem->buf->pos,
chain_elem->buf->last_buf
);
print_buffer(chain_elem->buf, num_bytes, _dbg_level);
}
}