mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2025-08-14 13:56:01 +03:00
809 lines
29 KiB
C
809 lines
29 KiB
C
/*
|
||
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
|
||
* Copyright (c) 2004-2013 Trustwave Holdings, Inc. (http://www.trustwave.com/)
|
||
*
|
||
* 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
|
||
*
|
||
* If any of the files related to licensing are missing or if you have any
|
||
* other questions related to licensing please contact Trustwave Holdings, Inc.
|
||
* directly using the email address security@modsecurity.org.
|
||
*/
|
||
|
||
#include "persist_dbm.h"
|
||
#include "apr_sdbm.h"
|
||
|
||
/**
|
||
*
|
||
*/
|
||
static apr_table_t *collection_unpack(modsec_rec *msr, const unsigned char *blob, unsigned int blob_size,
|
||
int log_vars)
|
||
{
|
||
apr_table_t *col = NULL;
|
||
unsigned int blob_offset;
|
||
|
||
col = apr_table_make(msr->mp, 32);
|
||
if (col == NULL) return NULL;
|
||
|
||
/* ENH verify the first 3 bytes (header) */
|
||
|
||
blob_offset = 3;
|
||
while (blob_offset + 1 < blob_size) {
|
||
msc_string *var = apr_pcalloc(msr->mp, sizeof(msc_string));
|
||
|
||
var->name_len = (blob[blob_offset] << 8) + blob[blob_offset + 1];
|
||
if (var->name_len == 0) {
|
||
/* Is the length a name length, or just the end of the blob? */
|
||
if (blob_offset < blob_size - 2) {
|
||
/* This should never happen as the name length
|
||
* includes the terminating NUL and should be 1 for ""
|
||
*/
|
||
if (msr->txcfg->debuglog_level >= 9) {
|
||
msr_log(msr, 9, "collection_unpack: BLOB[%d]: %s", blob_offset, log_escape_hex(msr->mp, blob + blob_offset, blob_size - blob_offset));
|
||
}
|
||
msr_log(msr, 4, "collection_unpack: Possibly corrupted database: var name length = 0 at blob offset %u-%u.", blob_offset, blob_offset + 1);
|
||
}
|
||
break;
|
||
}
|
||
else if (var->name_len > 65536) {
|
||
/* This should never happen as the length is restricted on store
|
||
* to 65536.
|
||
*/
|
||
if (msr->txcfg->debuglog_level >= 9) {
|
||
msr_log(msr, 9, "collection_unpack: BLOB[%d]: %s", blob_offset, log_escape_hex(msr->mp, blob + blob_offset, blob_size - blob_offset));
|
||
}
|
||
msr_log(msr, 4, "collection_unpack: Possibly corrupted database: var name length > 65536 (0x%04x) at blob offset %u-%u.", var->name_len, blob_offset, blob_offset + 1);
|
||
break;
|
||
}
|
||
|
||
blob_offset += 2;
|
||
if (blob_offset + var->name_len > blob_size) return NULL;
|
||
var->name = apr_pstrmemdup(msr->mp, (const char *)blob + blob_offset, var->name_len - 1);
|
||
blob_offset += var->name_len;
|
||
var->name_len--;
|
||
|
||
var->value_len = (blob[blob_offset] << 8) + blob[blob_offset + 1];
|
||
blob_offset += 2;
|
||
|
||
if (blob_offset + var->value_len > blob_size) return NULL;
|
||
var->value = apr_pstrmemdup(msr->mp, (const char *)blob + blob_offset, var->value_len - 1);
|
||
blob_offset += var->value_len;
|
||
var->value_len--;
|
||
|
||
if (log_vars && (msr->txcfg->debuglog_level >= 9)) {
|
||
msr_log(msr, 9, "collection_unpack: Read variable: name \"%s\", value \"%s\".",
|
||
log_escape_ex(msr->mp, var->name, var->name_len),
|
||
log_escape_ex(msr->mp, var->value, var->value_len));
|
||
}
|
||
|
||
apr_table_addn(col, var->name, (void *)var);
|
||
}
|
||
|
||
return col;
|
||
}
|
||
|
||
/**
|
||
*
|
||
*/
|
||
static apr_table_t *collection_retrieve_ex(apr_sdbm_t *existing_dbm, modsec_rec *msr, const char *col_name,
|
||
const char *col_key, int col_key_len)
|
||
{
|
||
char *dbm_filename = NULL;
|
||
apr_status_t rc;
|
||
apr_sdbm_datum_t key;
|
||
apr_sdbm_datum_t *value = NULL;
|
||
apr_sdbm_t *dbm = NULL;
|
||
apr_table_t *col = NULL;
|
||
const apr_array_header_t *arr;
|
||
apr_table_entry_t *te;
|
||
int expired = 0;
|
||
int i;
|
||
char *userinfo = get_username(msr->mp);
|
||
|
||
if (msr->txcfg->data_dir == NULL) {
|
||
msr_log(msr, 1, "collection_retrieve_ex: Unable to retrieve collection (name \"%s\", key \"%s\"). Use "
|
||
"SecDataDir to define data directory first.", log_escape(msr->mp, col_name),
|
||
log_escape_ex(msr->mp, col_key, col_key_len));
|
||
goto cleanup;
|
||
}
|
||
|
||
dbm_filename = apr_pstrcat(msr->mp, msr->txcfg->data_dir, "/", userinfo, "-", col_name, NULL);
|
||
|
||
if (msr->txcfg->debuglog_level >= 9) {
|
||
msr_log(msr, 9, "collection_retrieve_ex: collection_retrieve_ex: Retrieving collection (name \"%s\", filename \"%s\")",log_escape(msr->mp, col_name),
|
||
log_escape(msr->mp, dbm_filename));
|
||
}
|
||
|
||
key.dptr = (char *)col_key;
|
||
key.dsize = col_key_len + 1;
|
||
|
||
if (existing_dbm == NULL) {
|
||
#ifdef GLOBAL_COLLECTION_LOCK
|
||
rc = apr_global_mutex_lock(msr->modsecurity->dbm_lock);
|
||
if (rc != APR_SUCCESS) {
|
||
msr_log(msr, 1, "collection_retrieve_ex: Failed to lock proc mutex: %s",
|
||
get_apr_error(msr->mp, rc));
|
||
goto cleanup;
|
||
}
|
||
#endif
|
||
rc = apr_sdbm_open(&dbm, dbm_filename, APR_READ | APR_SHARELOCK,
|
||
CREATEMODE, msr->mp);
|
||
if (rc != APR_SUCCESS) {
|
||
dbm = NULL;
|
||
#ifdef GLOBAL_COLLECTION_LOCK
|
||
apr_global_mutex_unlock(msr->modsecurity->dbm_lock);
|
||
#endif
|
||
goto cleanup;
|
||
}
|
||
}
|
||
else {
|
||
dbm = existing_dbm;
|
||
}
|
||
|
||
value = (apr_sdbm_datum_t *)apr_pcalloc(msr->mp, sizeof(apr_sdbm_datum_t));
|
||
rc = apr_sdbm_fetch(dbm, value, key);
|
||
if (rc != APR_SUCCESS) {
|
||
msr_log(msr, 1, "collection_retrieve_ex: Failed to read from DBM file \"%s\": %s", log_escape(msr->mp,
|
||
dbm_filename), get_apr_error(msr->mp, rc));
|
||
goto cleanup;
|
||
}
|
||
|
||
if (value->dptr == NULL) { /* Key not found in DBM file. */
|
||
goto cleanup;
|
||
}
|
||
|
||
/* ENH Need expiration (and perhaps other metadata) accessible in blob
|
||
* form to determine if converting to a table is needed. This will
|
||
* save some cycles.
|
||
*/
|
||
|
||
/* Transform raw data into a table. */
|
||
col = collection_unpack(msr, (const unsigned char *)value->dptr, value->dsize, 1);
|
||
if (col == NULL) {
|
||
goto cleanup;
|
||
}
|
||
|
||
/* Close after "value" used from fetch or memory may be overwritten. */
|
||
if (existing_dbm == NULL) {
|
||
apr_sdbm_close(dbm);
|
||
#ifdef GLOBAL_COLLECTION_LOCK
|
||
apr_global_mutex_unlock(msr->modsecurity->dbm_lock);
|
||
#endif
|
||
dbm = NULL;
|
||
}
|
||
|
||
/* Remove expired variables. */
|
||
do {
|
||
arr = apr_table_elts(col);
|
||
te = (apr_table_entry_t *)arr->elts;
|
||
for (i = 0; i < arr->nelts; i++) {
|
||
if (strncmp(te[i].key, "__expire_", 9) == 0) {
|
||
msc_string *var = (msc_string *)te[i].val;
|
||
int expiry_time = atoi(var->value);
|
||
|
||
if (expiry_time <= apr_time_sec(msr->request_time)) {
|
||
char *key_to_expire = te[i].key;
|
||
|
||
/* Done early if the col expired */
|
||
if (strcmp(key_to_expire, "__expire_KEY") == 0) {
|
||
expired = 1;
|
||
}
|
||
|
||
if (msr->txcfg->debuglog_level >= 9) {
|
||
msr_log(msr, 9, "collection_retrieve_ex: Removing key \"%s\" from collection.", key_to_expire + 9);
|
||
msr_log(msr, 9, "collection_retrieve_ex: Removing key \"%s\" from collection.", key_to_expire);
|
||
}
|
||
|
||
apr_table_unset(col, key_to_expire + 9);
|
||
apr_table_unset(col, key_to_expire);
|
||
|
||
if (msr->txcfg->debuglog_level >= 4) {
|
||
msr_log(msr, 4, "collection_retrieve_ex: Removed expired variable \"%s\".", key_to_expire + 9);
|
||
}
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} while(!expired && (i != arr->nelts));
|
||
|
||
/* Delete the collection if the variable "KEY" does not exist.
|
||
*
|
||
* ENH It would probably be more efficient to hold the DBM
|
||
* open until determined if it needs deleted than to open a second
|
||
* time.
|
||
*/
|
||
if (apr_table_get(col, "KEY") == NULL) {
|
||
if (existing_dbm == NULL) {
|
||
#ifdef GLOBAL_COLLECTION_LOCK
|
||
rc = apr_global_mutex_lock(msr->modsecurity->dbm_lock);
|
||
if (rc != APR_SUCCESS) {
|
||
msr_log(msr, 1, "collection_retrieve_ex: Failed to lock proc mutex: %s",
|
||
get_apr_error(msr->mp, rc));
|
||
goto cleanup;
|
||
}
|
||
#endif
|
||
rc = apr_sdbm_open(&dbm, dbm_filename, APR_CREATE | APR_WRITE | APR_SHARELOCK,
|
||
CREATEMODE, msr->mp);
|
||
if (rc != APR_SUCCESS) {
|
||
msr_log(msr, 1, "collection_retrieve_ex: Failed to access DBM file \"%s\": %s",
|
||
log_escape(msr->mp, dbm_filename), get_apr_error(msr->mp, rc));
|
||
dbm = NULL;
|
||
#ifdef GLOBAL_COLLECTION_LOCK
|
||
apr_global_mutex_unlock(msr->modsecurity->dbm_lock);
|
||
#endif
|
||
goto cleanup;
|
||
}
|
||
}
|
||
else {
|
||
dbm = existing_dbm;
|
||
}
|
||
|
||
rc = apr_sdbm_delete(dbm, key);
|
||
if (rc != APR_SUCCESS) {
|
||
#ifdef LOG_NO_COLL_DELET_PB
|
||
if (msr->txcfg->debuglog_level >= 9)
|
||
#endif
|
||
msr_log(msr, 1, "collection_retrieve_ex: Failed deleting collection (name \"%s\", "
|
||
"key \"%s\"): %s", log_escape(msr->mp, col_name),
|
||
log_escape_ex(msr->mp, col_key, col_key_len), get_apr_error(msr->mp, rc));
|
||
msr->msc_sdbm_delete_error = 1;
|
||
goto cleanup;
|
||
}
|
||
|
||
|
||
if (existing_dbm == NULL) {
|
||
apr_sdbm_close(dbm);
|
||
#ifdef GLOBAL_COLLECTION_LOCK
|
||
apr_global_mutex_unlock(msr->modsecurity->dbm_lock);
|
||
#endif
|
||
dbm = NULL;
|
||
}
|
||
|
||
if (expired && (msr->txcfg->debuglog_level >= 9)) {
|
||
msr_log(msr, 9, "collection_retrieve_ex: Collection expired (name \"%s\", key \"%s\").", col_name,
|
||
log_escape_ex(msr->mp, col_key, col_key_len));
|
||
}
|
||
if (msr->txcfg->debuglog_level >= 4) {
|
||
msr_log(msr, 4, "collection_retrieve_ex: Deleted collection (name \"%s\", key \"%s\").",
|
||
log_escape(msr->mp, col_name), log_escape_ex(msr->mp, col_key, col_key_len));
|
||
}
|
||
goto cleanup;
|
||
}
|
||
|
||
/* Update UPDATE_RATE */
|
||
{
|
||
msc_string *var;
|
||
int create_time, counter;
|
||
|
||
var = (msc_string *)apr_table_get(col, "CREATE_TIME");
|
||
if (var == NULL) {
|
||
/* Error. */
|
||
} else {
|
||
create_time = atoi(var->value);
|
||
var = (msc_string *)apr_table_get(col, "UPDATE_COUNTER");
|
||
if (var == NULL) {
|
||
/* Error. */
|
||
} else {
|
||
apr_time_t td;
|
||
counter = atoi(var->value);
|
||
|
||
/* UPDATE_RATE is removed on store, so add it back here */
|
||
var = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));
|
||
var->name = "UPDATE_RATE";
|
||
var->name_len = strlen(var->name);
|
||
apr_table_setn(col, var->name, (void *)var);
|
||
|
||
/* NOTE: No rate if there has been no time elapsed */
|
||
td = (apr_time_sec(apr_time_now()) - create_time);
|
||
if (td == 0) {
|
||
var->value = apr_psprintf(msr->mp, "%d", 0);
|
||
}
|
||
else {
|
||
var->value = apr_psprintf(msr->mp, "%" APR_TIME_T_FMT,
|
||
(apr_time_t)((60 * counter)/td));
|
||
}
|
||
var->value_len = strlen(var->value);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (msr->txcfg->debuglog_level >= 4) {
|
||
msr_log(msr, 4, "collection_retrieve_ex: Retrieved collection (name \"%s\", key \"%s\").",
|
||
log_escape(msr->mp, col_name), log_escape_ex(msr->mp, col_key, col_key_len));
|
||
}
|
||
|
||
if ((existing_dbm == NULL) && dbm) {
|
||
/* Should not ever get here */
|
||
msr_log(msr, 1, "collection_retrieve_ex: Internal Error: Collection remained open (name \"%s\", key \"%s\").",
|
||
log_escape(msr->mp, col_name), log_escape_ex(msr->mp, col_key, col_key_len));
|
||
|
||
apr_sdbm_close(dbm);
|
||
#ifdef GLOBAL_COLLECTION_LOCK
|
||
apr_global_mutex_unlock(msr->modsecurity->dbm_lock);
|
||
#endif
|
||
}
|
||
|
||
return col;
|
||
|
||
cleanup:
|
||
|
||
if ((existing_dbm == NULL) && dbm) {
|
||
apr_sdbm_close(dbm);
|
||
#ifdef GLOBAL_COLLECTION_LOCK
|
||
apr_global_mutex_unlock(msr->modsecurity->dbm_lock);
|
||
ÿ |