mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2026-01-14 07:27:09 +03:00
ModSecurty-2.5.13-dev2
This commit is contained in:
@@ -630,6 +630,12 @@ static const char *add_rule(cmd_parms *cmd, directory_config *dcfg, int type,
|
||||
"be specified by chain starter rules.");
|
||||
}
|
||||
|
||||
/* Must NOT specify a skipafter action. */
|
||||
if (rule->actionset->skip_after != NOT_SET_P) {
|
||||
return apr_psprintf(cmd->pool, "ModSecurity: SkipAfter actions can only "
|
||||
"be specified by chain starter rules.");
|
||||
}
|
||||
|
||||
/* Must NOT specify a phase. */
|
||||
if (rule->actionset->phase != NOT_SET) {
|
||||
return apr_psprintf(cmd->pool, "ModSecurity: Execution phases can only be "
|
||||
|
||||
@@ -131,6 +131,18 @@ int modsecurity_init(msc_engine *msce, apr_pool_t *mp) {
|
||||
}
|
||||
#endif
|
||||
|
||||
rc = apr_global_mutex_create(&msce->geo_lock, NULL, APR_LOCK_DEFAULT, mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef __SET_MUTEX_PERMS
|
||||
rc = unixd_set_global_mutex_perms(msce->geo_lock);
|
||||
if (rc != APR_SUCCESS) {
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -147,6 +159,14 @@ void modsecurity_child_init(msc_engine *msce) {
|
||||
// ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init auditlog mutex");
|
||||
}
|
||||
}
|
||||
|
||||
if (msce->geo_lock != NULL) {
|
||||
apr_status_t rc = apr_global_mutex_child_init(&msce->geo_lock, NULL, msce->mp);
|
||||
if (rc != APR_SUCCESS) {
|
||||
// ap_log_error(APLOG_MARK, APLOG_ERR, rs, s, "Failed to child-init geo mutex");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -490,6 +490,7 @@ struct error_message {
|
||||
struct msc_engine {
|
||||
apr_pool_t *mp;
|
||||
apr_global_mutex_t *auditlog_lock;
|
||||
apr_global_mutex_t *geo_lock;
|
||||
msre_engine *msre;
|
||||
unsigned int processing_mode;
|
||||
};
|
||||
|
||||
@@ -287,6 +287,7 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro
|
||||
apr_size_t nbytes;
|
||||
unsigned int rec_val = 0;
|
||||
apr_off_t seekto = 0;
|
||||
apr_status_t ret;
|
||||
int rc;
|
||||
int country = 0;
|
||||
int level;
|
||||
@@ -332,6 +333,12 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro
|
||||
msr_log(msr, 9, "GEO: Using address \"%s\" (0x%08lx).", targetip, ipnum);
|
||||
}
|
||||
|
||||
ret = apr_global_mutex_lock(msr->modsecurity->geo_lock);
|
||||
if (ret != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s",
|
||||
get_apr_error(msr->mp, ret));
|
||||
}
|
||||
|
||||
for (level = 31; level >= 0; level--) {
|
||||
/* Read the record */
|
||||
seekto = 2 * reclen * rec_val;
|
||||
@@ -365,6 +372,13 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro
|
||||
if ((country <= 0) || (country > GEO_COUNTRY_LAST)) {
|
||||
*error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\" (country %d).", log_escape(msr->mp, target), country);
|
||||
msr_log(msr, 4, "%s", *error_msg);
|
||||
|
||||
ret = apr_global_mutex_unlock(msr->modsecurity->geo_lock);
|
||||
if (ret != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s",
|
||||
get_apr_error(msr->mp, ret));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -389,6 +403,13 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro
|
||||
if ((country <= 0) || (country > GEO_COUNTRY_LAST)) {
|
||||
*error_msg = apr_psprintf(msr->mp, "No geo data for \"%s\" (country %d).", log_escape(msr->mp, target), country);
|
||||
msr_log(msr, 4, "%s", *error_msg);
|
||||
|
||||
ret = apr_global_mutex_unlock(msr->modsecurity->geo_lock);
|
||||
if (ret != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s",
|
||||
get_apr_error(msr->mp, ret));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
@@ -477,6 +498,13 @@ int geo_lookup(modsec_rec *msr, geo_rec *georec, const char *target, char **erro
|
||||
}
|
||||
|
||||
*error_msg = apr_psprintf(msr->mp, "Geo lookup for \"%s\" succeeded.", log_escape(msr->mp, target));
|
||||
|
||||
ret = apr_global_mutex_unlock(msr->modsecurity->geo_lock);
|
||||
if (ret != APR_SUCCESS) {
|
||||
msr_log(msr, 1, "Geo Lookup: Failed to lock proc mutex: %s",
|
||||
get_apr_error(msr->mp, ret));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,12 @@ typedef struct {
|
||||
int index;
|
||||
} msc_lua_dumpr_t;
|
||||
|
||||
typedef struct {
|
||||
size_t buffer_size;
|
||||
char data[1];
|
||||
} VarBuffer;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -337,10 +343,83 @@ static int l_getvars(lua_State *L) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* \brief Function used to get user input via script
|
||||
*
|
||||
* \param L Pointer to Lua state
|
||||
* \param size buffer size
|
||||
*
|
||||
* \retval NULL On failure
|
||||
* \retval data On Success
|
||||
*/
|
||||
static char *get_varbuffer (lua_State *L, size_t size) {
|
||||
VarBuffer *var_chunk = NULL;
|
||||
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, "_buffer");
|
||||
var_chunk = (VarBuffer *) lua_touserdata(L, -1);
|
||||
|
||||
if (var_chunk == NULL || var_chunk->buffer_size < size) {
|
||||
var_chunk = (VarBuffer *) lua_newuserdata(L, sizeof(VarBuffer) + size);
|
||||
var_chunk->buffer_size = size;
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, "_buffer");
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
return var_chunk->data;
|
||||
}
|
||||
|
||||
/*
|
||||
* \brief New setvar function for Lua API. Users can put back
|
||||
* data in modsecurity core via new variables
|
||||
*
|
||||
* \param L Pointer to Lua state
|
||||
*
|
||||
* \retval -1 On failure
|
||||
* \retval 0 On Collection failure
|
||||
* \retval 1 On Success
|
||||
*/
|
||||
static int l_setvar(lua_State *L) {
|
||||
modsec_rec *msr = NULL;
|
||||
msre_rule *rule = NULL;
|
||||
char *var_value = NULL;
|
||||
char *var_name = NULL;
|
||||
size_t size;
|
||||
int nargs = lua_gettop(L);
|
||||
|
||||
lua_getglobal(L, "__msr");
|
||||
msr = (modsec_rec *)lua_topointer(L, -1);
|
||||
|
||||
lua_getglobal(L, "__rule");
|
||||
rule = (msre_rule *)lua_topointer(L, -1);
|
||||
|
||||
if(nargs != 2) {
|
||||
msr_log(msr, 8, "m.setvar: Failed m.setvar funtion must has 2 arguments");
|
||||
return -1;
|
||||
}
|
||||
|
||||
//const char *var_value = luaL_checkstring (L, 2);
|
||||
//const char *var_name = luaL_checkstring (L, 1);
|
||||
|
||||
const char *setvar_value = lua_tolstring(L, 2, &size);
|
||||
var_value = get_varbuffer(L, size);
|
||||
|
||||
if(var_value != NULL && setvar_value != NULL)
|
||||
strncpy(var_value, setvar_value, size + 1);
|
||||
|
||||
const char *setvar_name = lua_tolstring(L, 1, &size);
|
||||
var_name = get_varbuffer(L, size);
|
||||
|
||||
if(var_name != NULL && setvar_name != NULL)
|
||||
strncpy(var_name, setvar_name, size + 1);
|
||||
|
||||
return msre_action_setvar_execute(msr,msr->msc_rule_mptmp,rule,var_name,var_value);
|
||||
}
|
||||
|
||||
static const struct luaL_Reg mylib[] = {
|
||||
{ "log", l_log },
|
||||
{ "getvar", l_getvar },
|
||||
{ "getvars", l_getvars },
|
||||
{ "setvar", l_setvar },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ extern DSOLOCAL modsec_build_type_rec modsec_build_type[];
|
||||
#define MODSEC_VERSION_MINOR "5"
|
||||
#define MODSEC_VERSION_MAINT "13"
|
||||
#define MODSEC_VERSION_TYPE "dev"
|
||||
#define MODSEC_VERSION_RELEASE "1"
|
||||
#define MODSEC_VERSION_RELEASE "2"
|
||||
|
||||
#define MODSEC_VERSION_SUFFIX MODSEC_VERSION_TYPE MODSEC_VERSION_RELEASE
|
||||
|
||||
|
||||
20
apache2/re.c
20
apache2/re.c
@@ -843,7 +843,7 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr)
|
||||
msre_rule **rules;
|
||||
apr_status_t rc;
|
||||
const char *skip_after = NULL;
|
||||
int i, mode, skip;
|
||||
int i, mode, skip, skipped;
|
||||
|
||||
/* First determine which set of rules we need to use. */
|
||||
switch (msr->phase) {
|
||||
@@ -873,6 +873,7 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr)
|
||||
|
||||
/* Loop through the rules in the selected set. */
|
||||
skip = 0;
|
||||
skipped = 0;
|
||||
mode = NEXT_RULE;
|
||||
rules = (msre_rule **)arr->elts;
|
||||
for (i = 0; i < arr->nelts; i++) {
|
||||
@@ -900,6 +901,18 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
msre_rule *last_rule = rules[i-1];
|
||||
|
||||
if(last_rule->actionset->is_chained) {
|
||||
mode = NEXT_RULE;
|
||||
skipped = 1;
|
||||
--i;
|
||||
} else {
|
||||
mode = SKIP_RULES;
|
||||
skipped = 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
@@ -1074,6 +1087,11 @@ apr_status_t msre_ruleset_process_phase(msre_ruleset *ruleset, modsec_rec *msr)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(skipped == 1) {
|
||||
mode = SKIP_RULES;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We had a match but the transaction was not
|
||||
* intercepted. In that case we proceed with the
|
||||
* next rule...
|
||||
|
||||
@@ -49,6 +49,8 @@ typedef struct msre_cache_rec msre_cache_rec;
|
||||
|
||||
/* Actions, variables, functions and operator functions */
|
||||
|
||||
apr_status_t DSOLOCAL msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptmp, msre_rule *rule, char *var_name, char *var_value);
|
||||
|
||||
apr_status_t DSOLOCAL collection_original_setvar(modsec_rec *msr, const char *col_name, const msc_string *orig_var);
|
||||
|
||||
int DSOLOCAL expand_macros(modsec_rec *msr, msc_string *var, msre_rule *rule, apr_pool_t *mptmp);
|
||||
|
||||
@@ -1168,35 +1168,19 @@ static apr_status_t msre_action_setenv_execute(modsec_rec *msr, apr_pool_t *mptm
|
||||
}
|
||||
|
||||
/* setvar */
|
||||
static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptmp,
|
||||
msre_rule *rule, msre_action *action)
|
||||
apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptmp,
|
||||
msre_rule *rule, char *var_name, char *var_value)
|
||||
{
|
||||
char *data = apr_pstrdup(mptmp, action->param);
|
||||
char *col_name = NULL, *var_name = NULL, *var_value = NULL;
|
||||
char *col_name = NULL;
|
||||
char *s = NULL;
|
||||
apr_table_t *target_col = NULL;
|
||||
int is_negated = 0;
|
||||
msc_string *var = NULL;
|
||||
|
||||
/* Extract the name and the value. */
|
||||
/* IMP1 We have a function for this now, parse_name_eq_value? */
|
||||
s = strstr(data, "=");
|
||||
if (s == NULL) {
|
||||
var_name = data;
|
||||
var_value = "1";
|
||||
} else {
|
||||
var_name = data;
|
||||
var_value = s + 1;
|
||||
*s = '\0';
|
||||
|
||||
while ((*var_value != '\0')&&(isspace(*var_value))) var_value++;
|
||||
}
|
||||
|
||||
if (msr->txcfg->debuglog_level >= 9) {
|
||||
msr_log(msr, 9, "Setting variable: %s=%s", var_name, var_value);
|
||||
}
|
||||
|
||||
|
||||
/* Expand and escape any macros in the name */
|
||||
var = apr_palloc(msr->mp, sizeof(msc_string));
|
||||
if (var == NULL) {
|
||||
@@ -1335,6 +1319,43 @@ static apr_status_t msre_action_setvar_execute(modsec_rec *msr, apr_pool_t *mptm
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* \brief Parse fuction for setvar input
|
||||
*
|
||||
* \param msr Pointer to the engine
|
||||
* \param mptmp Pointer to the pool
|
||||
* \param rule Pointer to rule struct
|
||||
* \param action input data
|
||||
*
|
||||
* \retval -1 On failure
|
||||
* \retval 0 On Collection failure
|
||||
* \retval 1 On Success
|
||||
*/
|
||||
static apr_status_t msre_action_setvar_parse(modsec_rec *msr, apr_pool_t *mptmp,
|
||||
msre_rule *rule, msre_action *action)
|
||||
{
|
||||
char *data = apr_pstrdup(mptmp, action->param);
|
||||
char *var_name = NULL, *var_value = NULL;
|
||||
char *s = NULL;
|
||||
|
||||
/* Extract the name and the value. */
|
||||
/* IMP1 We have a function for this now, parse_name_eq_value? */
|
||||
s = strstr(data, "=");
|
||||
if (s == NULL) {
|
||||
var_name = data;
|
||||
var_value = "1";
|
||||
} else {
|
||||
var_name = data;
|
||||
var_value = s + 1;
|
||||
*s = '\0';
|
||||
|
||||
while ((*var_value != '\0')&&(isspace(*var_value))) var_value++;
|
||||
}
|
||||
|
||||
return msre_action_setvar_execute(msr,mptmp,rule,var_name,var_value);
|
||||
}
|
||||
|
||||
|
||||
/* expirevar */
|
||||
static apr_status_t msre_action_expirevar_execute(modsec_rec *msr, apr_pool_t *mptmp,
|
||||
msre_rule *rule, msre_action *action)
|
||||
@@ -2264,7 +2285,7 @@ void msre_engine_register_default_actions(msre_engine *engine) {
|
||||
ACTION_CGROUP_NONE,
|
||||
NULL,
|
||||
NULL,
|
||||
msre_action_setvar_execute
|
||||
msre_action_setvar_parse
|
||||
);
|
||||
|
||||
/* expirevar */
|
||||
|
||||
@@ -153,9 +153,13 @@ static int msre_op_rx_execute(modsec_rec *msr, msre_rule *rule, msre_var *var, c
|
||||
apr_table_setn(msr->tx_vars, s->name, (void *)s);
|
||||
|
||||
*error_msg = apr_psprintf(msr->mp,
|
||||
"Rule execution error - "
|
||||
"Rule %pp [id \"%s\"][file \"%s\"][line \"%d\"] - "
|
||||
"Execution error - "
|
||||
"PCRE limits exceeded (%d): %s",
|
||||
rc, my_error_msg);
|
||||
rule,((rule->actionset != NULL)&&(rule->actionset->id != NULL)) ? rule->actionset->id : "-",
|
||||
rule->filename != NULL ? rule->filename : "-",
|
||||
rule->line_num,rc, my_error_msg);
|
||||
|
||||
|
||||
msr_log(msr, 3, "%s.", *error_msg);
|
||||
|
||||
|
||||
@@ -25,6 +25,30 @@
|
||||
#include "re.h"
|
||||
#include "msc_util.h"
|
||||
|
||||
/* Base64 tables used in decodeBase64Ext */
|
||||
|
||||
static const char b64_pad = '=';
|
||||
|
||||
static const short b64_reverse_t[256] = {
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -2, -1, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,
|
||||
-2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -2, -2, -2, -2, -2,
|
||||
-2, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
|
||||
-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2
|
||||
};
|
||||
|
||||
|
||||
/* lowercase */
|
||||
|
||||
static int msre_fn_lowercase_execute(apr_pool_t *mptmp, unsigned char *input,
|
||||
@@ -572,6 +596,95 @@ static int msre_fn_parityOdd7bit_execute(apr_pool_t *mptmp, unsigned char *input
|
||||
return changed;
|
||||
}
|
||||
|
||||
/*
|
||||
* \brief Decode Base64 data with special chars
|
||||
*
|
||||
* \param plain_text Pointer to plain text data
|
||||
* \param input Pointer to input data
|
||||
* \param input_len Input data length
|
||||
*
|
||||
* \retval 0 On failure
|
||||
* \retval string length On Success
|
||||
*/
|
||||
int decode_base64_ext(char *plain_text, const char *input, int input_len)
|
||||
{
|
||||
const char *encoded = input;
|
||||
int i = 0, j = 0, k = 0;
|
||||
int ch = 0;
|
||||
|
||||
while ((ch = *encoded++) != '\0' && input_len-- > 0) {
|
||||
if (ch == b64_pad) {
|
||||
if (*encoded != '=' && (i % 4) == 1) {
|
||||
return 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ch = b64_reverse_t[ch];
|
||||
if (ch < 0 || ch == -1) {
|
||||
continue;
|
||||
} else if (ch == -2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch(i % 4) {
|
||||
case 0:
|
||||
plain_text[j] = ch << 2;
|
||||
break;
|
||||
case 1:
|
||||
plain_text[j++] |= ch >> 4;
|
||||
plain_text[j] = (ch & 0x0f) << 4;
|
||||
break;
|
||||
case 2:
|
||||
plain_text[j++] |= ch >>2;
|
||||
plain_text[j] = (ch & 0x03) << 6;
|
||||
break;
|
||||
case 3:
|
||||
plain_text[j++] |= ch;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
k = j;
|
||||
if (ch == b64_pad) {
|
||||
switch(i % 4) {
|
||||
case 1:
|
||||
return 0;
|
||||
case 2:
|
||||
k++;
|
||||
case 3:
|
||||
plain_text[k] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
plain_text[j] = '\0';
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
/*
|
||||
* \brief Base64 transformation function based on RFC2045
|
||||
*
|
||||
* \param mptmp Pointer to resource poil
|
||||
* \param input Pointer to input data
|
||||
* \param input_len Input data length
|
||||
* \param rval Pointer to decoded buffer
|
||||
* \param rval_len Decoded buffer length
|
||||
*
|
||||
* \retval 0 On failure
|
||||
* \retval 1 On Success
|
||||
*/
|
||||
static int msre_fn_decodeBase64Ext_execute(apr_pool_t *mptmp, unsigned char *input, long int input_len, char **rval, long int *rval_len)
|
||||
{
|
||||
*rval_len = input_len;
|
||||
*rval = apr_palloc(mptmp, *rval_len);
|
||||
*rval_len = decode_base64_ext(*rval, (const char *)input, input_len);
|
||||
|
||||
return *rval_len ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
@@ -775,4 +888,11 @@ void msre_engine_register_default_tfns(msre_engine *engine) {
|
||||
"urlEncode",
|
||||
msre_fn_urlEncode_execute
|
||||
);
|
||||
|
||||
/* decodeBase64Ext */
|
||||
msre_engine_tfn_register(engine,
|
||||
"decodeBase64Ext",
|
||||
msre_fn_decodeBase64Ext_execute
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user