// 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 #include #include #include #include #include #include #include #include #include #include "nginx_attachment_util.h" #include "ngx_cp_initializer.h" #include "nginx_attachment_common.h" #include "ngx_cp_metric.h" #define USERCHECK_TITLE_START "" #define USERCHECK_BODY_START "" #define USERCHECK_UUID_START "" #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 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; 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, ¤t_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); memcpy(web_response_uuid, "Incident Id: ", strlen("Incident Id: ")); memcpy(web_response_uuid + strlen("Incident Id: "), uuid->data, web_response_uuid_size); } 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); memcpy(web_response_uuid, uuid->data, web_response_uuid_size); } 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; 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, (u_char *)web_response_uuid }; if (web_response_title_size == 0 || web_response_body_size == 0) return NGX_ERROR_ERR; 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 += 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 + strlen("Incident Id: "); } ngx_uint_t get_web_response_uuid_size(void) { return web_response_uuid_size - strlen("Incident Id: "); } 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, " ", 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 Application Security 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_ERROR; fail_open_timeout = getFailOpenTimeout(); // Setting fail wait open/close fail_mode_hold_verdict = isFailOpenHoldMode() == 1 ? NGX_OK : NGX_ERROR; 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(); 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", 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 ); 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); } }