mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2026-01-13 15:07:10 +03:00
Backport regression suite to 2.5.
This commit is contained in:
@@ -76,7 +76,7 @@ clean: clean-extras
|
|||||||
@rm -rf *.la *.lo *.o *.slo .libs msc_test msc-test-debug.log
|
@rm -rf *.la *.lo *.o *.slo .libs msc_test msc-test-debug.log
|
||||||
|
|
||||||
maintainer-clean: clean
|
maintainer-clean: clean
|
||||||
@rm -rf Makefile mlogc-src/Makefile t/run-tests.pl config config.log config.status configure mod_security2_config.h ../tools/*.pl autoscan.log configure.scan build/libtool.m4 build/config.guess build/config.sub build/ltmain.sh build/apxs-wrapper
|
@rm -rf Makefile mlogc-src/Makefile t/run-unit-tests.pl t/run-regression-tests.pl t/gen_rx-pm.pl t/csv_rx-pm.pl t/run-tests.pl t/regression/server_root/conf/httpd.conf t/regression/server_root/conf/config_*.t_*.conf config config.log config.status configure mod_security2_config.h ../tools/*.pl autoscan.log configure.scan build/libtool.m4 build/config.guess build/config.sub build/ltmain.sh build/apxs-wrapper
|
||||||
|
|
||||||
distclean: maintainer-clean
|
distclean: maintainer-clean
|
||||||
|
|
||||||
@@ -117,14 +117,17 @@ msc_test.lo: msc_test.c
|
|||||||
$(LIBTOOL) --mode=compile $(CC) $(APXS_INCLUDES) $(APXS_CFLAGS) $(EXTRA_CFLAGS) $(MODSEC_EXTRA_CFLAGS) $(CPPFLAGS) $(APR_CFLAGS) $(APU_CFLAGS) -o msc_test.lo -c msc_test.c
|
$(LIBTOOL) --mode=compile $(CC) $(APXS_INCLUDES) $(APXS_CFLAGS) $(EXTRA_CFLAGS) $(MODSEC_EXTRA_CFLAGS) $(CPPFLAGS) $(APR_CFLAGS) $(APU_CFLAGS) -o msc_test.lo -c msc_test.c
|
||||||
|
|
||||||
msc_test: $(TESTOBJS) $(MOD_SECURITY2_H}) msc_test.lo
|
msc_test: $(TESTOBJS) $(MOD_SECURITY2_H}) msc_test.lo
|
||||||
@objs=""; \
|
objs=""; \
|
||||||
for f in $(MSC_TEST); do \
|
for f in $(MSC_TEST); do \
|
||||||
objs="$$objs $$f.lo"; \
|
objs="$$objs $$f.lo"; \
|
||||||
done; \
|
done; \
|
||||||
$(LIBTOOL) --mode=link $(CC) $$objs -o msc_test msc_test.lo $(LDFLAGS) $(LIBS) $(APR_LINK_LD) $(APU_LINK_LD)
|
$(LIBTOOL) --mode=link $(CC) $$objs -o msc_test msc_test.lo $(LDFLAGS) $(LIBS) $(APR_LINK_LD) $(APU_LINK_LD)
|
||||||
|
|
||||||
test: t/run-tests.pl msc_test
|
test: t/run-unit-tests.pl msc_test
|
||||||
@rm -f msc-test-debug.log; \
|
@rm -f msc-test-debug.log; \
|
||||||
$(PERL) t/run-tests.pl
|
$(PERL) t/run-unit-tests.pl
|
||||||
|
|
||||||
.PHONY: all install clean-extras clean maintainer-clean distclean install-mods test
|
test-regression: t/run-regression-tests.pl
|
||||||
|
@$(PERL) t/run-regression-tests.pl
|
||||||
|
|
||||||
|
.PHONY: all install clean-extras clean maintainer-clean distclean install-mods test test-regression
|
||||||
|
|||||||
@@ -39,6 +39,25 @@ AC_FUNC_MALLOC
|
|||||||
AC_FUNC_MEMCMP
|
AC_FUNC_MEMCMP
|
||||||
AC_CHECK_FUNCS([atexit fchmod getcwd memset strcasecmp strchr strdup strerror strncasecmp strrchr strstr strtol])
|
AC_CHECK_FUNCS([atexit fchmod getcwd memset strcasecmp strchr strdup strerror strncasecmp strrchr strstr strtol])
|
||||||
|
|
||||||
|
# Some directories
|
||||||
|
MSC_BASE_DIR=`pwd`
|
||||||
|
MSC_PKGBASE_DIR="$MSC_BASE_DIR/.."
|
||||||
|
MSC_TEST_DIR="$MSC_BASE_DIR/t"
|
||||||
|
MSC_REGRESSION_DIR="$MSC_TEST_DIR/regression"
|
||||||
|
MSC_REGRESSION_SERVERROOT_DIR="$MSC_REGRESSION_DIR/server_root"
|
||||||
|
MSC_REGRESSION_CONF_DIR="$MSC_REGRESSION_SERVERROOT_DIR/conf"
|
||||||
|
MSC_REGRESSION_LOGS_DIR="$MSC_REGRESSION_SERVERROOT_DIR/logs"
|
||||||
|
MSC_REGRESSION_DOCROOT_DIR="$MSC_REGRESSION_SERVERROOT_DIR/htdocs"
|
||||||
|
|
||||||
|
AC_SUBST(MSC_BASE_DIR)
|
||||||
|
AC_SUBST(MSC_PKGBASE_DIR)
|
||||||
|
AC_SUBST(MSC_TEST_DIR)
|
||||||
|
AC_SUBST(MSC_REGRESSION_DIR)
|
||||||
|
AC_SUBST(MSC_REGRESSION_SERVERROOT_DIR)
|
||||||
|
AC_SUBST(MSC_REGRESSION_CONF_DIR)
|
||||||
|
AC_SUBST(MSC_REGRESSION_LOGS_DIR)
|
||||||
|
AC_SUBST(MSC_REGRESSION_DOCROOT_DIR)
|
||||||
|
|
||||||
# Find apxs
|
# Find apxs
|
||||||
AC_MSG_NOTICE(looking for Apache module support via DSO through APXS)
|
AC_MSG_NOTICE(looking for Apache module support via DSO through APXS)
|
||||||
AC_ARG_WITH(apxs,
|
AC_ARG_WITH(apxs,
|
||||||
@@ -107,6 +126,14 @@ VERSION_OK
|
|||||||
fi
|
fi
|
||||||
APXS_LIBTOOL="`$APXS -q LIBTOOL`"
|
APXS_LIBTOOL="`$APXS -q LIBTOOL`"
|
||||||
APXS_CC="`$APXS -q CC`"
|
APXS_CC="`$APXS -q CC`"
|
||||||
|
APXS_BINDIR="`$APXS -q BINDIR`"
|
||||||
|
APXS_SBINDIR="`$APXS -q SBINDIR`"
|
||||||
|
APXS_PROGNAME="`$APXS -q PROGNAME`"
|
||||||
|
if test "$APXS_SBINDIR" = "/"; then
|
||||||
|
APXS_HTTPD="$APXS_SBINDIR/$APXS_PROGNAME"
|
||||||
|
else
|
||||||
|
APXS_HTTPD="$APXS_SBINDIR/$APXS_PROGNAME"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
AC_MSG_ERROR(couldn't find APXS)
|
AC_MSG_ERROR(couldn't find APXS)
|
||||||
fi
|
fi
|
||||||
@@ -263,6 +290,11 @@ AC_SUBST(APXS_LIBS)
|
|||||||
AC_SUBST(APXS_CFLAGS)
|
AC_SUBST(APXS_CFLAGS)
|
||||||
AC_SUBST(APXS_LIBTOOL)
|
AC_SUBST(APXS_LIBTOOL)
|
||||||
AC_SUBST(APXS_CC)
|
AC_SUBST(APXS_CC)
|
||||||
|
AC_SUBST(APXS_LIBDIR)
|
||||||
|
AC_SUBST(APXS_BINDIR)
|
||||||
|
AC_SUBST(APXS_SBINDIR)
|
||||||
|
AC_SUBST(APXS_PROGNAME)
|
||||||
|
AC_SUBST(APXS_HTTPD)
|
||||||
|
|
||||||
CHECK_PCRE()
|
CHECK_PCRE()
|
||||||
CHECK_APR()
|
CHECK_APR()
|
||||||
@@ -274,7 +306,11 @@ CHECK_CURL()
|
|||||||
AC_CONFIG_FILES([Makefile])
|
AC_CONFIG_FILES([Makefile])
|
||||||
AC_CONFIG_FILES([build/apxs-wrapper], [chmod +x build/apxs-wrapper])
|
AC_CONFIG_FILES([build/apxs-wrapper], [chmod +x build/apxs-wrapper])
|
||||||
if test -e "$PERL"; then
|
if test -e "$PERL"; then
|
||||||
AC_CONFIG_FILES([t/run-tests.pl], [chmod +x t/run-tests.pl])
|
AC_CONFIG_FILES([t/run-unit-tests.pl], [chmod +x t/run-unit-tests.pl])
|
||||||
|
AC_CONFIG_FILES([t/run-regression-tests.pl], [chmod +x t/run-regression-tests.pl])
|
||||||
|
AC_CONFIG_FILES([t/gen_rx-pm.pl], [chmod +x t/gen_rx-pm.pl])
|
||||||
|
AC_CONFIG_FILES([t/csv_rx-pm.pl], [chmod +x t/csv_rx-pm.pl])
|
||||||
|
AC_CONFIG_FILES([t/regression/server_root/conf/httpd.conf])
|
||||||
|
|
||||||
# Perl based tools
|
# Perl based tools
|
||||||
AC_CONFIG_FILES([../tools/rules-updater.pl], [chmod +x ../tools/rules-updater.pl])
|
AC_CONFIG_FILES([../tools/rules-updater.pl], [chmod +x ../tools/rules-updater.pl])
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include <apr.h>
|
#include <apr.h>
|
||||||
|
#include <apr_getopt.h>
|
||||||
|
|
||||||
#include "modsecurity.h"
|
#include "modsecurity.h"
|
||||||
#include "re.h"
|
#include "re.h"
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
|
|
||||||
#define ISHEX(X) (((X >= '0')&&(X <= '9')) || ((X >= 'a')&&(X <= 'f')) || ((X >= 'A')&&(X <= 'F')))
|
#define ISHEX(X) (((X >= '0')&&(X <= '9')) || ((X >= 'a')&&(X <= 'f')) || ((X >= 'A')&&(X <= 'F')))
|
||||||
|
|
||||||
#define BUFLEN 8192
|
#define BUFLEN 32768
|
||||||
|
|
||||||
#define RESULT_SUCCESS 0
|
#define RESULT_SUCCESS 0
|
||||||
#define RESULT_ERROR -1
|
#define RESULT_ERROR -1
|
||||||
@@ -24,10 +25,50 @@
|
|||||||
#define RESULT_WRONGSIZE -3
|
#define RESULT_WRONGSIZE -3
|
||||||
#define RESULT_WRONGRET -4
|
#define RESULT_WRONGRET -4
|
||||||
|
|
||||||
|
#define CMDLINE_OPTS "t:n:p:P:r:I:D:Nh"
|
||||||
|
|
||||||
|
/* Types */
|
||||||
|
typedef struct tfn_data_t tfn_data_t;
|
||||||
|
typedef struct op_data_t op_data_t;
|
||||||
|
typedef struct action_data_t action_data_t;
|
||||||
|
|
||||||
|
struct tfn_data_t {
|
||||||
|
const char *name;
|
||||||
|
const char *param;
|
||||||
|
unsigned char *input;
|
||||||
|
apr_size_t input_len;
|
||||||
|
msre_tfn_metadata *metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct op_data_t {
|
||||||
|
const char *name;
|
||||||
|
const char *param;
|
||||||
|
unsigned char *input;
|
||||||
|
apr_size_t input_len;
|
||||||
|
msre_ruleset *ruleset;
|
||||||
|
msre_rule *rule;
|
||||||
|
msre_var *var;
|
||||||
|
msre_op_metadata *metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct action_data_t {
|
||||||
|
const char *name;
|
||||||
|
unsigned char *input;
|
||||||
|
apr_size_t input_len;
|
||||||
|
msre_ruleset *ruleset;
|
||||||
|
msre_rule *rule;
|
||||||
|
msre_var *var;
|
||||||
|
msre_actionset *actionset;
|
||||||
|
msre_action *action;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/* Globals */
|
/* Globals */
|
||||||
|
static int debuglog_level = 0;
|
||||||
static char *test_name = NULL;
|
static char *test_name = NULL;
|
||||||
static apr_pool_t *g_mp = NULL;
|
static apr_pool_t *g_mp = NULL;
|
||||||
static modsec_rec *g_msr = NULL;
|
static modsec_rec *g_msr = NULL;
|
||||||
|
static unsigned char buf[BUFLEN];
|
||||||
msc_engine *modsecurity = NULL;
|
msc_engine *modsecurity = NULL;
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +86,10 @@ int apache2_exec(modsec_rec *msr, const char *command, const char **argv, char *
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *get_apr_error(apr_pool_t *p, apr_status_t rc) {
|
char *get_apr_error(apr_pool_t *p, apr_status_t rc) {
|
||||||
return "FAKE APR ERROR";
|
char *text = apr_pcalloc(p, 201);
|
||||||
|
if (text == NULL) return NULL;
|
||||||
|
apr_strerror(rc, text, 200);
|
||||||
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
void msr_log(modsec_rec *msr, int level, const char *text, ...) {
|
void msr_log(modsec_rec *msr, int level, const char *text, ...) {
|
||||||
@@ -67,7 +111,7 @@ void msr_log(modsec_rec *msr, int level, const char *text, ...) {
|
|||||||
if (msr->txcfg->debuglog_fd != NULL) {
|
if (msr->txcfg->debuglog_fd != NULL) {
|
||||||
apr_size_t nbytes_written = 0;
|
apr_size_t nbytes_written = 0;
|
||||||
apr_vsnprintf(str1, sizeof(str1), text, ap);
|
apr_vsnprintf(str1, sizeof(str1), text, ap);
|
||||||
apr_snprintf(str2, sizeof(str2), "[%d] [%s] %s\n", level, test_name, str1);
|
apr_snprintf(str2, sizeof(str2), "%lu: [%d] [%s] %s\n", (unsigned long)getpid(), level, test_name, str1);
|
||||||
|
|
||||||
apr_file_write_full(msr->txcfg->debuglog_fd, str2, strlen(str2), &nbytes_written);
|
apr_file_write_full(msr->txcfg->debuglog_fd, str2, strlen(str2), &nbytes_written);
|
||||||
}
|
}
|
||||||
@@ -145,42 +189,48 @@ static char *escape(unsigned char *str, apr_size_t *len)
|
|||||||
|
|
||||||
/* Testing functions */
|
/* Testing functions */
|
||||||
|
|
||||||
static int test_tfn(const char *name, unsigned char *input, apr_size_t input_len, unsigned char **rval, apr_size_t *rval_len, char **errmsg)
|
static int init_tfn(tfn_data_t *data, const char *name, unsigned char *input, apr_size_t input_len, char **errmsg) {
|
||||||
{
|
|
||||||
int rc = -1;
|
|
||||||
msre_tfn_metadata *metadata = NULL;
|
|
||||||
|
|
||||||
*errmsg = NULL;
|
*errmsg = NULL;
|
||||||
|
|
||||||
/* Lookup the tfn */
|
data->name = name;
|
||||||
metadata = msre_engine_tfn_resolve(modsecurity->msre, name);
|
data->input = apr_pmemdup(g_mp, input, input_len);
|
||||||
|
data->input_len = input_len;
|
||||||
if (metadata == NULL) {
|
data->metadata = msre_engine_tfn_resolve(modsecurity->msre, name);
|
||||||
|
if (data->metadata == NULL) {
|
||||||
*errmsg = apr_psprintf(g_mp, "Failed to fetch tfn \"%s\".", name);
|
*errmsg = apr_psprintf(g_mp, "Failed to fetch tfn \"%s\".", name);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_tfn(tfn_data_t *data, unsigned char **rval, apr_size_t *rval_len, char **errmsg)
|
||||||
|
{
|
||||||
|
int rc = -1;
|
||||||
|
|
||||||
|
*errmsg = NULL;
|
||||||
|
|
||||||
/* Execute the tfn */
|
/* Execute the tfn */
|
||||||
rc = metadata->execute(g_mp, input, (long)input_len, (char **)rval, (long *)rval_len);
|
rc = data->metadata->execute(g_mp, data->input, (long)(data->input_len), (char **)rval, (long *)rval_len);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
*errmsg = apr_psprintf(g_mp, "Failed to execute tfn \"%s\".", name);
|
*errmsg = apr_psprintf(g_mp, "Failed to execute tfn \"%s\".", data->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int test_op(const char *name, const char *param, const unsigned char *input, apr_size_t input_len, char **errmsg)
|
static int init_op(op_data_t *data, const char *name, const char *param, unsigned char *input, apr_size_t input_len, char **errmsg) {
|
||||||
{
|
|
||||||
const char *args = apr_psprintf(g_mp, "@%s %s", name, param);
|
const char *args = apr_psprintf(g_mp, "@%s %s", name, param);
|
||||||
char *conf_fn;
|
char *conf_fn;
|
||||||
msre_ruleset *ruleset = NULL;
|
|
||||||
msre_rule *rule = NULL;
|
|
||||||
msre_var *var = NULL;
|
|
||||||
msre_op_metadata *metadata = NULL;
|
|
||||||
int rc = -1;
|
int rc = -1;
|
||||||
|
|
||||||
*errmsg = NULL;
|
*errmsg = NULL;
|
||||||
|
|
||||||
|
data->name = name;
|
||||||
|
data->param = param;
|
||||||
|
data->input = input;
|
||||||
|
data->input_len = input_len;
|
||||||
|
|
||||||
if ( apr_filepath_merge(&conf_fn, NULL, "t/unit-test.conf", APR_FILEPATH_TRUENAME, g_mp) != APR_SUCCESS) {
|
if ( apr_filepath_merge(&conf_fn, NULL, "t/unit-test.conf", APR_FILEPATH_TRUENAME, g_mp) != APR_SUCCESS) {
|
||||||
*errmsg = apr_psprintf(g_mp, "Failed to build a conf filename.");
|
*errmsg = apr_psprintf(g_mp, "Failed to build a conf filename.");
|
||||||
return -1;
|
return -1;
|
||||||
@@ -198,49 +248,136 @@ static int test_op(const char *name, const char *param, const unsigned char *inp
|
|||||||
);
|
);
|
||||||
|
|
||||||
/* Lookup the operator */
|
/* Lookup the operator */
|
||||||
metadata = msre_engine_op_resolve(modsecurity->msre, name);
|
data->metadata = msre_engine_op_resolve(modsecurity->msre, name);
|
||||||
if (metadata == NULL) {
|
if (data->metadata == NULL) {
|
||||||
*errmsg = apr_psprintf(g_mp, "Failed to fetch op \"%s\".", name);
|
*errmsg = apr_psprintf(g_mp, "Failed to fetch op \"%s\".", name);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create a ruleset/rule */
|
/* Create a ruleset/rule */
|
||||||
ruleset = msre_ruleset_create(modsecurity->msre, g_mp);
|
data->ruleset = msre_ruleset_create(modsecurity->msre, g_mp);
|
||||||
if (ruleset == NULL) {
|
if (data->ruleset == NULL) {
|
||||||
*errmsg = apr_psprintf(g_mp, "Failed to create ruleset for op \"%s\".", name);
|
*errmsg = apr_psprintf(g_mp, "Failed to create ruleset for op \"%s\".", name);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
rule = msre_rule_create(ruleset, RULE_TYPE_NORMAL, conf_fn, 1, "UNIT_TEST", args, "t:none,pass,nolog", errmsg);
|
data->rule = msre_rule_create(data->ruleset, RULE_TYPE_NORMAL, conf_fn, 1, "UNIT_TEST", args, "t:none,pass,nolog", errmsg);
|
||||||
if (rule == NULL) {
|
if (data->rule == NULL) {
|
||||||
*errmsg = apr_psprintf(g_mp, "Failed to create rule for op \"%s\": %s", name, *errmsg);
|
*errmsg = apr_psprintf(g_mp, "Failed to create rule for op \"%s\": %s", name, *errmsg);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create a fake variable */
|
/* Create a fake variable */
|
||||||
var = (msre_var *)apr_pcalloc(g_mp, sizeof(msre_var));
|
data->var = (msre_var *)apr_pcalloc(g_mp, sizeof(msre_var));
|
||||||
var->name = "UNIT_TEST";
|
data->var->name = "UNIT_TEST";
|
||||||
var->value = apr_pstrmemdup(g_mp, (char *)input, input_len);
|
data->var->value = apr_pstrmemdup(g_mp, (char *)input, input_len);
|
||||||
var->value_len = input_len;
|
data->var->value_len = input_len;
|
||||||
var->metadata = msre_resolve_var(modsecurity->msre, var->name);
|
data->var->metadata = msre_resolve_var(modsecurity->msre, data->var->name);
|
||||||
if (var->metadata == NULL) {
|
if (data->var->metadata == NULL) {
|
||||||
*errmsg = apr_psprintf(g_mp, "Failed to resolve variable for op \"%s\": %s", name, var->name);
|
*errmsg = apr_psprintf(g_mp, "Failed to resolve variable for op \"%s\": %s", name, data->var->name);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize the operator parameter */
|
/* Initialize the operator parameter */
|
||||||
if (metadata->param_init != NULL) {
|
if (data->metadata->param_init != NULL) {
|
||||||
rc = metadata->param_init(rule, errmsg);
|
rc = data->metadata->param_init(data->rule, errmsg);
|
||||||
if (rc <= 0) {
|
if (rc <= 0) {
|
||||||
*errmsg = apr_psprintf(g_mp, "Failed to init op \"%s\": %s", name, *errmsg);
|
*errmsg = apr_psprintf(g_mp, "Failed to init op \"%s\": %s", name, *errmsg);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_op(op_data_t *data, char **errmsg)
|
||||||
|
{
|
||||||
|
int rc = -1;
|
||||||
|
|
||||||
|
*errmsg = NULL;
|
||||||
|
|
||||||
/* Execute the operator */
|
/* Execute the operator */
|
||||||
if (metadata->execute != NULL) {
|
if (data->metadata->execute != NULL) {
|
||||||
rc = metadata->execute(g_msr, rule, var, errmsg);
|
rc = data->metadata->execute(g_msr, data->rule, data->var, errmsg);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
*errmsg = apr_psprintf(g_mp, "Failed to execute op \"%s\": %s", name, *errmsg);
|
*errmsg = apr_psprintf(g_mp, "Failed to execute op \"%s\": %s", data->name, *errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int init_action(action_data_t *data, const char *name, const char *param, char **errmsg)
|
||||||
|
{
|
||||||
|
const char *action_string = NULL;
|
||||||
|
char *conf_fn;
|
||||||
|
|
||||||
|
*errmsg = NULL;
|
||||||
|
|
||||||
|
if ((param == NULL) || (strcmp("", param) == 0)) {
|
||||||
|
action_string = apr_psprintf(g_mp, "%s", name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
action_string = apr_psprintf(g_mp, "%s:%s", name, param);
|
||||||
|
}
|
||||||
|
if (action_string == NULL) {
|
||||||
|
*errmsg = apr_psprintf(g_mp, "Failed to build action string for action: \"%s\".", name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( apr_filepath_merge(&conf_fn, NULL, "t/unit-test.conf", APR_FILEPATH_TRUENAME, g_mp) != APR_SUCCESS) {
|
||||||
|
*errmsg = apr_psprintf(g_mp, "Failed to build a conf filename.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Register UNIT_TEST variable */
|
||||||
|
msre_engine_variable_register(modsecurity->msre,
|
||||||
|
"UNIT_TEST",
|
||||||
|
VAR_SIMPLE,
|
||||||
|
0, 0,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
VAR_DONT_CACHE,
|
||||||
|
PHASE_REQUEST_HEADERS
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Create a ruleset/rule */
|
||||||
|
data->ruleset = msre_ruleset_create(modsecurity->msre, g_mp);
|
||||||
|
if (data->ruleset == NULL) {
|
||||||
|
*errmsg = apr_psprintf(g_mp, "Failed to create ruleset for action \"%s\".", name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
data->rule = msre_rule_create(data->ruleset, RULE_TYPE_NORMAL, conf_fn, 1, "UNIT_TEST", "@unconditionalMatch", action_string, errmsg);
|
||||||
|
if (data->rule == NULL) {
|
||||||
|
*errmsg = apr_psprintf(g_mp, "Failed to create rule for action \"%s\": %s", name, *errmsg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the actionset/action */
|
||||||
|
data->actionset = data->rule->actionset;
|
||||||
|
if (data->actionset == NULL) {
|
||||||
|
*errmsg = apr_psprintf(g_mp, "Failed to fetch actionset for action \"%s\"", name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
data->action = (msre_action *)apr_table_get(data->actionset->actions, name);
|
||||||
|
if (data->action == NULL) {
|
||||||
|
*errmsg = apr_psprintf(g_mp, "Failed to fetch action for action \"%s\"", name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_action(action_data_t *data, char **errmsg)
|
||||||
|
{
|
||||||
|
int rc = -1;
|
||||||
|
|
||||||
|
*errmsg = NULL;
|
||||||
|
|
||||||
|
/* Execute the action */
|
||||||
|
if (data->action->metadata->execute != NULL) {
|
||||||
|
rc = data->action->metadata->execute(g_msr, g_mp, data->rule, data->action);
|
||||||
|
if (rc < 0) {
|
||||||
|
*errmsg = apr_psprintf(g_mp, "Failed to execute action \"%s\": %d", data->name, rc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +402,7 @@ static void init_msr() {
|
|||||||
dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT;
|
dcfg->of_limit_action = RESPONSE_BODY_LIMIT_ACTION_REJECT;
|
||||||
dcfg->debuglog_fd = NOT_SET_P;
|
dcfg->debuglog_fd = NOT_SET_P;
|
||||||
dcfg->debuglog_name = "msc-test-debug.log";
|
dcfg->debuglog_name = "msc-test-debug.log";
|
||||||
dcfg->debuglog_level = 9;
|
dcfg->debuglog_level = debuglog_level;
|
||||||
dcfg->cookie_format = 0;
|
dcfg->cookie_format = 0;
|
||||||
dcfg->argument_separator = '&';
|
dcfg->argument_separator = '&';
|
||||||
dcfg->rule_inheritance = 0;
|
dcfg->rule_inheritance = 0;
|
||||||
@@ -282,7 +419,7 @@ static void init_msr() {
|
|||||||
dcfg->upload_dir = NULL;
|
dcfg->upload_dir = NULL;
|
||||||
dcfg->upload_keep_files = KEEP_FILES_OFF;
|
dcfg->upload_keep_files = KEEP_FILES_OFF;
|
||||||
dcfg->upload_validates_files = 0;
|
dcfg->upload_validates_files = 0;
|
||||||
dcfg->data_dir = NULL;
|
dcfg->data_dir = ".";
|
||||||
dcfg->webappid = "default";
|
dcfg->webappid = "default";
|
||||||
dcfg->content_injection_enabled = 0;
|
dcfg->content_injection_enabled = 0;
|
||||||
dcfg->pdfp_enabled = 0;
|
dcfg->pdfp_enabled = 0;
|
||||||
@@ -322,115 +459,167 @@ static void init_msr() {
|
|||||||
g_msr->request_headers = NULL;
|
g_msr->request_headers = NULL;
|
||||||
g_msr->hostname = "localhost";
|
g_msr->hostname = "localhost";
|
||||||
g_msr->msc_rule_mptmp = g_mp;
|
g_msr->msc_rule_mptmp = g_mp;
|
||||||
g_msr->tx_vars = apr_table_make(g_mp, 10);
|
g_msr->tx_vars = apr_table_make(g_mp, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usage text.
|
||||||
|
*/
|
||||||
|
static void usage() {
|
||||||
|
fprintf(stderr, "ModSecurity Unit Tester v%s\n", MODULE_RELEASE);
|
||||||
|
fprintf(stderr, " Usage: msc_test [options]\n");
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, " Options:\n");
|
||||||
|
fprintf(stderr, " -t Type (required)\n");
|
||||||
|
fprintf(stderr, " -n Name (required)\n");
|
||||||
|
fprintf(stderr, " -p Parameter (required)\n");
|
||||||
|
fprintf(stderr, " -P Prerun (optional for actions)\n");
|
||||||
|
fprintf(stderr, " -r Function return code (required for some types)\n");
|
||||||
|
fprintf(stderr, " -I Iterations (default 1)\n");
|
||||||
|
fprintf(stderr, " -D Debug log level (default 0)\n");
|
||||||
|
fprintf(stderr, " -N No input on stdin.\n\n");
|
||||||
|
fprintf(stderr, " -h This help\n\n");
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "Input is from stdin unless -N is used.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Main */
|
/* Main */
|
||||||
|
|
||||||
int main(int argc, const char * const argv[])
|
int main(int argc, const char * const argv[])
|
||||||
{
|
{
|
||||||
|
apr_getopt_t *opt;
|
||||||
apr_file_t *fd;
|
apr_file_t *fd;
|
||||||
unsigned char buf[BUFLEN];
|
apr_size_t nbytes = 0;
|
||||||
apr_size_t nbytes = BUFLEN;
|
|
||||||
unsigned char input[BUFLEN];
|
|
||||||
const char *type = NULL;
|
const char *type = NULL;
|
||||||
const char *name = NULL;
|
const char *name = NULL;
|
||||||
unsigned char *param = NULL;
|
unsigned char *param = NULL;
|
||||||
|
unsigned char *prerun = NULL;
|
||||||
const char *returnval = NULL;
|
const char *returnval = NULL;
|
||||||
|
int iterations = 1;
|
||||||
char *errmsg = NULL;
|
char *errmsg = NULL;
|
||||||
unsigned char *out = NULL;
|
unsigned char *out = NULL;
|
||||||
apr_size_t input_len = 0;
|
|
||||||
apr_size_t param_len = 0;
|
apr_size_t param_len = 0;
|
||||||
|
apr_size_t prerun_len = 0;
|
||||||
apr_size_t out_len = 0;
|
apr_size_t out_len = 0;
|
||||||
|
int noinput = 0;
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
int result = 0;
|
int result = 0;
|
||||||
int ec = 0;
|
int ec = 0;
|
||||||
|
int i;
|
||||||
|
apr_time_t T0 = 0;
|
||||||
|
apr_time_t T1 = 0;
|
||||||
|
tfn_data_t tfn_data;
|
||||||
|
op_data_t op_data;
|
||||||
|
action_data_t action_data;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
memset(&tfn_data, 0, sizeof(tfn_data_t));
|
||||||
|
memset(&op_data, 0, sizeof(op_data_t));
|
||||||
|
memset(&action_data, 0, sizeof(action_data_t));
|
||||||
|
|
||||||
apr_app_initialize(&argc, &argv, NULL);
|
apr_app_initialize(&argc, &argv, NULL);
|
||||||
atexit(apr_terminate);
|
atexit(apr_terminate);
|
||||||
|
|
||||||
if (argc < 4) {
|
apr_pool_create(&g_mp, NULL);
|
||||||
fprintf(stderr, "Usage: %s <type> <name> <param> [<returnval>]\n", argv[0]);
|
|
||||||
|
rc = apr_getopt_init(&opt, g_mp, argc, argv);
|
||||||
|
if (rc != APR_SUCCESS) {
|
||||||
|
fprintf(stderr, "Failed to initialize.\n\n");
|
||||||
|
usage();
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
apr_pool_create(&g_mp, NULL);
|
do {
|
||||||
modsecurity = modsecurity_create(g_mp, MODSEC_OFFLINE);
|
char ch;
|
||||||
|
const char *val;
|
||||||
|
rc = apr_getopt(opt, CMDLINE_OPTS, &ch, &val);
|
||||||
|
switch (rc) {
|
||||||
|
case APR_SUCCESS:
|
||||||
|
switch (ch) {
|
||||||
|
case 't':
|
||||||
|
type = val;
|
||||||
|
break;
|
||||||
|
case 'n':
|
||||||
|
name = val;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
param_len = strlen(val);
|
||||||
|
param = apr_pmemdup(g_mp, val, param_len + 1);
|
||||||
|
unescape_inplace(param, ¶m_len);
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
prerun_len = strlen(val);
|
||||||
|
prerun = apr_pmemdup(g_mp, val, prerun_len + 1);
|
||||||
|
unescape_inplace(prerun, &prerun_len);
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
returnval = val;
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
iterations = atoi(val);
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
debuglog_level = atoi(val);
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
noinput = 1;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
usage();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APR_BADCH:
|
||||||
|
case APR_BADARG:
|
||||||
|
usage();
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
} while (rc != APR_EOF);
|
||||||
|
|
||||||
type = argv[1];
|
rc = apr_getopt_init(&opt, g_mp, argc, argv);
|
||||||
name = argv[2];
|
if (!type || !name || !param) {
|
||||||
param_len = strlen(argv[3]);
|
usage();
|
||||||
param = apr_pmemdup(g_mp, argv[3], param_len + 1);
|
exit(1);
|
||||||
unescape_inplace(param, ¶m_len);
|
|
||||||
if (argc >= 5) {
|
|
||||||
returnval = argv[4];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modsecurity = modsecurity_create(g_mp, MODSEC_OFFLINE);
|
||||||
test_name = apr_psprintf(g_mp, "%s/%s", type, name);
|
test_name = apr_psprintf(g_mp, "%s/%s", type, name);
|
||||||
|
|
||||||
if (apr_file_open_stdin(&fd, g_mp) != APR_SUCCESS) {
|
if (noinput == 0) {
|
||||||
fprintf(stderr, "Failed to open stdin\n");
|
if (apr_file_open_stdin(&fd, g_mp) != APR_SUCCESS) {
|
||||||
exit(1);
|
fprintf(stderr, "Failed to open stdin\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read in the input */
|
||||||
|
nbytes = BUFLEN;
|
||||||
|
memset(buf, 0, nbytes);
|
||||||
|
rc = apr_file_read(fd, buf, &nbytes);
|
||||||
|
if ((rc != APR_EOF) && (rc != APR_SUCCESS)) {
|
||||||
|
fprintf(stderr, "Failed to read data\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbytes < 0) {
|
||||||
|
fprintf(stderr, "Error reading data\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
apr_file_close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(buf, 0, BUFLEN);
|
|
||||||
rc = apr_file_read(fd, buf, &nbytes);
|
|
||||||
if ((rc != APR_EOF) && (rc != APR_SUCCESS)) {
|
|
||||||
fprintf(stderr, "Failed to read data\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nbytes < 0) {
|
|
||||||
fprintf(stderr, "Error reading data\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
apr_file_close(fd);
|
|
||||||
|
|
||||||
/* Make a copy as transformations are done in-place */
|
|
||||||
memcpy(input, buf, BUFLEN);
|
|
||||||
input_len = nbytes;
|
|
||||||
|
|
||||||
if (strcmp("tfn", type) == 0) {
|
if (strcmp("tfn", type) == 0) {
|
||||||
/* Transformations */
|
ret = returnval ? atoi(returnval) : -8888;
|
||||||
int ret = returnval ? atoi(returnval) : -8888;
|
|
||||||
rc = test_tfn(name, input, input_len, &out, &out_len, &errmsg);
|
rc = init_tfn(&tfn_data, name, buf, nbytes, &errmsg);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
fprintf(stderr, "ERROR: %s\n", errmsg);
|
fprintf(stderr, "ERROR: %s\n", errmsg);
|
||||||
result = RESULT_ERROR;
|
result = RESULT_ERROR;
|
||||||
}
|
}
|
||||||
else if ((ret != -8888) && (rc != ret)) {
|
|
||||||
fprintf(stderr, "Returned %d (expected %d)\n", rc, ret);
|
|
||||||
result = RESULT_WRONGRET;
|
|
||||||
}
|
|
||||||
else if (param_len != out_len) {
|
|
||||||
fprintf(stderr, "Lenth %" APR_SIZE_T_FMT " (expected %" APR_SIZE_T_FMT ")\n", out_len, param_len);
|
|
||||||
result = RESULT_WRONGSIZE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
result = memcmp(param, out, param_len) ? RESULT_MISMATCHED : RESULT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != RESULT_SUCCESS) {
|
|
||||||
apr_size_t s0len = nbytes;
|
|
||||||
const char *s0 = escape(buf, &s0len);
|
|
||||||
apr_size_t s1len = out_len;
|
|
||||||
const char *s1 = escape(out, &s1len);
|
|
||||||
apr_size_t s2len = param_len;
|
|
||||||
const char *s2 = escape(param, &s2len);
|
|
||||||
|
|
||||||
fprintf(stderr, " Input: '%s' len=%" APR_SIZE_T_FMT "\n"
|
|
||||||
"Output: '%s' len=%" APR_SIZE_T_FMT "\n"
|
|
||||||
"Expect: '%s' len=%" APR_SIZE_T_FMT "\n",
|
|
||||||
s0, nbytes, s1, out_len, s2, param_len);
|
|
||||||
ec = 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (strcmp("op", type) == 0) {
|
else if (strcmp("op", type) == 0) {
|
||||||
/* Operators */
|
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
if (!returnval) {
|
if (!returnval) {
|
||||||
fprintf(stderr, "Return value required for type \"%s\"\n", type);
|
fprintf(stderr, "Return value required for type \"%s\"\n", type);
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -439,34 +628,176 @@ int main(int argc, const char * const argv[])
|
|||||||
|
|
||||||
init_msr();
|
init_msr();
|
||||||
|
|
||||||
rc = test_op(name, (const char *)param, (const unsigned char *)input, input_len, &errmsg);
|
rc = init_op(&op_data, name, (const char *)param, buf, nbytes, &errmsg);
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
fprintf(stderr, "ERROR: %s\n", errmsg);
|
fprintf(stderr, "ERROR: %s\n", errmsg);
|
||||||
result = RESULT_ERROR;
|
result = RESULT_ERROR;
|
||||||
}
|
}
|
||||||
else if (rc != ret) {
|
}
|
||||||
fprintf(stderr, "Returned %d (expected %d)\n", rc, ret);
|
else if (strcmp("action", type) == 0) {
|
||||||
result = RESULT_WRONGRET;
|
if (!returnval) {
|
||||||
|
fprintf(stderr, "Return value required for type \"%s\"\n", type);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
ret = atoi(returnval);
|
||||||
|
|
||||||
|
init_msr();
|
||||||
|
|
||||||
|
if (prerun) {
|
||||||
|
action_data_t paction_data;
|
||||||
|
char *pname = apr_pstrdup(g_mp, (const char *)prerun);
|
||||||
|
char *pparam = NULL;
|
||||||
|
|
||||||
|
if ((pparam = strchr((const char *)pname, ':'))) {
|
||||||
|
pparam[0] = '\0';
|
||||||
|
pparam++;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = init_action(&paction_data, pname, (const char *)pparam, &errmsg);
|
||||||
|
if (rc < 0) {
|
||||||
|
fprintf(stderr, "ERROR: prerun - %s\n", errmsg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = test_action(&paction_data, &errmsg);
|
||||||
|
if (rc < 0) {
|
||||||
|
fprintf(stderr, "ERROR: prerun - %s\n", errmsg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = init_action(&action_data, name, (const char *)param, &errmsg);
|
||||||
|
if (rc < 0) {
|
||||||
|
fprintf(stderr, "ERROR: %s\n", errmsg);
|
||||||
|
result = RESULT_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iterations > 1) {
|
||||||
|
apr_time_clock_hires (g_mp);
|
||||||
|
T0 = apr_time_now();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 1; i <= iterations; i++) {
|
||||||
|
#ifdef VERBOSE
|
||||||
|
if (i % 100 == 0) {
|
||||||
|
if (i == 100) {
|
||||||
|
fprintf(stderr, "Iterations/100: .");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fprintf(stderr, ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (strcmp("tfn", type) == 0) {
|
||||||
|
/* Transformations */
|
||||||
|
rc = test_tfn(&tfn_data, &out, &out_len, &errmsg);
|
||||||
|
if (rc < 0) {
|
||||||
|
fprintf(stderr, "ERROR: %s\n", errmsg);
|
||||||
|
result = RESULT_ERROR;
|
||||||
|
}
|
||||||
|
else if ((ret != -8888) && (rc != ret)) {
|
||||||
|
fprintf(stderr, "Returned %d (expected %d)\n", rc, ret);
|
||||||
|
result = RESULT_WRONGRET;
|
||||||
|
}
|
||||||
|
else if (param_len != out_len) {
|
||||||
|
fprintf(stderr, "Lenth %" APR_SIZE_T_FMT " (expected %" APR_SIZE_T_FMT ")\n", out_len, param_len);
|
||||||
|
result = RESULT_WRONGSIZE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = memcmp(param, out, param_len) ? RESULT_MISMATCHED : RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != RESULT_SUCCESS) {
|
||||||
|
apr_size_t s0len = nbytes;
|
||||||
|
const char *s0 = escape(buf, &s0len);
|
||||||
|
apr_size_t s1len = out_len;
|
||||||
|
const char *s1 = escape(out, &s1len);
|
||||||
|
apr_size_t s2len = param_len;
|
||||||
|
const char *s2 = escape(param, &s2len);
|
||||||
|
|
||||||
|
fprintf(stderr, " Input: '%s' len=%" APR_SIZE_T_FMT "\n"
|
||||||
|
"Output: '%s' len=%" APR_SIZE_T_FMT "\n"
|
||||||
|
"Expect: '%s' len=%" APR_SIZE_T_FMT "\n",
|
||||||
|
s0, nbytes, s1, out_len, s2, param_len);
|
||||||
|
ec = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp("op", type) == 0) {
|
||||||
|
/* Operators */
|
||||||
|
rc = test_op(&op_data, &errmsg);
|
||||||
|
if (rc < 0) {
|
||||||
|
fprintf(stderr, "ERROR: %s\n", errmsg);
|
||||||
|
result = RESULT_ERROR;
|
||||||
|
}
|
||||||
|
else if (rc != ret) {
|
||||||
|
fprintf(stderr, "Returned %d (expected %d)\n", rc, ret);
|
||||||
|
result = RESULT_WRONGRET;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != RESULT_SUCCESS) {
|
||||||
|
apr_size_t s0len = nbytes;
|
||||||
|
const char *s0 = escape(buf, &s0len);
|
||||||
|
|
||||||
|
fprintf(stderr, " Test: '@%s %s'\n"
|
||||||
|
"Input: '%s' len=%" APR_SIZE_T_FMT "\n",
|
||||||
|
name, param, s0, nbytes);
|
||||||
|
ec = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp("action", type) == 0) {
|
||||||
|
/* Actions */
|
||||||
|
|
||||||
|
rc = test_action(&action_data, &errmsg);
|
||||||
|
if (rc < 0) {
|
||||||
|
fprintf(stderr, "ERROR: %s\n", errmsg);
|
||||||
|
result = RESULT_ERROR;
|
||||||
|
}
|
||||||
|
else if (rc != ret) {
|
||||||
|
fprintf(stderr, "Returned %d (expected %d)\n", rc, ret);
|
||||||
|
result = RESULT_WRONGRET;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = RESULT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != RESULT_SUCCESS) {
|
||||||
|
fprintf(stderr, " Test: '%s:%s'\n"
|
||||||
|
"Prerun: '%s'\n",
|
||||||
|
name, param, (prerun ? (const char *)prerun : ""));
|
||||||
|
ec = 1;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
result = RESULT_SUCCESS;
|
fprintf(stderr, "Unknown type: \"%s\"\n", type);
|
||||||
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result != RESULT_SUCCESS) {
|
if (ec != 0) {
|
||||||
apr_size_t s0len = nbytes;
|
fprintf(stdout, "%s\n", errmsg ? errmsg : "");
|
||||||
const char *s0 = escape(buf, &s0len);
|
return ec;
|
||||||
|
|
||||||
fprintf(stderr, " Test: '@%s %s'\n"
|
|
||||||
"Input: '%s' len=%" APR_SIZE_T_FMT "\n",
|
|
||||||
name, param, s0, nbytes);
|
|
||||||
ec = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
fprintf(stderr, "Unknown type: \"%s\"\n", type);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (iterations > 1) {
|
||||||
|
double dT;
|
||||||
|
T1 = apr_time_now();
|
||||||
|
|
||||||
|
dT = apr_time_as_msec(T1 - T0);
|
||||||
|
|
||||||
|
#ifdef VERBOSE
|
||||||
|
if (i >= 100) {
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
fprintf(stdout, "%d @ %.4f msec per iteration.\n", iterations, dT / iterations);
|
||||||
|
}
|
||||||
fprintf(stdout, "%s\n", errmsg ? errmsg : "");
|
fprintf(stdout, "%s\n", errmsg ? errmsg : "");
|
||||||
|
|
||||||
return ec;
|
return ec;
|
||||||
|
|||||||
21
apache2/t/csv_rx-pm.pl.in
Executable file
21
apache2/t/csv_rx-pm.pl.in
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
#!@PERL@
|
||||||
|
#
|
||||||
|
# Example to generate CSV performance data from test results taken from
|
||||||
|
# test generated by gen_rx-pm.pl.
|
||||||
|
#
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
my %H = ();
|
||||||
|
while (<>) {
|
||||||
|
chomp;
|
||||||
|
my ($op, $label, $n, $i, $value) = (m/\s*\d+\)\s+\S+\s+"([^"]*)"\s+(\S+)\s+(\d+) item\(s\): passed\s+\((\d+)\s+\@\s+([-\+\d\.E]+) msec\s.*/);
|
||||||
|
|
||||||
|
next unless defined($value);
|
||||||
|
$H{$n}{$label} = $value;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "%s, %s, %s, %s\n", qw(N rx1 rx2 pm1);
|
||||||
|
for (sort {$a <=> $b} keys %H) {
|
||||||
|
printf "%s, %s, %s, %s\n", $_, $H{$_}{rx1}, $H{$_}{rx2}, $H{$_}{pm1}
|
||||||
|
};
|
||||||
99
apache2/t/gen_rx-pm.pl.in
Executable file
99
apache2/t/gen_rx-pm.pl.in
Executable file
@@ -0,0 +1,99 @@
|
|||||||
|
#!@PERL@
|
||||||
|
#
|
||||||
|
# Generates a test file for comparing @rx and @pm speed.
|
||||||
|
#
|
||||||
|
use strict;
|
||||||
|
use Regexp::Assemble;
|
||||||
|
|
||||||
|
srand(424242); # We want this static, so we can compare different runs
|
||||||
|
|
||||||
|
my $MIN = $ARGV[0] || 0;
|
||||||
|
my $MAX = $ARGV[1] || 5000;
|
||||||
|
my $INC = $ARGV[2] || int($MAX * .05);
|
||||||
|
my $ITERATIONS = 10000;
|
||||||
|
my $MINSTRLEN = 2;
|
||||||
|
my $MAXSTRLEN = 8;
|
||||||
|
|
||||||
|
my $match = join '', ('a' .. 'z');
|
||||||
|
my @param = ();
|
||||||
|
my $i=$MIN;
|
||||||
|
while ($i <= $MAX) {
|
||||||
|
my $ra = Regexp::Assemble->new;
|
||||||
|
|
||||||
|
while (@param < $i) {
|
||||||
|
unshift @param, rndstr();
|
||||||
|
}
|
||||||
|
|
||||||
|
$ra->add(@param);
|
||||||
|
|
||||||
|
printf (
|
||||||
|
"# rx: %6d\n".
|
||||||
|
"{\n".
|
||||||
|
" comment => \"rx1 %6d item(s)\",\n".
|
||||||
|
" type => \"op\",\n".
|
||||||
|
" name => \"rx\",\n".
|
||||||
|
" param => qr/%s/,\n".
|
||||||
|
" input => \"%s\",\n".
|
||||||
|
" ret => " . (@param ? 0 : 1) . ",".
|
||||||
|
" iterations => %d,\n".
|
||||||
|
"},\n",
|
||||||
|
$i,
|
||||||
|
$i,
|
||||||
|
(@param ? '(?:' . join('|', @param) . ')' : ""),
|
||||||
|
$match,
|
||||||
|
$ITERATIONS,
|
||||||
|
);
|
||||||
|
|
||||||
|
printf (
|
||||||
|
"# rx-optimized: %6d\n".
|
||||||
|
"{\n".
|
||||||
|
" comment => \"rx2 %6d item(s)\",\n".
|
||||||
|
" type => \"op\",\n".
|
||||||
|
" name => \"rx\",\n".
|
||||||
|
" param => qr/%s/,\n".
|
||||||
|
" input => \"%s\",\n".
|
||||||
|
" ret => " . (@param ? 0 : 1) . ",".
|
||||||
|
" iterations => %d,\n".
|
||||||
|
"},\n",
|
||||||
|
$i,
|
||||||
|
$i,
|
||||||
|
(@param ? $ra->as_string : ""),
|
||||||
|
$match,
|
||||||
|
$ITERATIONS,
|
||||||
|
);
|
||||||
|
|
||||||
|
printf (
|
||||||
|
"# pm: %6d\n".
|
||||||
|
"{\n".
|
||||||
|
" comment => \"pm1 %6d item(s)\",\n".
|
||||||
|
" type => \"op\",\n".
|
||||||
|
" name => \"pm\",\n".
|
||||||
|
" param => \"%s\",\n".
|
||||||
|
" input => \"%s\",\n".
|
||||||
|
" ret => 0,".
|
||||||
|
" iterations => %d,\n".
|
||||||
|
"},\n",
|
||||||
|
$i,
|
||||||
|
$i,
|
||||||
|
join(' ', @param ? @param : ("''")),
|
||||||
|
$match,
|
||||||
|
$ITERATIONS,
|
||||||
|
);
|
||||||
|
|
||||||
|
$i = ($i == $MIN) ? ($i + $INC) - ($i % $INC) : $i + $INC;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rndstr {
|
||||||
|
my @c = ('a' .. 'z');
|
||||||
|
my $rndstr;
|
||||||
|
my $max = int(rand($MAXSTRLEN - $MINSTRLEN)) + $MINSTRLEN;
|
||||||
|
foreach (1 .. $max) {
|
||||||
|
$rndstr .= $c[rand @c];
|
||||||
|
}
|
||||||
|
# We need a string that is not in another string for "last"
|
||||||
|
if ($match =~ m/$rndstr/) {
|
||||||
|
$rndstr = rndstr();
|
||||||
|
}
|
||||||
|
return $rndstr;
|
||||||
|
}
|
||||||
531
apache2/t/regression/action/00-disruptive-actions.t
Normal file
531
apache2/t/regression/action/00-disruptive-actions.t
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
### Tests all of the actions in each phase
|
||||||
|
|
||||||
|
# Pass
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "pass in phase:1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:1,pass"
|
||||||
|
SecAction "phase:1,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "pass in phase:2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:2,pass"
|
||||||
|
SecAction "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "pass in phase:3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:3,pass"
|
||||||
|
SecAction "phase:3,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "pass in phase:4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:4,pass"
|
||||||
|
SecAction "phase:4,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Allow
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "allow in phase:1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:1,allow"
|
||||||
|
SecAction "phase:1,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access allowed \(phase 1\). Unconditional match in SecAction/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "allow in phase:2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:2,allow"
|
||||||
|
SecAction "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access allowed \(phase 2\). Unconditional match in SecAction/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "allow in phase:3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:3,allow"
|
||||||
|
SecAction "phase:3,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access allowed \(phase 3\). Unconditional match in SecAction/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "allow in phase:4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:4,allow"
|
||||||
|
SecAction "phase:4,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access allowed \(phase 4\). Unconditional match in SecAction/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Deny
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "deny in phase:1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:1,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with code 403 \(phase 1\). Unconditional match in SecAction./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "deny in phase:2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with code 403 \(phase 2\). Unconditional match in SecAction./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "deny in phase:3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:3,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with code 403 \(phase 3\). Unconditional match in SecAction./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "deny in phase:4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:4,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with code 403 \(phase 4\). Unconditional match in SecAction./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Drop
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "drop in phase:1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:1,drop"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with connection close \(phase 1\). Unconditional match in SecAction./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^500$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "drop in phase:2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:2,drop"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with connection close \(phase 2\). Unconditional match in SecAction./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^500$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "drop in phase:3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:3,drop"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with connection close \(phase 3\). Unconditional match in SecAction./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^500$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "drop in phase:4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:4,drop"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with connection close \(phase 4\). Unconditional match in SecAction./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^500$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Redirect
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "redirect in phase:1 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 1\)/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "redirect in phase:2 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 2\)/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "redirect in phase:3 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 3\)/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "redirect in phase:4 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied with redirection to .* using status 302 \(phase 4\)/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Proxy
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "proxy in phase:1 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied using proxy to \(phase 1\)/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "proxy in phase:2 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied using proxy to \(phase 2\)/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "proxy in phase:3 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied with code 500 \(phase 3\) \(Configuration Error: Proxy action requested but it does not work in output phases\)./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^500$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "proxy in phase:4 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied with code 500 \(phase 4\) \(Configuration Error: Proxy action requested but it does not work in output phases\)./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^500$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
8
apache2/t/regression/action/00-meta.t
Normal file
8
apache2/t/regression/action/00-meta.t
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
### Test meta actions
|
||||||
|
|
||||||
|
# TODO: id
|
||||||
|
# TODO: logdata
|
||||||
|
# TODO: msg
|
||||||
|
# TODO: rev
|
||||||
|
# TODO: severity
|
||||||
|
# TODO: tag
|
||||||
24
apache2/t/regression/action/00-misc.t
Normal file
24
apache2/t/regression/action/00-misc.t
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
### Test misc actions
|
||||||
|
|
||||||
|
# TODO: append
|
||||||
|
# TODO: block
|
||||||
|
# TODO: capture
|
||||||
|
# TODO: chain
|
||||||
|
# TODO: deprecatevar
|
||||||
|
# TODO: exec
|
||||||
|
# TODO: expirevar
|
||||||
|
# TODO: initcol
|
||||||
|
# TODO: multiMatch
|
||||||
|
# TODO: pause
|
||||||
|
# TODO: prepend
|
||||||
|
# TODO: sanitiseArg
|
||||||
|
# TODO: sanitiseMatched
|
||||||
|
# TODO: sanitiseRequestHeader
|
||||||
|
# TODO: sanitiseResponseHeader
|
||||||
|
# TODO: setuid
|
||||||
|
# TODO: setsid
|
||||||
|
# TODO: setenv
|
||||||
|
# TODO: setvar
|
||||||
|
# TODO: skip
|
||||||
|
# TODO: skipAfter
|
||||||
|
# TODO: xmlns
|
||||||
8
apache2/t/regression/action/00-transformations.t
Normal file
8
apache2/t/regression/action/00-transformations.t
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
### Transformation tests
|
||||||
|
|
||||||
|
# NOTE: individual tests done in unit tests
|
||||||
|
|
||||||
|
# TODO: t:none to override default
|
||||||
|
# TODO: t:none inline
|
||||||
|
# TODO: combined
|
||||||
|
# TODO: caching
|
||||||
56
apache2/t/regression/action/10-ctl.t
Normal file
56
apache2/t/regression/action/10-ctl.t
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
### ctl
|
||||||
|
|
||||||
|
### ruleRemoveById
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "ruleRemoveById existing rule across phases",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAction "phase:2,id:666,deny"
|
||||||
|
SecAction "phase:1,pass,ctl:ruleRemoveById=666"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "ruleRemoveById future rule across phases",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAction "phase:1,pass,ctl:ruleRemoveById=666"
|
||||||
|
SecAction "phase:2,id:666,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "ruleRemoveById future rule same phase",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAction "phase:1,pass,ctl:ruleRemoveById=666"
|
||||||
|
SecAction "phase:1,id:666,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
545
apache2/t/regression/action/10-detectiononly-actions.t
Normal file
545
apache2/t/regression/action/10-detectiononly-actions.t
Normal file
@@ -0,0 +1,545 @@
|
|||||||
|
### Tests all of the actions in each phase in detection only mode
|
||||||
|
|
||||||
|
# Pass
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "pass in phase:1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecAction "phase:1,pass,msg:'PASSED'"
|
||||||
|
SecAction "phase:1,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "pass in phase:2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:2,pass,msg:'PASSED'"
|
||||||
|
SecAction "phase:2,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "pass in phase:3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:3,pass,msg:'PASSED'"
|
||||||
|
SecAction "phase:3,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "pass in phase:4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "phase:4,pass,msg:'PASSED'"
|
||||||
|
SecAction "phase:4,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*PASSED/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Allow
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "allow in phase:1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:1,allow,msg:'ALLOWED'"
|
||||||
|
SecAction "phase:1,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ],
|
||||||
|
-error => [ qr/Access allowed/, 1 ],
|
||||||
|
# TODO: Allow should probably rule stop execution
|
||||||
|
# -error => [ qr/DENIED/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "allow in phase:2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:2,allow,msg:'ALLOWED'"
|
||||||
|
SecAction "phase:2,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ],
|
||||||
|
-error => [ qr/Access allowed/, 1 ],
|
||||||
|
# TODO: Allow should probably rule stop execution
|
||||||
|
# -error => [ qr/DENIED/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "allow in phase:3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:3,allow,msg:'ALLOWED'"
|
||||||
|
SecAction "phase:3,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ],
|
||||||
|
-error => [ qr/Access allowed/, 1 ],
|
||||||
|
# TODO: Allow should probably rule stop execution
|
||||||
|
# -error => [ qr/DENIED/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "allow in phase:4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:4,allow,msg:'ALLOWED'"
|
||||||
|
SecAction "phase:4,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*ALLOWED/, 1 ],
|
||||||
|
-error => [ qr/Access allowed/, 1 ],
|
||||||
|
# TODO: Allow should probably rule stop execution
|
||||||
|
# -error => [ qr/DENIED/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Deny
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "deny in phase:1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:1,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "deny in phase:2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:2,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "deny in phase:3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:3,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "deny in phase:4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:4,deny,msg:'DENIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DENIED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Drop
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "drop in phase:1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:1,drop,msg:'DROPPED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "drop in phase:2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:2,drop,msg:'DROPPED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "drop in phase:3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:3,drop,msg:'DROPPED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "drop in phase:4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecAction "phase:4,drop,msg:'DROPPED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction.*DROPPED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Redirect
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "redirect in phase:1 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST 2$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "redirect in phase:2 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST 2$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "redirect in phase:3 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST 2$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "redirect in phase:4 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,redirect:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'REDIRECTED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*REDIRECTED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST 2$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Proxy
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "proxy in phase:1 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:1,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST 2$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "proxy in phase:2 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:2,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
content => qr/^TEST 2$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "proxy in phase:3 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:3,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "proxy in phase:4 (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecRule REQUEST_URI "\@streq /test2.txt" "phase:4,proxy:'http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt',msg:'PROXIED'"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. String match "\/test2.txt" at REQUEST_URI.*PROXIED/, 1 ],
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test2.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
248
apache2/t/regression/action/10-logging.t
Normal file
248
apache2/t/regression/action/10-logging.t
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
### Logging tests
|
||||||
|
|
||||||
|
# log/nolog
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "log",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,log"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "nolog",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,nolog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# auditlog/noauditlog
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "auditlog",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,auditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
audit => [ qr/Message: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "noauditlog",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,noauditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# All log/nolog auditlog/noauditlog combos
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "log,auditlog",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,log,auditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
audit => [ qr/Message: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "log,noauditlog",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,log,noauditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "nolog,auditlog",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,nolog,auditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
# No message, but should have data. This may need changed
|
||||||
|
audit => [ qr/-H--\s+Stopwatch: /s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "nolog,noauditlog",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,nolog,noauditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "auditlog,log",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,auditlog,log"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
audit => [ qr/Message: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "auditlog,nolog",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,auditlog,nolog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "noauditlog,log",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,noauditlog,log"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "action",
|
||||||
|
comment => "noauditlog,nolog",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAction "phase:1,pass,noauditlog,nolog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
23
apache2/t/regression/config/00-load-modsec.t
Normal file
23
apache2/t/regression/config/00-load-modsec.t
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "module loaded",
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity for Apache.* configured\./, 10 ],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "minimal config",
|
||||||
|
conf => sub {
|
||||||
|
# Open the minimal conf file, substituting the
|
||||||
|
# relative log paths with full paths.
|
||||||
|
open(C, "<$ENV{DIST_ROOT}/modsecurity.conf-minimal") or die "$!\n";
|
||||||
|
(my $conf = join('', <C>)) =~ s#Log logs/#Log $ENV{TEST_SERVER_ROOT}/logs/#g;
|
||||||
|
close C;
|
||||||
|
|
||||||
|
return $conf;
|
||||||
|
},
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity for Apache.* configured\./, 10 ],
|
||||||
|
},
|
||||||
|
},
|
||||||
264
apache2/t/regression/config/10-audit-directives.t
Normal file
264
apache2/t/regression/config/10-audit-directives.t
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
### SecAudit* directive tests
|
||||||
|
|
||||||
|
# SecAuditEngine
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditEngine On",
|
||||||
|
conf => qq(
|
||||||
|
SecAuditEngine On
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditEngine Off",
|
||||||
|
conf => qq(
|
||||||
|
SecAuditEngine Off
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditEngine RelevantOnly (pos)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecDefaultAction "phase:2,log,auditlog,pass"
|
||||||
|
SecRule REQUEST_URI "." "phase:4,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditEngine RelevantOnly (neg)",
|
||||||
|
conf => qq(
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecDefaultAction "phase:2,log,auditlog,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecAuditLogType & SecAuditLogStorageDir
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditLogType Serial",
|
||||||
|
conf => qq(
|
||||||
|
SecAuditEngine On
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
SecAuditLogType Serial
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^404$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/bogus",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditLogType Concurrent",
|
||||||
|
conf => qq(
|
||||||
|
SecAuditEngine On
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
SecAuditLogType Concurrent
|
||||||
|
SecAuditLogStorageDir "$ENV{LOGS_DIR}/audit"
|
||||||
|
),
|
||||||
|
test => sub {
|
||||||
|
### Perl code to parse the audit log entry and verify
|
||||||
|
### that the concurrent audit log exists and contains
|
||||||
|
### the correct data.
|
||||||
|
###
|
||||||
|
### TODO: Need some API for this :)
|
||||||
|
###
|
||||||
|
|
||||||
|
# Parse log
|
||||||
|
my $alogre = qr/^(?:\S+)\ (?:\S+)\ (?:\S+)\ (?:\S+)\ \[(?:[^:]+):(?:\d+:\d+:\d+)\ (?:[^\]]+)\]\ \"(?:.*)\"\ (?:\d+)\ (?:\S+)\ \"(?:.*)\"\ \"(?:.*)\"\ (\S+)\ \"(?:.*)\"\ (\S+)\ (?:\d+)\ (?:\d+)\ (?:\S+)(?:.*)$/m;
|
||||||
|
my $alog = match_log("audit", $alogre, 1);
|
||||||
|
chomp $alog;
|
||||||
|
my @log = ($alog =~ m/$alogre/);
|
||||||
|
my($id, $fn) = ($log[0], $log[1]);
|
||||||
|
if (!$id or !$fn) {
|
||||||
|
dbg("LOG ENTRY: $alog");
|
||||||
|
die "Failed to parse audit log: $ENV{AUDIT_LOG}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify concurrent log exists
|
||||||
|
my $alogdatafn = "$ENV{LOGS_DIR}/audit$fn";
|
||||||
|
if (! -e "$alogdatafn") {
|
||||||
|
die "Audit log does not exist: $alogdatafn\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify concurrent log contents
|
||||||
|
if (defined match_file($alogdatafn, qr/^--[^-]+-A--.*$id.*-Z--$/s)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Error
|
||||||
|
dbg("LOGDATA: \"$FILE{$alogdatafn}{buf}\"");
|
||||||
|
die "Audit log data did not match.\n";
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecAuditLogRelevantStatus
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditLogRelevantStatus (pos)",
|
||||||
|
conf => qq(
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
SecAuditLogRelevantStatus "^4"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^404$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/bogus",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditLogRelevantStatus (neg)",
|
||||||
|
conf => qq(
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
SecAuditLogRelevantStatus "^4"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecAuditLogParts
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditLogParts (minimal)",
|
||||||
|
conf => qq(
|
||||||
|
SecAuditEngine On
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecAuditLogParts "AZ"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/-A--.*-Z--/s, 1 ],
|
||||||
|
-audit => [ qr/-[B-Y]--/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1r&=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditLogParts (default)",
|
||||||
|
conf => qq(
|
||||||
|
SecAuditEngine On
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/-A--.*-B--.*-F--.*-H--.*-Z--/s, 1 ],
|
||||||
|
-audit => [ qr/-[DEGIJK]--/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1r&=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecAuditLogParts (all)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecAuditEngine On
|
||||||
|
SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecAuditLogParts "ABCDEFGHIJKZ"
|
||||||
|
SecAction "phase:4,log,auditlog,allow"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/-A--.*-B--.*-C--.*-F--.*-E--.*-H--.*-K--.*-Z--/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1r&=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
278
apache2/t/regression/config/10-debug-directives.t
Normal file
278
apache2/t/regression/config/10-debug-directives.t
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
### SecDebug* directive tests
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLog (pos)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLog (neg)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-debug => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 0",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 0
|
||||||
|
SecRule REQUEST_URI "." "phase:1,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-debug => [ qr/./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 1
|
||||||
|
SecRuleScript "test.lua" "phase:1"
|
||||||
|
SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/\]\[[1]\] /, 1 ],
|
||||||
|
-debug => [ qr/\]\[[2-9]\] /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine DetectionOnly
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 2
|
||||||
|
SecRuleScript "test.lua" "phase:1"
|
||||||
|
SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/\]\[2\] /, 1 ],
|
||||||
|
-debug => [ qr/\]\[[3-9]\] /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 3
|
||||||
|
SecRuleScript "test.lua" "phase:1"
|
||||||
|
SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/\]\[3\] /, 1 ],
|
||||||
|
-debug => [ qr/\]\[[4-9]\] /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecRuleScript "test.lua" "phase:1"
|
||||||
|
SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/\]\[4\] /, 1 ],
|
||||||
|
-debug => [ qr/\]\[[5-9]\] /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 5",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 5
|
||||||
|
SecRuleScript "test.lua" "phase:1"
|
||||||
|
SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/\]\[5\] /, 1 ],
|
||||||
|
-debug => [ qr/\]\[[6-9]\] /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 6",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 6
|
||||||
|
SecRuleScript "test.lua" "phase:1"
|
||||||
|
SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/\]\[6\] /, 1 ],
|
||||||
|
-debug => [ qr/\]\[[7-9]\] /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 7",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 7
|
||||||
|
SecRuleScript "test.lua" "phase:1"
|
||||||
|
SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/\]\[7\] /, 1 ],
|
||||||
|
-debug => [ qr/\]\[[8-9]\] /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 8",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 8
|
||||||
|
SecRuleScript "test.lua" "phase:1"
|
||||||
|
SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/\]\[8\] /, 1 ],
|
||||||
|
-debug => [ qr/\]\[9\] /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDebugLogLevel 9",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRuleScript "test.lua" "phase:1"
|
||||||
|
SecRule REQUEST_URI "(.)" "phase:4,deny,deprecatevar:bogus"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/\]\[9\] /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
148
apache2/t/regression/config/10-misc-directives.t
Normal file
148
apache2/t/regression/config/10-misc-directives.t
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
### Misc directive tests
|
||||||
|
|
||||||
|
### TODO:
|
||||||
|
# SecTmpDir
|
||||||
|
# SecUploadKeepFiles
|
||||||
|
# SecChrootDir
|
||||||
|
# SecGuardianLog
|
||||||
|
|
||||||
|
# SecDefaultAction
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDefaultAction",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine on
|
||||||
|
SecDefaultAction "phase:1,deny,status:500"
|
||||||
|
SecRule REQUEST_URI "test.txt"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied with code 500 \(phase 1\)/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^500$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecServerSignature
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecServerSignature On",
|
||||||
|
conf => qq(
|
||||||
|
SecServerSignature "NewServerSignature"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/NewServerSignature/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
raw => qr/^Server: +NewServerSignature$/m,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecDataDir
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecDataDir",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDataDir "$ENV{DATA_DIR}"
|
||||||
|
SecAction initcol:ip=%{REMOTE_ADDR},setvar:ip.dummy=1,pass
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Warning. Unconditional match in SecAction\./, 1 ],
|
||||||
|
},
|
||||||
|
match_file => {
|
||||||
|
"$ENV{DATA_DIR}/ip.pag" => qr/\x00\x06dummy\x00\x00\x021\x00/,
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecTmpDir/SecUploadDir/SecUploadKeepFiles
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecTmpDir/SecUploadDir/SecUploadKeepFiles",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecTmpDir "$ENV{TEMP_DIR}"
|
||||||
|
SecUploadKeepFiles On
|
||||||
|
SecUploadDir "$ENV{UPLOAD_DIR}"
|
||||||
|
),
|
||||||
|
test => sub {
|
||||||
|
# Get the filename and make sure the file exists
|
||||||
|
my $fn = match_log(debug => qr/Moved file from .* to ".*"\./, 5);
|
||||||
|
die "Failed to determine uploaded filename\n" unless (defined $fn);
|
||||||
|
|
||||||
|
$fn =~ s/Moved file from .* to "(.*)"\..*/$1/;
|
||||||
|
die "File does not exist: $fn\n" unless (-e $fn);
|
||||||
|
|
||||||
|
# Check the contents of the file
|
||||||
|
return 0 if (match_file($fn, qr/^TESTFILE$/m));
|
||||||
|
|
||||||
|
msg("Failed to match contents of uploaded file: $fn");
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/Created temporary file: $ENV{TEMP_DIR}/, 1 ],
|
||||||
|
-debug => [ qr/Failed to /, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------19813181771830765643996187206",
|
||||||
|
],
|
||||||
|
q(-----------------------------19813181771830765643996187206
|
||||||
|
Content-Disposition: form-data; name="upload-file"; filename="test"
|
||||||
|
Content-Type: application/octet-stream
|
||||||
|
|
||||||
|
TESTFILE
|
||||||
|
-----------------------------19813181771830765643996187206
|
||||||
|
Content-Disposition: form-data; name="file"
|
||||||
|
|
||||||
|
Upload File
|
||||||
|
-----------------------------19813181771830765643996187206--),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecWebAppId
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecWebAppId",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
SecWebAppId "app-1"
|
||||||
|
SecAction "pass,log,auditlog,id:1"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Warning\. Unconditional match in SecAction\./, 1 ],
|
||||||
|
debug => [ qr/Warning\. Unconditional match in SecAction\./, 1 ],
|
||||||
|
audit => [ qr/^WebApp-Info: "app-1"/m, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
328
apache2/t/regression/config/10-request-directives.t
Normal file
328
apache2/t/regression/config/10-request-directives.t
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
### Tests for directives altering how a request is handled
|
||||||
|
|
||||||
|
# SecArgumentSeparator
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecArgumentSeparator (get-pos)",
|
||||||
|
conf => q(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecArgumentSeparator ";"
|
||||||
|
SecRule ARGS:a "@streq 1" "phase:1,deny,chain"
|
||||||
|
SecRule ARGS:b "@streq 2"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with code 403 \(phase 1\)\. String match "2" at ARGS:b\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?a=1;b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecArgumentSeparator (get-neg)",
|
||||||
|
conf => q(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRule ARGS:a "@streq 1" "phase:1,deny,chain"
|
||||||
|
SecRule ARGS:b "@streq 2"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?a=1;b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecArgumentSeparator (post-pos)",
|
||||||
|
conf => q(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecArgumentSeparator ";"
|
||||||
|
SecRule ARGS:a "@streq 1" "phase:2,deny,chain"
|
||||||
|
SecRule ARGS:b "@streq 2"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with code 403 \(phase 2\)\. String match "2" at ARGS:b\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1;b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecArgumentSeparator (post-neg)",
|
||||||
|
conf => q(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRule ARGS:a "@streq 1" "phase:2,deny"
|
||||||
|
SecRule ARGS:b "@streq 2" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1;b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecRequestBodyAccess
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRequestBodyAccess (pos)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRule ARGS:a "\@streq 1" "phase:2,deny,chain"
|
||||||
|
SecRule ARGS:b "\@streq 2"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with code 403 \(phase 2\)\. String match "2" at ARGS:b\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRequestBodyAccess (neg)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess Off
|
||||||
|
SecRule ARGS:a "\@streq 1" "phase:2,deny"
|
||||||
|
SecRule ARGS:b "\@streq 2" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecRequestBodyLimit
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRequestBodyLimit (equal)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRequestBodyLimit 7
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Request body is larger than the configured limit/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRequestBodyLimit (greater)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRequestBodyLimit 5
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Request body is larger than the configured limit \(5\)\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^413$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"a=1&b=2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecRequestBodyInMemoryLimit
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRequestBodyInMemoryLimit (equal)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRequestBodyLimit 1000
|
||||||
|
SecRequestBodyInMemoryLimit 276
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-debug => [ qr/Input filter: Request too large to store in memory, switching to disk\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => normalize_raw_request_data(
|
||||||
|
qq(
|
||||||
|
POST /test.txt HTTP/1.1
|
||||||
|
Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT}
|
||||||
|
User-Agent: $ENV{USER_AGENT}
|
||||||
|
Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646
|
||||||
|
Transfer-Encoding: chunked
|
||||||
|
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.encode_chunked(
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="b"
|
||||||
|
|
||||||
|
2
|
||||||
|
-----------------------------69343412719991675451336310646--
|
||||||
|
)
|
||||||
|
),
|
||||||
|
1024
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRequestBodyInMemoryLimit (greater)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRequestBodyLimit 1000
|
||||||
|
SecRequestBodyInMemoryLimit 16
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/Input filter: Request too large to store in memory, switching to disk\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => normalize_raw_request_data(
|
||||||
|
qq(
|
||||||
|
POST /test.txt HTTP/1.1
|
||||||
|
Host: $ENV{SERVER_NAME}:$ENV{SERVER_PORT}
|
||||||
|
User-Agent: $ENV{USER_AGENT}
|
||||||
|
Content-Type: multipart/form-data; boundary=---------------------------69343412719991675451336310646
|
||||||
|
Transfer-Encoding: chunked
|
||||||
|
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.encode_chunked(
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="b"
|
||||||
|
|
||||||
|
2
|
||||||
|
-----------------------------69343412719991675451336310646--
|
||||||
|
)
|
||||||
|
),
|
||||||
|
1024
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecCookieFormat
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecCookieFormat (pos)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 5
|
||||||
|
SecCookieFormat 1
|
||||||
|
SecRule REQUEST_COOKIES_NAMES "\@streq SESSIONID" "phase:1,deny,chain"
|
||||||
|
SecRule REQUEST_COOKIES:\$SESSIONID_PATH "\@streq /" "chain"
|
||||||
|
SecRule REQUEST_COOKIES:SESSIONID "\@streq cookieval"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with code 403 \(phase 1\)\. String match "cookieval" at REQUEST_COOKIES:SESSIONID\./, 1 ],
|
||||||
|
debug => [ qr(Adding request cookie: name "\$SESSIONID_PATH", value "/"), 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Cookie" => q($Version="1"; SESSIONID="cookieval"; $PATH="/"),
|
||||||
|
],
|
||||||
|
undef,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecCookieFormat (neg)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 5
|
||||||
|
SecCookieFormat 0
|
||||||
|
SecRule REQUEST_COOKIES_NAMES "\@streq SESSIONID" "phase:1,deny,chain"
|
||||||
|
SecRule REQUEST_COOKIES:\$SESSIONID_PATH "\@streq /" "chain"
|
||||||
|
SecRule REQUEST_COOKIES:SESSIONID "\@streq cookieval"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
-debug => [ qr(Adding request cookie: name "\$SESSIONID_PATH", value "/"), 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Cookie" => q($Version="1"; SESSIONID="cookieval"; $PATH="/"),
|
||||||
|
],
|
||||||
|
undef,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
174
apache2/t/regression/config/10-response-directives.t
Normal file
174
apache2/t/regression/config/10-response-directives.t
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
### Tests for directives altering how a response is handled
|
||||||
|
|
||||||
|
# SecResponseBodyMimeTypesClear
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecResponseBodyMimeTypesClear",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeTypesClear
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule RESPONSE_BODY "TEST" "phase:4,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
debug => [ qr/Not buffering response body for unconfigured MIME type/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecResponseBodyAccess & SecResponseBodyMimeType
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecResponseBodyAccess On",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecRule RESPONSE_BODY "TEST" "phase:4,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Access denied with code 403 \(phase 4\)\. Pattern match "TEST" at RESPONSE_BODY\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecResponseBodyAccess Off",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecResponseBodyAccess Off
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecRule RESPONSE_BODY "TEST" "phase:4,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Access denied/, 1 ],
|
||||||
|
debug => [ qr/Response body buffering is not enabled\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecResponseBodyLimit
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecResponseBodyLimit (equal)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecResponseBodyLimit 8192
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Content-Length \(\d+\) over the limit/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecResponseBodyLimit (less)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecResponseBodyLimit 9000
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Content-Length \(\d+\) over the limit/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecResponseBodyLimit (greater)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecResponseBodyLimit 8000
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Content-Length \(\d+\) over the limit \(8000\)\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^500$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# ResponseBodyLimitAction
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecResponseBodyLimitAction Reject",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecResponseBodyLimit 5
|
||||||
|
SecResponseBodyLimitAction Reject
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Content-Length \(\d+\) over the limit \(5\)\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^500$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecResponseBodyLimitAction ProcessPartial",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecResponseBodyLimit 5
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecResponseBodyLimitAction ProcessPartial
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Content-Length \(\d+\) over the limit/, 1 ],
|
||||||
|
debug => [ qr/Processing partial response body \(limit 5\)/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/8k.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
35
apache2/t/regression/config/20-chroot.t
Normal file
35
apache2/t/regression/config/20-chroot.t
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
### SecChroot tests
|
||||||
|
# TODO: Will not work as we need root access
|
||||||
|
|
||||||
|
#{
|
||||||
|
# type => "config",
|
||||||
|
# comment => "SecChroot",
|
||||||
|
# httpd_opts => qw(
|
||||||
|
# -DCHROOT
|
||||||
|
# ),
|
||||||
|
# conf => qq(
|
||||||
|
# # These will be in the chroot
|
||||||
|
# PidFile /logs/httpd.pid
|
||||||
|
# ScoreBoardFile /logs/httpd.scoreboard
|
||||||
|
# User nobody
|
||||||
|
# Group nogroup
|
||||||
|
#
|
||||||
|
# SecAuditEngine On
|
||||||
|
# SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
# SecDebugLogLevel 9
|
||||||
|
# SecAuditLog $ENV{AUDIT_LOG}
|
||||||
|
# SecAuditLogStorageDir "/logs/audit"
|
||||||
|
# SecAuditLogType Concurrent
|
||||||
|
# SecChrootDir "$ENV{TEST_SERVER_ROOT}"
|
||||||
|
# ),
|
||||||
|
# match_log => {
|
||||||
|
# debug => [ qr/./, 1 ],
|
||||||
|
# audit => [ qr/./, 1 ],
|
||||||
|
# },
|
||||||
|
# match_response => {
|
||||||
|
# status => qr/^200$/,
|
||||||
|
# },
|
||||||
|
# request => new HTTP::Request(
|
||||||
|
# GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
# ),
|
||||||
|
#},
|
||||||
364
apache2/t/regression/misc/00-multipart-parser.t
Normal file
364
apache2/t/regression/misc/00-multipart-parser.t
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
### Multipart parser tests
|
||||||
|
|
||||||
|
# Final CRLF or not, we should still work
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser (final CRLF)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny"
|
||||||
|
SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "a", value "1".*Adding request argument \(BODY\): name "b", value "2"/s, 1 ],
|
||||||
|
-debug => [ qr/Multipart error:/, 1 ],
|
||||||
|
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="b"
|
||||||
|
|
||||||
|
2
|
||||||
|
-----------------------------69343412719991675451336310646--
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser (no final CRLF)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny"
|
||||||
|
SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "a", value "1".*Adding request argument \(BODY\): name "b", value "2"/s, 1 ],
|
||||||
|
-debug => [ qr/Multipart error:/, 1 ],
|
||||||
|
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="b"
|
||||||
|
|
||||||
|
2
|
||||||
|
-----------------------------69343412719991675451336310646--),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Should work with a boundary of "boundary"
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser (boundary contains \"boundary\")",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny"
|
||||||
|
SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "a", value "1".*Adding request argument \(BODY\): name "b", value "2"/s, 1 ],
|
||||||
|
-debug => [ qr/Multipart error:/, 1 ],
|
||||||
|
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=------------------------------------------------boundary",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
--------------------------------------------------boundary
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
--------------------------------------------------boundary
|
||||||
|
Content-Disposition: form-data; name="b"
|
||||||
|
|
||||||
|
2
|
||||||
|
--------------------------------------------------boundary--
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser (boundary contains \"bOuNdArY\")",
|
||||||
|
note => q(
|
||||||
|
KHTML Boundary
|
||||||
|
),
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
SecRule MULTIPART_UNMATCHED_BOUNDARY "\@eq 1" "phase:2,deny"
|
||||||
|
SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "a", value "1".*Adding request argument \(BODY\): name "b", value "2"/s, 1 ],
|
||||||
|
-debug => [ qr/Multipart error:/, 1 ],
|
||||||
|
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=--------0xKhTmLbOuNdArY",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
----------0xKhTmLbOuNdArY
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
----------0xKhTmLbOuNdArY
|
||||||
|
Content-Disposition: form-data; name="b"
|
||||||
|
|
||||||
|
2
|
||||||
|
----------0xKhTmLbOuNdArY--
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# We should handle data starting with a "--"
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser (data contains \"--\")",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecRule MULTIPART_STRICT_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
SecRule REQBODY_PROCESSOR_ERROR "\@eq 1" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "a", value "--test".*Adding request argument \(BODY\): name "b", value "--"/s, 1 ],
|
||||||
|
-debug => [ qr/Multipart error:/, 1 ],
|
||||||
|
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
--test
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="b"
|
||||||
|
|
||||||
|
--
|
||||||
|
-----------------------------69343412719991675451336310646--),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# We should emit warnings for parsing errors
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser error (no final boundary)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/Final boundary missing/, 1 ],
|
||||||
|
debug => [ qr/Final boundary missing/, 1 ],
|
||||||
|
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data; name="b"
|
||||||
|
|
||||||
|
2
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser error (no disposition)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-debug => [ qr/Multipart error:/, 1 ],
|
||||||
|
audit => [ qr/Part missing Content-Disposition header/, 1 ],
|
||||||
|
debug => [ qr/Part missing Content-Disposition header/, 1 ],
|
||||||
|
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
|
||||||
|
2
|
||||||
|
-----------------------------69343412719991675451336310646--
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser error (bad disposition)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
audit => [ qr/Invalid Content-Disposition header/, 1 ],
|
||||||
|
debug => [ qr/Invalid Content-Disposition header/, 1 ],
|
||||||
|
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data name="a"
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data name="b"
|
||||||
|
|
||||||
|
2
|
||||||
|
-----------------------------69343412719991675451336310646--
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "multipart parser error (no disposition name)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecAuditLog "$ENV{AUDIT_LOG}"
|
||||||
|
SecAuditEngine RelevantOnly
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-debug => [ qr/Multipart error:/, 1 ],
|
||||||
|
audit => [ qr/Content-Disposition header missing name field/, 1 ],
|
||||||
|
debug => [ qr/Content-Disposition header missing name field/, 1 ],
|
||||||
|
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "multipart/form-data; boundary=---------------------------69343412719991675451336310646",
|
||||||
|
],
|
||||||
|
normalize_raw_request_data(
|
||||||
|
q(
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data;
|
||||||
|
|
||||||
|
1
|
||||||
|
-----------------------------69343412719991675451336310646
|
||||||
|
Content-Disposition: form-data;
|
||||||
|
|
||||||
|
2
|
||||||
|
-----------------------------69343412719991675451336310646--
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
151
apache2/t/regression/misc/00-phases.t
Normal file
151
apache2/t/regression/misc/00-phases.t
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
### Test the phases
|
||||||
|
|
||||||
|
# Phase 1 (request headers)
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "phase 1",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecRule REQUEST_LINE "^POST" "phase:1,pass,log,auditlog"
|
||||||
|
SecRule ARGS "val1" "phase:1,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_HEADERS:Last-Modified "." "phase:1,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_BODY "TEST" "phase:1,pass,log,auditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "\^POST" at REQUEST_LINE/, 1 ],
|
||||||
|
-error => [ qr/Pattern match .* (ARGS|RESPONSE)/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Phase 2 (request body)
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "phase 2",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecRule REQUEST_LINE "^POST" "phase:2,pass,log,auditlog"
|
||||||
|
SecRule ARGS "val1" "phase:2,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_HEADERS:Last-Modified "." "phase:2,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_BODY "TEST" "phase:2,pass,log,auditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS/s, 1 ],
|
||||||
|
-error => [ qr/Pattern match .* RESPONSE/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Phase 3 (response headers)
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "phase 3",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecRule REQUEST_LINE "^POST" "phase:3,pass,log,auditlog"
|
||||||
|
SecRule ARGS "val1" "phase:3,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_HEADERS:Last-Modified "." "phase:3,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_BODY "TEST" "phase:3,pass,log,auditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS.*Pattern match "\." at RESPONSE_HEADERS/s, 1 ],
|
||||||
|
-error => [ qr/Pattern match .* RESPONSE_BODY/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Phase 4 (response body)
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "phase 4",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecDebugLog "$ENV{DEBUG_LOG}"
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule REQUEST_LINE "^POST" "phase:4,pass,log,auditlog"
|
||||||
|
SecRule ARGS "val1" "phase:4,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_HEADERS:Last-Modified "." "phase:4,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_BODY "TEST" "phase:4,pass,log,auditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS.*Pattern match "\." at RESPONSE_HEADERS.*Pattern match "TEST" at RESPONSE_BODY/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# Phase 5 (logging)
|
||||||
|
{
|
||||||
|
type => "misc",
|
||||||
|
comment => "phase 5",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType text/plain null
|
||||||
|
SecRule REQUEST_LINE "^POST" "phase:5,pass,log,auditlog"
|
||||||
|
SecRule ARGS "val1" "phase:5,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_HEADERS:Last-Modified "." "phase:5,pass,log,auditlog"
|
||||||
|
SecRule RESPONSE_BODY "TEST" "phase:5,pass,log,auditlog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "\^POST" at REQUEST_LINE.*Pattern match "val1" at ARGS.*Pattern match "\." at RESPONSE_HEADERS.*Pattern match "TEST" at RESPONSE_BODY/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
24
apache2/t/regression/rule/00-basics.t
Normal file
24
apache2/t/regression/rule/00-basics.t
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
### Tests for basic rule components
|
||||||
|
|
||||||
|
# SecAction
|
||||||
|
{
|
||||||
|
type => "rule",
|
||||||
|
comment => "SecAction (override default)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 4
|
||||||
|
SecAction "nolog"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
debug => [ qr/Warning\. Unconditional match in SecAction\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
4
apache2/t/regression/rule/00-inheritance.t
Normal file
4
apache2/t/regression/rule/00-inheritance.t
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
### Tests for rule inheritance
|
||||||
|
|
||||||
|
### TODO:
|
||||||
|
# SecRuleInheritance
|
||||||
63
apache2/t/regression/rule/00-script.t
Normal file
63
apache2/t/regression/rule/00-script.t
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
### Test for SecRuleScript
|
||||||
|
|
||||||
|
# Lua
|
||||||
|
{
|
||||||
|
type => "rule",
|
||||||
|
comment => "SecRuleScript (lua absolute nomatch)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 1
|
||||||
|
SecRuleScript "$ENV{CONF_DIR}/test.lua" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Lua script matched\./, 1 ],
|
||||||
|
debug => [ qr/Test message\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "rule",
|
||||||
|
comment => "SecRuleScript (lua relative nomatch)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 1
|
||||||
|
SecRuleScript "test.lua" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Lua script matched\./, 1 ],
|
||||||
|
debug => [ qr/Test message\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "rule",
|
||||||
|
comment => "SecRuleScript (lua relative match)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 1
|
||||||
|
SecRuleScript "match.lua" "phase:2,deny"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/ModSecurity: Access denied with code 403 \(phase 2\)\. Lua script matched\./, 1 ],
|
||||||
|
debug => [ qr/Test message\./, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^403$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
129
apache2/t/regression/rule/20-exceptions.t
Normal file
129
apache2/t/regression/rule/20-exceptions.t
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
### Tests for rule exceptions
|
||||||
|
|
||||||
|
# SecRuleRemoveByMsg
|
||||||
|
|
||||||
|
# SecRuleRemoveById
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRuleRemoveById (single)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:1"
|
||||||
|
SecRuleRemoveById 1
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ],
|
||||||
|
-debug => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRuleRemoveById (multiple)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:1"
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:2"
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:3"
|
||||||
|
SecRuleRemoveById 1 2 3
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ],
|
||||||
|
-debug => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRuleRemoveById (range)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:1"
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:2"
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:3"
|
||||||
|
SecRuleRemoveById 1-3
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ],
|
||||||
|
-debug => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRuleRemoveById (multiple + range)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:1"
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:2"
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:3"
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:4"
|
||||||
|
SecRuleRemoveById 1 2-4
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ],
|
||||||
|
-debug => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# SecRuleRemoveByMsg
|
||||||
|
{
|
||||||
|
type => "config",
|
||||||
|
comment => "SecRuleRemoveByMsg",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule REQUEST_URI "test" "phase:1,deny,status:500,id:1,msg:'testing rule'"
|
||||||
|
SecRuleRemoveByMsg "testing rule"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/ModSecurity: /, 1 ],
|
||||||
|
-audit => [ qr/./, 1 ],
|
||||||
|
debug => [ qr/Starting phase REQUEST_HEADERS\..*This phase consists of 0 rule.*Starting phase RESPONSE_HEADERS\./s, 1 ],
|
||||||
|
-debug => [ qr/Access denied/, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
),
|
||||||
|
},
|
||||||
35
apache2/t/regression/server_root/conf/httpd.conf.in
Normal file
35
apache2/t/regression/server_root/conf/httpd.conf.in
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
### Base configuration for starting Apache httpd
|
||||||
|
|
||||||
|
<IfDefine !CHROOT>
|
||||||
|
# File locations
|
||||||
|
PidFile @MSC_REGRESSION_LOGS_DIR@/httpd.pid
|
||||||
|
ScoreBoardFile @MSC_REGRESSION_LOGS_DIR@/httpd.scoreboard
|
||||||
|
</IfDefine>
|
||||||
|
|
||||||
|
<IfModule !mod_proxy.c>
|
||||||
|
LoadModule proxy_module modules/mod_proxy.so
|
||||||
|
LoadModule proxy_http_module modules/mod_proxy_http.so
|
||||||
|
</IfModule>
|
||||||
|
<IfModule !mod_unique_id.c>
|
||||||
|
LoadModule unique_id_module modules/mod_unique_id.so
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
<IfDefine !NOMODSEC>
|
||||||
|
# TODO: Need to have these configurable
|
||||||
|
LoadFile /usr/lib/libxml2.so
|
||||||
|
LoadFile /usr/lib/liblua5.1.so
|
||||||
|
LoadModule security2_module modules/mod_security2.so
|
||||||
|
</IfDefine>
|
||||||
|
|
||||||
|
ServerName localhost
|
||||||
|
|
||||||
|
LogLevel debug
|
||||||
|
ErrorLog @MSC_REGRESSION_LOGS_DIR@/error.log
|
||||||
|
|
||||||
|
<IfDefine !CHROOT>
|
||||||
|
DocumentRoot @MSC_REGRESSION_DOCROOT_DIR@
|
||||||
|
<Directory "@MSC_REGRESSION_DOCROOT_DIR@">
|
||||||
|
Options Indexes FollowSymLinks
|
||||||
|
AllowOverride None
|
||||||
|
</Directory>
|
||||||
|
</IfDefine>
|
||||||
14
apache2/t/regression/server_root/conf/match.lua
Normal file
14
apache2/t/regression/server_root/conf/match.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- Test matching Lua Script to just print debug messages
|
||||||
|
function main()
|
||||||
|
m.log(1, "Test message.");
|
||||||
|
m.log(2, "Test message.");
|
||||||
|
m.log(3, "Test message.");
|
||||||
|
m.log(4, "Test message.");
|
||||||
|
m.log(5, "Test message.");
|
||||||
|
m.log(6, "Test message.");
|
||||||
|
m.log(7, "Test message.");
|
||||||
|
m.log(8, "Test message.");
|
||||||
|
m.log(9, "Test message.");
|
||||||
|
|
||||||
|
return "Lua script matched.";
|
||||||
|
end
|
||||||
14
apache2/t/regression/server_root/conf/test.lua
Normal file
14
apache2/t/regression/server_root/conf/test.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-- Test Lua Script to just print debug messages
|
||||||
|
function main()
|
||||||
|
m.log(1, "Test message.");
|
||||||
|
m.log(2, "Test message.");
|
||||||
|
m.log(3, "Test message.");
|
||||||
|
m.log(4, "Test message.");
|
||||||
|
m.log(5, "Test message.");
|
||||||
|
m.log(6, "Test message.");
|
||||||
|
m.log(7, "Test message.");
|
||||||
|
m.log(8, "Test message.");
|
||||||
|
m.log(9, "Test message.");
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
BIN
apache2/t/regression/server_root/htdocs/8k.txt
Normal file
BIN
apache2/t/regression/server_root/htdocs/8k.txt
Normal file
Binary file not shown.
1
apache2/t/regression/server_root/htdocs/index.html
Normal file
1
apache2/t/regression/server_root/htdocs/index.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
INDEX
|
||||||
1
apache2/t/regression/server_root/htdocs/test.txt
Normal file
1
apache2/t/regression/server_root/htdocs/test.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
TEST
|
||||||
1
apache2/t/regression/server_root/htdocs/test2.txt
Normal file
1
apache2/t/regression/server_root/htdocs/test2.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
TEST 2
|
||||||
487
apache2/t/regression/target/00-targets.t
Normal file
487
apache2/t/regression/target/00-targets.t
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
### Test basic targets
|
||||||
|
|
||||||
|
# ARGS
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS "val1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS "val2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "val1" at ARGS.*Pattern match "val2" at ARGS/s, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS (post)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS "val1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS "val2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "val1" at ARGS.*Pattern match "val2" at ARGS/s, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# ARGS_COMBINED_SIZE
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_COMBINED_SIZE (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule ARGS_COMBINED_SIZE "\@eq 16" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Operator EQ matched 16 at ARGS_COMBINED_SIZE\./s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_COMBINED_SIZE (post)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecRule ARGS_COMBINED_SIZE "\@eq 16" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Operator EQ matched 16 at ARGS_COMBINED_SIZE\./s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# ARGS_NAMES
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_NAMES (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_NAMES "arg1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_NAMES "arg2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "arg1" at ARGS.*Pattern match "arg2" at ARGS/s, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_NAMES (post)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_NAMES "arg1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_NAMES "arg2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "arg1" at ARGS_NAMES.*Pattern match "arg2" at ARGS_NAMES/s, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# ARGS_GET
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_GET (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_GET "val1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_GET "val2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "val1" at ARGS_GET.*Pattern match "val2" at ARGS_GET/s, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_GET (post)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_GET "val1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_GET "val2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Pattern match/, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# ARGS_GET_NAMES
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_GET_NAMES (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_GET_NAMES "arg1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_GET_NAMES "arg2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "arg1" at ARGS_GET.*Pattern match "arg2" at ARGS_GET/s, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_GET_NAMES (post)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_GET_NAMES "arg1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_GET_NAMES "arg2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Pattern match/, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# ARGS_POST
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_POST (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_POST "val1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_POST "val2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Pattern match/, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_POST (post)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_POST "val1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_POST "val2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "val1" at ARGS_POST.*Pattern match "val2" at ARGS_POST/s, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# ARGS_POST_NAMES
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_POST_NAMES (get)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_POST_NAMES "arg1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_POST_NAMES "arg2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
-error => [ qr/Pattern match/, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(QUERY_STRING\): name "arg1", value "val1".*Adding request argument \(QUERY_STRING\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt?arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type => "target",
|
||||||
|
comment => "ARGS_POST_NAMES (post)",
|
||||||
|
conf => qq(
|
||||||
|
SecRuleEngine On
|
||||||
|
SecRequestBodyAccess On
|
||||||
|
SecResponseBodyAccess On
|
||||||
|
SecResponseBodyMimeType null
|
||||||
|
SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
SecDebugLogLevel 9
|
||||||
|
SecRule ARGS_POST_NAMES "arg1" "phase:2,log,pass"
|
||||||
|
SecRule ARGS_POST_NAMES "arg2" "phase:2,log,pass"
|
||||||
|
),
|
||||||
|
match_log => {
|
||||||
|
error => [ qr/Pattern match "arg1" at ARGS_POST.*Pattern match "arg2" at ARGS_POST/s, 1 ],
|
||||||
|
debug => [ qr/Adding request argument \(BODY\): name "arg1", value "val1".*Adding request argument \(BODY\): name "arg2", value "val2"/s, 1 ],
|
||||||
|
},
|
||||||
|
match_response => {
|
||||||
|
status => qr/^200$/,
|
||||||
|
},
|
||||||
|
request => new HTTP::Request(
|
||||||
|
POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
[
|
||||||
|
"Content-Type" => "application/x-www-form-urlencoded",
|
||||||
|
],
|
||||||
|
"arg1=val1&arg2=val2",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
# AUTH_TYPE
|
||||||
|
#{
|
||||||
|
# type => "target",
|
||||||
|
# comment => "AUTH_TYPE",
|
||||||
|
# conf => qq(
|
||||||
|
# <IfVersion >= 2.2>
|
||||||
|
# <IfModule !mod_authn_file.c>
|
||||||
|
# LoadModule authn_file_module modules/mod_authn_file.so
|
||||||
|
# </IfModule>
|
||||||
|
# </IfVersion>
|
||||||
|
## <IfVersion ~ ^2.0.>
|
||||||
|
## <IfModule !mod_auth.c>
|
||||||
|
## LoadModule auth_module modules/mod_auth.so
|
||||||
|
## </IfModule>
|
||||||
|
## </IfVersion>
|
||||||
|
# <Location />
|
||||||
|
# AuthType Basic
|
||||||
|
# AuthName Test
|
||||||
|
# AuthUserFile "$ENV{CONF_DIR}/htpasswd"
|
||||||
|
# Require user nobody
|
||||||
|
# </Location>
|
||||||
|
# SecRuleEngine On
|
||||||
|
# SecRequestBodyAccess On
|
||||||
|
# SecResponseBodyAccess On
|
||||||
|
# SecResponseBodyMimeType null
|
||||||
|
## SecDebugLog $ENV{DEBUG_LOG}
|
||||||
|
## SecDebugLogLevel 9
|
||||||
|
# SecRule REQUEST_HEADERS:Authorization "Basic (.*)" "phase:2,log,pass,capture,chain"
|
||||||
|
# SecRule TX:1 "nobody:test" "t:none,t:base64Decode,chain"
|
||||||
|
# SecRule AUTH_TYPE "Basic"
|
||||||
|
# ),
|
||||||
|
# match_log => {
|
||||||
|
# error => [ qr/Pattern match "Basic" at AUTH_TYPE/s, 1 ],
|
||||||
|
# },
|
||||||
|
# match_response => {
|
||||||
|
# status => qr/^200$/,
|
||||||
|
# },
|
||||||
|
# request => new HTTP::Request(
|
||||||
|
# GET => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt",
|
||||||
|
# [
|
||||||
|
# "Authorization" => "Basic bm9ib2R5OnRlc3Q="
|
||||||
|
# ],
|
||||||
|
# ),
|
||||||
|
#},
|
||||||
|
|
||||||
|
# TODO: ENV
|
||||||
|
# TODO: FILES
|
||||||
|
# TODO: FILES_COMBINED_SIZE
|
||||||
|
# TODO: FILES_NAMES
|
||||||
|
# TODO: FILES_SIZES
|
||||||
|
# TODO: FILES_TMPNAMES
|
||||||
|
# TODO: GEO
|
||||||
|
# TODO: HIGHEST_SEVERITY
|
||||||
|
# TODO: MATCHED_VAR
|
||||||
|
# TODO: MATCHED_VAR_NAME
|
||||||
|
# TODO: MODSEC_BUILD
|
||||||
|
# TODO: MULTIPART_CRLF_LF_LINES
|
||||||
|
# TODO: MULTIPART_STRICT_ERROR
|
||||||
|
# TODO: MULTIPART_UNMATCHED_BOUNDARY
|
||||||
|
# TODO: PATH_INFO
|
||||||
|
# TODO: QUERY_STRING
|
||||||
|
# TODO: REMOTE_ADDR
|
||||||
|
# TODO: REMOTE_HOST
|
||||||
|
# TODO: REMOTE_PORT
|
||||||
|
# TODO: REMOTE_USER
|
||||||
|
# TODO: REQBODY_PROCESSOR
|
||||||
|
# TODO: REQBODY_PROCESSOR_ERROR
|
||||||
|
# TODO: REQBODY_PROCESSOR_ERROR_MSG
|
||||||
|
# TODO: REQUEST_BASENAME
|
||||||
|
# TODO: REQUEST_BODY
|
||||||
|
# TODO: REQUEST_COOKIES
|
||||||
|
# TODO: REQUEST_COOKIES_NAMES
|
||||||
|
# TODO: REQUEST_FILENAME
|
||||||
|
# TODO: REQUEST_HEADERS
|
||||||
|
# TODO: REQUEST_HEADERS_NAMES
|
||||||
|
# TODO: REQUEST_LINE
|
||||||
|
# TODO: REQUEST_METHOD
|
||||||
|
# TODO: REQUEST_PROTOCOL
|
||||||
|
# TODO: REQUEST_URI
|
||||||
|
# TODO: REQUEST_URI_RAW
|
||||||
|
# TODO: RESPONSE_BODY
|
||||||
|
# TODO: RESPONSE_CONTENT_LENGTH
|
||||||
|
# TODO: RESPONSE_CONTENT_TYPE
|
||||||
|
# TODO: RESPONSE_HEADERS
|
||||||
|
# TODO: RESPONSE_HEADERS_NAMES
|
||||||
|
# TODO: RESPONSE_PROTOCOL
|
||||||
|
# TODO: RESPONSE_STATUS
|
||||||
|
# TODO: RULE
|
||||||
|
# TODO: SCRIPT_BASENAME
|
||||||
|
# TODO: SCRIPT_FILENAME
|
||||||
|
# TODO: SCRIPT_GID
|
||||||
|
# TODO: SCRIPT_GROUPNAME
|
||||||
|
# TODO: SCRIPT_MODE
|
||||||
|
# TODO: SCRIPT_UID
|
||||||
|
# TODO: SCRIPT_USERNAME
|
||||||
|
# TODO: SERVER_ADDR
|
||||||
|
# TODO: SERVER_NAME
|
||||||
|
# TODO: SERVER_PORT
|
||||||
|
# TODO: SESSION
|
||||||
|
# TODO: SESSIONID
|
||||||
|
# TODO: TIME
|
||||||
|
# TODO: TIME_DAY
|
||||||
|
# TODO: TIME_EPOCH
|
||||||
|
# TODO: TIME_HOUR
|
||||||
|
# TODO: TIME_MIN
|
||||||
|
# TODO: TIME_MON
|
||||||
|
# TODO: TIME_SEC
|
||||||
|
# TODO: TIME_WDAY
|
||||||
|
# TODO: TIME_YEAR
|
||||||
|
# TODO: TX
|
||||||
|
# TODO: USERID
|
||||||
|
# TODO: WEBAPPID
|
||||||
|
# TODO: WEBSERVER_ERROR_LOG
|
||||||
|
# TODO: XML
|
||||||
|
|
||||||
738
apache2/t/run-regression-tests.pl.in
Executable file
738
apache2/t/run-regression-tests.pl.in
Executable file
@@ -0,0 +1,738 @@
|
|||||||
|
#!@PERL@
|
||||||
|
#
|
||||||
|
# Run regression tests.
|
||||||
|
#
|
||||||
|
# Syntax: run-regression-tests.pl [options] [file [N]]
|
||||||
|
#
|
||||||
|
# All: run-regression-tests.pl
|
||||||
|
# All in file: run-regression-tests.pl file
|
||||||
|
# Nth in file: run-regression-tests.pl file N
|
||||||
|
#
|
||||||
|
use strict;
|
||||||
|
use Time::HiRes qw(gettimeofday sleep);
|
||||||
|
use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG);
|
||||||
|
use File::Spec qw(rel2abs);
|
||||||
|
use File::Basename qw(basename dirname);
|
||||||
|
use FileHandle;
|
||||||
|
use IPC::Open2 qw(open2);
|
||||||
|
use IPC::Open3 qw(open3);
|
||||||
|
use Getopt::Std;
|
||||||
|
use Data::Dumper;
|
||||||
|
use IO::Socket;
|
||||||
|
use LWP::UserAgent;
|
||||||
|
|
||||||
|
my @TYPES = qw(config misc action target rule);
|
||||||
|
my $SCRIPT = basename($0);
|
||||||
|
my $SCRIPT_DIR = File::Spec->rel2abs(dirname($0));
|
||||||
|
my $REG_DIR = "$SCRIPT_DIR/regression";
|
||||||
|
my $SROOT_DIR = "$REG_DIR/server_root";
|
||||||
|
my $DATA_DIR = "$SROOT_DIR/data";
|
||||||
|
my $TEMP_DIR = "$SROOT_DIR/tmp";
|
||||||
|
my $UPLOAD_DIR = "$SROOT_DIR/upload";
|
||||||
|
my $CONF_DIR = "$SROOT_DIR/conf";
|
||||||
|
my $FILES_DIR = "$SROOT_DIR/logs";
|
||||||
|
my $PID_FILE = "$FILES_DIR/httpd.pid";
|
||||||
|
my $HTTPD = q(@APXS_HTTPD@);
|
||||||
|
my $PASSED = 0;
|
||||||
|
my $TOTAL = 0;
|
||||||
|
my %C = ();
|
||||||
|
my %FILE = ();
|
||||||
|
my $UA_NAME = "ModSecurity Regression Tests/1.2.3";
|
||||||
|
my $UA = LWP::UserAgent->new;
|
||||||
|
$UA->agent($UA_NAME);
|
||||||
|
|
||||||
|
# Hack for testing the script w/o configure
|
||||||
|
if ($HTTPD eq "\@APXS_HTTPD\@") {
|
||||||
|
$HTTPD = "/usr/local/apache2/bin/httpd";
|
||||||
|
}
|
||||||
|
|
||||||
|
$SIG{TERM} = $SIG{INT} = \&handle_interrupt;
|
||||||
|
|
||||||
|
my %opt;
|
||||||
|
getopts('A:E:D:C:T:H:a:p:dh', \%opt);
|
||||||
|
|
||||||
|
if ($opt{D}) {
|
||||||
|
$Data::Dumper::Indent = 1;
|
||||||
|
$Data::Dumper::Terse = 1;
|
||||||
|
$Data::Dumper::Pad = "";
|
||||||
|
$Data::Dumper::Quotekeys = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub usage {
|
||||||
|
print stderr <<"EOT";
|
||||||
|
@_
|
||||||
|
Usage: $SCRIPT [options] [file [N]]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-A file Specify ModSecurity audit log to read.
|
||||||
|
-D file Specify ModSecurity debug log to read.
|
||||||
|
-E file Specify Apache httpd error log to read.
|
||||||
|
-C file Specify Apache httpd base conf file to generate/reload.
|
||||||
|
-H path Specify Apache httpd htdocs path.
|
||||||
|
-S path Specify Apache httpd server root path.
|
||||||
|
-a file Specify Apache httpd binary (default: httpd)
|
||||||
|
-p port Specify Apache httpd port (default: 8088)
|
||||||
|
-d Enable debugging.
|
||||||
|
-h This help.
|
||||||
|
|
||||||
|
EOT
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() if ($opt{h});
|
||||||
|
|
||||||
|
### Check httpd binary
|
||||||
|
if (defined $opt{a}) {
|
||||||
|
$HTTPD = $opt{a};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$opt{a} = $HTTPD;
|
||||||
|
}
|
||||||
|
usage("Invalid Apache startup script: $HTTPD\n") unless (-e $HTTPD);
|
||||||
|
|
||||||
|
### Defaults
|
||||||
|
$opt{A} = "$FILES_DIR/modsec_audit.log" unless (defined $opt{A});
|
||||||
|
$opt{D} = "$FILES_DIR/modsec_debug.log" unless (defined $opt{D});
|
||||||
|
$opt{E} = "$FILES_DIR/error.log" unless (defined $opt{E});
|
||||||
|
$opt{C} = "$CONF_DIR/httpd.conf" unless (defined $opt{C});
|
||||||
|
$opt{H} = "$SROOT_DIR/htdocs" unless (defined $opt{H});
|
||||||
|
$opt{p} = 8088 unless (defined $opt{p});
|
||||||
|
|
||||||
|
unless (defined $opt{S}) {
|
||||||
|
my $httpd_root = `$HTTPD -V`;
|
||||||
|
($opt{S} = $httpd_root) =~ s/.*-D HTTPD_ROOT="([^"]*)".*/$1/sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
%ENV = (
|
||||||
|
%ENV,
|
||||||
|
SERVER_ROOT => $opt{S},
|
||||||
|
SERVER_PORT => $opt{p},
|
||||||
|
SERVER_NAME => "localhost",
|
||||||
|
TEST_SERVER_ROOT => $SROOT_DIR,
|
||||||
|
DATA_DIR => $DATA_DIR,
|
||||||
|
TEMP_DIR => $TEMP_DIR,
|
||||||
|
UPLOAD_DIR => $UPLOAD_DIR,
|
||||||
|
CONF_DIR => $CONF_DIR,
|
||||||
|
LOGS_DIR => $FILES_DIR,
|
||||||
|
SCRIPT_DIR => $SCRIPT_DIR,
|
||||||
|
REGRESSION_DIR => $REG_DIR,
|
||||||
|
DIST_ROOT => File::Spec->rel2abs(dirname("$SCRIPT_DIR/../../..")),
|
||||||
|
AUDIT_LOG => $opt{A},
|
||||||
|
DEBUG_LOG => $opt{D},
|
||||||
|
ERROR_LOG => $opt{E},
|
||||||
|
HTTPD_CONF => $opt{C},
|
||||||
|
HTDOCS => $opt{H},
|
||||||
|
USER_AGENT => $UA_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
#dbg("OPTIONS: ", \%opt);
|
||||||
|
|
||||||
|
if (-e "$PID_FILE") {
|
||||||
|
msg("Shutting down previous instance: $PID_FILE");
|
||||||
|
httpd_stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined $ARGV[0]) {
|
||||||
|
runfile(dirname($ARGV[0]), basename($ARGV[0]), $ARGV[1]);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $type (@TYPES) {
|
||||||
|
my $dir = "$SCRIPT_DIR/regression/$type";
|
||||||
|
my @cfg = ();
|
||||||
|
|
||||||
|
# Get test names
|
||||||
|
opendir(DIR, "$dir") or quit(1, "Failed to open \"$dir\": $!");
|
||||||
|
@cfg = grep { /\.t$/ && -f "$dir/$_" } readdir(DIR);
|
||||||
|
closedir(DIR);
|
||||||
|
|
||||||
|
for my $cfg (sort @cfg) {
|
||||||
|
runfile($dir, $cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
|
||||||
|
|
||||||
|
sub runfile {
|
||||||
|
my($dir, $cfg, $testnum) = @_;
|
||||||
|
my $fn = "$dir/$cfg";
|
||||||
|
my @data = ();
|
||||||
|
my $edata;
|
||||||
|
my @C = ();
|
||||||
|
my @test = ();
|
||||||
|
my $teststr;
|
||||||
|
my $n = 0;
|
||||||
|
my $pass = 0;
|
||||||
|
|
||||||
|
open(CFG, "<$fn") or quit(1, "Failed to open \"$fn\": $!");
|
||||||
|
@data = <CFG>;
|
||||||
|
|
||||||
|
$edata = q/@C = (/ . join("", @data) . q/)/;
|
||||||
|
eval $edata;
|
||||||
|
quit(1, "Failed to read test data \"$cfg\": $@") if ($@);
|
||||||
|
|
||||||
|
unless (@C) {
|
||||||
|
msg("\nNo tests defined for $fn");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg("\nLoaded ".@C." tests from $fn");
|
||||||
|
for my $t (@C) {
|
||||||
|
$n++;
|
||||||
|
next if (defined $testnum and $n != $testnum);
|
||||||
|
|
||||||
|
my $httpd_up = 0;
|
||||||
|
my %t = %{$t || {}};
|
||||||
|
my $id = sprintf("%3d", $n);
|
||||||
|
my $out = "";
|
||||||
|
my $rc = 0;
|
||||||
|
my $conf_fn;
|
||||||
|
|
||||||
|
# Startup httpd with optionally included conf.
|
||||||
|
if (exists $t{conf} and defined $t{conf}) {
|
||||||
|
$conf_fn = sprintf "%s/%s_%s_%06d.conf",
|
||||||
|
$CONF_DIR, $t{type}, $cfg, $n;
|
||||||
|
# dbg("Writing test config to: $conf_fn");
|
||||||
|
open(CONF, ">$conf_fn") or die "Failed to open conf \"$conf_fn\": $!\n";
|
||||||
|
print CONF (ref $t{conf} eq "CODE" ? eval { &{$t{conf}} } : $t{conf});
|
||||||
|
msg("$@") if ($@);
|
||||||
|
close CONF;
|
||||||
|
$httpd_up = httpd_start(\%t, "Include $conf_fn") ? 0 : 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$httpd_up = httpd_start(\%t) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run any prerun setup
|
||||||
|
if ($rc == 0 and exists $t{prerun} and defined $t{prerun}) {
|
||||||
|
dbg("Executing perl prerun...");
|
||||||
|
$rc = &{$t{prerun}};
|
||||||
|
dbg("Perl prerun returned: $rc");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($httpd_up) {
|
||||||
|
# Perform the request and check response
|
||||||
|
if (exists $t{request}) {
|
||||||
|
my $resp = do_request($t{request});
|
||||||
|
if (!$resp) {
|
||||||
|
msg("invalid response");
|
||||||
|
dbg("RESPONSE: ", $resp);
|
||||||
|
$rc = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for my $key (keys %{ $t{match_response} || {}}) {
|
||||||
|
my($neg,$mtype) = ($key =~ m/^(-?)(.*)$/);
|
||||||
|
my $m = $t{match_response}{$key};
|
||||||
|
my $match = match_response($mtype, $resp, $m);
|
||||||
|
if ($neg and defined $match) {
|
||||||
|
$rc = 1;
|
||||||
|
msg("response $mtype matched: $m");
|
||||||
|
dbg($resp);
|
||||||
|
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
elsif (!$neg and !defined $match) {
|
||||||
|
$rc = 1;
|
||||||
|
msg("response $mtype failed to match: $m");
|
||||||
|
dbg($resp);
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run any arbitrary perl tests
|
||||||
|
if ($rc == 0 and exists $t{test} and defined $t{test}) {
|
||||||
|
#dbg("Executing perl test(s)...");
|
||||||
|
$rc = eval { &{$t{test}} };
|
||||||
|
if (! defined $rc) {
|
||||||
|
msg("Error running test: $@");
|
||||||
|
$rc = -1;
|
||||||
|
}
|
||||||
|
#dbg("Perl tests returned: $rc");
|
||||||
|
}
|
||||||
|
|
||||||
|
# Search for all log matches
|
||||||
|
if ($rc == 0 and exists $t{match_log} and defined $t{match_log}) {
|
||||||
|
for my $key (keys %{ $t{match_log} || {}}) {
|
||||||
|
my($neg,$mtype) = ($key =~ m/^(-?)(.*)$/);
|
||||||
|
my $m = $t{match_log}{$key};
|
||||||
|
my $match = match_log($mtype, @{$m || []});
|
||||||
|
if ($neg and defined $match) {
|
||||||
|
$rc = 1;
|
||||||
|
msg("$mtype log matched: $m->[0]");
|
||||||
|
msg("Log: $FILE{$mtype}{fn}");
|
||||||
|
dbg(escape("$FILE{$mtype}{buf}"));
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
elsif (!$neg and !defined $match) {
|
||||||
|
$rc = 1;
|
||||||
|
msg("$mtype log failed to match: $m->[0]");
|
||||||
|
msg("Log: $FILE{$mtype}{fn}");
|
||||||
|
dbg(escape("$FILE{$mtype}{buf}"));
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Search for all file matches
|
||||||
|
if ($rc == 0 and exists $t{match_file} and defined $t{match_file}) {
|
||||||
|
for my $key (keys %{ $t{match_file} || {}}) {
|
||||||
|
my($neg,$fn) = ($key =~ m/^(-?)(.*)$/);
|
||||||
|
my $m = $t{match_file}{$key};
|
||||||
|
my $match = match_file($fn, $m);
|
||||||
|
if ($neg and defined $match) {
|
||||||
|
$rc = 1;
|
||||||
|
msg("$fn file matched: $m");
|
||||||
|
dbg(escape("$FILE{$fn}{buf}"));
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
elsif (!$neg and !defined $match) {
|
||||||
|
$rc = 1;
|
||||||
|
msg("$fn file failed match: $m");
|
||||||
|
dbg(escape("$FILE{$fn}{buf}"));
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg("Failed to start httpd.");
|
||||||
|
$rc = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rc == 0) {
|
||||||
|
$pass++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dbg("Test config: $conf_fn");
|
||||||
|
}
|
||||||
|
|
||||||
|
msg(sprintf("%s) %s%s: %s%s", $id, $t{type}, (exists($t{comment}) ? " - $t{comment}" : ""), ($rc ? "failed" : "passed"), ((defined($out) && $out ne "")? " ($out)" : "")));
|
||||||
|
|
||||||
|
if ($httpd_up) {
|
||||||
|
$httpd_up = httpd_stop(\%t) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$TOTAL += $testnum ? 1 : $n;
|
||||||
|
$PASSED += $pass;
|
||||||
|
|
||||||
|
msg(sprintf("Passed: %2d; Failed: %2d", $pass, $testnum ? (1 - $pass) : ($n - $pass)));
|
||||||
|
}
|
||||||
|
|
||||||
|
# Take out any indenting and translate LF -> CRLF
|
||||||
|
sub normalize_raw_request_data {
|
||||||
|
my $r = $_[0];
|
||||||
|
|
||||||
|
# Allow for indenting in test file
|
||||||
|
$r =~ s/^[ \t]*\x0d?\x0a//s;
|
||||||
|
my($indention) = ($r =~ m/^([ \t]*)/s); # indention taken from first line
|
||||||
|
$r =~ s/^$indention//mg;
|
||||||
|
$r =~ s/(\x0d?\x0a)[ \t]+$/$1/s;
|
||||||
|
|
||||||
|
# Translate LF to CRLF
|
||||||
|
$r =~ s/^\x0a/\x0d\x0a/mg;
|
||||||
|
$r =~ s/([^\x0d])\x0a/$1\x0d\x0a/mg;
|
||||||
|
|
||||||
|
return $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub do_raw_request {
|
||||||
|
my $sock = new IO::Socket::INET(
|
||||||
|
Proto => "tcp",
|
||||||
|
PeerAddr => "localhost",
|
||||||
|
PeerPort => $opt{p},
|
||||||
|
) or msg("Failed to connect to localhost:$opt{p}: $@");
|
||||||
|
return unless ($sock);
|
||||||
|
|
||||||
|
# Join togeather the request
|
||||||
|
my $r = join("", @_);
|
||||||
|
dbg($r);
|
||||||
|
|
||||||
|
# Write to socket
|
||||||
|
print $sock "$r";
|
||||||
|
$sock->shutdown(1);
|
||||||
|
|
||||||
|
# Read from socket
|
||||||
|
my @resp = <$sock>;
|
||||||
|
$sock->close();
|
||||||
|
|
||||||
|
return HTTP::Response->parse(join("", @resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub do_request {
|
||||||
|
my $r = $_[0];
|
||||||
|
|
||||||
|
# Allow test to execute code
|
||||||
|
if (ref $r eq "CODE") {
|
||||||
|
$r = eval { &$r };
|
||||||
|
msg("$@") unless (defined $r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref $r eq "HTTP::Request") {
|
||||||
|
# dbg("REQUEST: ", $r);
|
||||||
|
my $resp = $UA->request($r);
|
||||||
|
if ($opt{d}) {
|
||||||
|
dbg($resp->request()->as_string());
|
||||||
|
}
|
||||||
|
return $resp
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# dbg("REQUEST:\n", $r);
|
||||||
|
return do_raw_request($r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub match_response {
|
||||||
|
my($name, $resp, $re) = @_;
|
||||||
|
|
||||||
|
msg("Warning: Empty regular expression.") if (!defined $re or $re eq "");
|
||||||
|
|
||||||
|
if ($name eq "status") {
|
||||||
|
return $& if ($resp->code =~ m/$re/);
|
||||||
|
}
|
||||||
|
elsif ($name eq "content") {
|
||||||
|
return $& if ($resp->content =~ m/$re/m);
|
||||||
|
}
|
||||||
|
elsif ($name eq "raw") {
|
||||||
|
return $& if ($resp->as_string =~ m/$re/m);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub match_log {
|
||||||
|
my($name, $re, $timeout) = @_;
|
||||||
|
my $t0 = gettimeofday;
|
||||||
|
my($fh,$rbuf) = ($FILE{$name}{fd}, \$FILE{$name}{buf});
|
||||||
|
my $n = length($$rbuf);
|
||||||
|
|
||||||
|
msg("Warning: Empty regular expression.") if (!defined $re or $re eq "");
|
||||||
|
|
||||||
|
unless (defined $fh) {
|
||||||
|
msg("Error: File \"$name\" is not opened for matching.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$timeout = 0 unless (defined $timeout);
|
||||||
|
|
||||||
|
do {
|
||||||
|
$n += $fh->sysread($$rbuf, 1024, $n);
|
||||||
|
# dbg("Match \"$re\" in $name \"$$rbuf\" ($n)");
|
||||||
|
return $& if ($$rbuf =~ m/$re/m);
|
||||||
|
# TODO: Use select()/poll()
|
||||||
|
sleep 0.1;
|
||||||
|
} while (gettimeofday - $t0 < $timeout);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub match_file {
|
||||||
|
my($neg,$fn) = ($_[0] =~ m/^(-?)(.*)$/);
|
||||||
|
unless (exists $FILE{$fn}) {
|
||||||
|
eval {
|
||||||
|
$FILE{$fn}{fn} = $fn;
|
||||||
|
$FILE{$fn}{fd} = new FileHandle($fn, O_RDONLY) or die "$!\n";
|
||||||
|
$FILE{$fn}{fd}->blocking(0);
|
||||||
|
$FILE{$fn}{buf} = "";
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
msg("Warning: Failed to open file \"$fn\": $@");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match_log($_[0], $_[1]); # timeout makes no sense
|
||||||
|
}
|
||||||
|
|
||||||
|
sub quote_shell {
|
||||||
|
my($s) = @_;
|
||||||
|
return $s unless ($s =~ m|[^\w!%+,\-./:@^]|);
|
||||||
|
$s =~ s/(['\\])/\\$1/g;
|
||||||
|
return "'$s'";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub escape {
|
||||||
|
my @new = ();
|
||||||
|
for my $c (split(//, $_[0])) {
|
||||||
|
my $oc = ord($c);
|
||||||
|
push @new, ((($oc >= 0x20 and $oc <= 0x7e) or $oc == 0x0a or $oc == 0x0d) ? $c : sprintf("\\x%02x", ord($c)));
|
||||||
|
}
|
||||||
|
join('', @new);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub dbg {
|
||||||
|
return unless(@_ and $opt{d});
|
||||||
|
my $out = join "", map {
|
||||||
|
(ref $_ ne "" ? Dumper($_) : $_)
|
||||||
|
} @_;
|
||||||
|
$out =~ s/^/DBG: /mg;
|
||||||
|
print STDOUT "$out\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub msg {
|
||||||
|
return unless(@_);
|
||||||
|
my $out = join "", map {
|
||||||
|
(ref $_ ne "" ? Dumper($_) : $_)
|
||||||
|
} @_;
|
||||||
|
print STDOUT "$out\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub handle_interrupt {
|
||||||
|
$SIG{TERM} = $SIG{INT} = \&handle_interrupt;
|
||||||
|
|
||||||
|
msg("Interrupted via SIG$_[0]. Shutting down tests...");
|
||||||
|
httpd_stop();
|
||||||
|
|
||||||
|
quit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub quit {
|
||||||
|
my($ec,$msg) = @_;
|
||||||
|
$ec = 0 unless (defined $_[0]);
|
||||||
|
|
||||||
|
msg("$msg") if (defined $msg);
|
||||||
|
|
||||||
|
exit $ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub done {
|
||||||
|
if ($PASSED != $TOTAL) {
|
||||||
|
quit(1, "\n$PASSED/$TOTAL tests passed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
quit(0, "\nAll tests passed ($TOTAL).");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub httpd_start {
|
||||||
|
my $t = shift;
|
||||||
|
httpd_reset_fd($t);
|
||||||
|
my @p = (
|
||||||
|
$HTTPD,
|
||||||
|
-d => $opt{S},
|
||||||
|
-f => $opt{C},
|
||||||
|
(map { (-c => $_) } ("Listen localhost:$opt{p}", @_)),
|
||||||
|
-k => "start",
|
||||||
|
);
|
||||||
|
|
||||||
|
my $httpd_out;
|
||||||
|
my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1);
|
||||||
|
my $out = join("\\n", split(/\n/, <$httpd_out>));
|
||||||
|
close $httpd_out;
|
||||||
|
waitpid($httpd_pid, 0);
|
||||||
|
|
||||||
|
my $rc = $?;
|
||||||
|
if ( WIFEXITED($rc) ) {
|
||||||
|
$rc = WEXITSTATUS($rc);
|
||||||
|
dbg("Httpd start returned with $rc.") if ($rc);
|
||||||
|
}
|
||||||
|
elsif( WIFSIGNALED($rc) ) {
|
||||||
|
msg("Httpd start failed with signal " . WTERMSIG($rc) . ".");
|
||||||
|
$rc = -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg("Httpd start failed with unknown error.");
|
||||||
|
$rc = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defined $out and $out ne "") {
|
||||||
|
dbg(join(" ", map { quote_shell($_) } @p));
|
||||||
|
msg("Httpd start failed with error messages:\n$out");
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Look for startup msg
|
||||||
|
unless (defined match_log("error", qr/resuming normal operations/, 10)) {
|
||||||
|
dbg(join(" ", map { quote_shell($_) } @p));
|
||||||
|
dbg(match_log("error", qr/(^.*ModSecurity: .*)/sm, 10));
|
||||||
|
msg("Httpd server failed to start.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub httpd_stop {
|
||||||
|
my $t = shift;
|
||||||
|
my @p = (
|
||||||
|
$HTTPD,
|
||||||
|
-d => $opt{S},
|
||||||
|
-f => $opt{C},
|
||||||
|
(map { (-c => $_) } ("Listen localhost:$opt{p}", @_)),
|
||||||
|
-k => "stop",
|
||||||
|
);
|
||||||
|
|
||||||
|
my $httpd_out;
|
||||||
|
my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1);
|
||||||
|
my $out = join("\\n", split(/\n/, <$httpd_out>));
|
||||||
|
close $httpd_out;
|
||||||
|
waitpid($httpd_pid, 0);
|
||||||
|
|
||||||
|
if (defined $out and $out ne "") {
|
||||||
|
msg("Httpd stop failed with error messages:\n$out");
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
my $rc = $?;
|
||||||
|
if ( WIFEXITED($rc) ) {
|
||||||
|
$rc = WEXITSTATUS($rc);
|
||||||
|
dbg("Httpd stop returned with $rc.") if ($rc);
|
||||||
|
}
|
||||||
|
elsif( WIFSIGNALED($rc) ) {
|
||||||
|
msg("Httpd stop failed with signal " . WTERMSIG($rc) . ".");
|
||||||
|
$rc = -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg("Httpd stop failed with unknown error.");
|
||||||
|
$rc = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Look for startup msg
|
||||||
|
unless (defined match_log("error", qr/caught SIG[A-Z]+, shutting down/, 10)) {
|
||||||
|
dbg(join(" ", map { quote_shell($_) } @p));
|
||||||
|
msg("Httpd server failed to shutdown.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub httpd_reload {
|
||||||
|
my $t = shift;
|
||||||
|
httpd_reset_fd($t);
|
||||||
|
my @p = (
|
||||||
|
$HTTPD,
|
||||||
|
-d => $opt{S},
|
||||||
|
-f => $opt{C},
|
||||||
|
(map { (-c => $_) } ("Listen localhost:$opt{p}", @_)),
|
||||||
|
-k => "graceful",
|
||||||
|
);
|
||||||
|
|
||||||
|
my $httpd_out;
|
||||||
|
my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1);
|
||||||
|
my $out = join("\\n", split(/\n/, <$httpd_out>));
|
||||||
|
close $httpd_out;
|
||||||
|
waitpid($httpd_pid, 0);
|
||||||
|
|
||||||
|
if (defined $out and $out ne "") {
|
||||||
|
msg("Httpd reload failed with error messages:\n$out");
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
my $rc = $?;
|
||||||
|
if ( WIFEXITED($rc) ) {
|
||||||
|
$rc = WEXITSTATUS($rc);
|
||||||
|
dbg("Httpd reload returned with $rc.") if ($rc);
|
||||||
|
}
|
||||||
|
elsif( WIFSIGNALED($rc) ) {
|
||||||
|
msg("Httpd reload failed with signal " . WTERMSIG($rc) . ".");
|
||||||
|
$rc = -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg("Httpd reload failed with unknown error.");
|
||||||
|
$rc = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Look for startup msg
|
||||||
|
unless (defined match_log("error", qr/resuming normal operations/, 10)) {
|
||||||
|
dbg(join(" ", map { quote_shell($_) } @p));
|
||||||
|
msg("Httpd server failed to reload.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub httpd_reset_fd {
|
||||||
|
my($t) = @_;
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
for my $key (keys %FILE) {
|
||||||
|
if (exists $FILE{$key}{fd} and defined $FILE{$key}{fd}) {
|
||||||
|
$FILE{$key}{fd}->close();
|
||||||
|
}
|
||||||
|
delete $FILE{$key};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Error
|
||||||
|
eval {
|
||||||
|
$FILE{error}{fn} = $opt{E};
|
||||||
|
$FILE{error}{fd} = new FileHandle($opt{E}, O_RDWR|O_CREAT) or die "$!\n";
|
||||||
|
$FILE{error}{fd}->blocking(0);
|
||||||
|
$FILE{error}{fd}->sysseek(0, 2);
|
||||||
|
$FILE{error}{buf} = "";
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
msg("Warning: Failed to open file \"$opt{E}\": $@");
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Audit
|
||||||
|
eval {
|
||||||
|
$FILE{audit}{fn} = $opt{A};
|
||||||
|
$FILE{audit}{fd} = new FileHandle($opt{A}, O_RDWR|O_CREAT) or die "$!\n";
|
||||||
|
$FILE{audit}{fd}->blocking(0);
|
||||||
|
$FILE{audit}{fd}->sysseek(0, 2);
|
||||||
|
$FILE{audit}{buf} = "";
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
msg("Warning: Failed to open file \"$opt{A}\": $@");
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
eval {
|
||||||
|
$FILE{debug}{fn} = $opt{D};
|
||||||
|
$FILE{debug}{fd} = new FileHandle($opt{D}, O_RDWR|O_CREAT) or die "$!\n";
|
||||||
|
$FILE{debug}{fd}->blocking(0);
|
||||||
|
$FILE{debug}{fd}->sysseek(0, 2);
|
||||||
|
$FILE{debug}{buf} = "";
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
msg("Warning: Failed to open file \"$opt{D}\": $@");
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Any extras listed in "match_log"
|
||||||
|
if ($t and exists $t->{match_log}) {
|
||||||
|
for my $k (keys %{ $t->{match_log} || {} }) {
|
||||||
|
my($neg,$fn) = ($k =~ m/^(-?)(.*)$/);
|
||||||
|
next if (!$fn or exists $FILE{$fn});
|
||||||
|
eval {
|
||||||
|
$FILE{$fn}{fn} = $fn;
|
||||||
|
$FILE{$fn}{fd} = new FileHandle($fn, O_RDWR|O_CREAT) or die "$!\n";
|
||||||
|
$FILE{$fn}{fd}->blocking(0);
|
||||||
|
$FILE{$fn}{fd}->sysseek(0, 2);
|
||||||
|
$FILE{$fn}{buf} = "";
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
msg("Warning: Failed to open file \"$fn\": $@");
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub encode_chunked {
|
||||||
|
my($data, $size) = @_;
|
||||||
|
$size = 128 unless ($size);
|
||||||
|
my $chunked = "";
|
||||||
|
|
||||||
|
my $n = 0;
|
||||||
|
my $bytes = length($data);
|
||||||
|
while ($bytes >= $size) {
|
||||||
|
$chunked .= sprintf "%x\x0d\x0a%s\x0d\x0a", $size, substr($data, $n, $size);
|
||||||
|
$n += $size;
|
||||||
|
$bytes -= $size;
|
||||||
|
}
|
||||||
|
if ($bytes) {
|
||||||
|
$chunked .= sprintf "%x\x0d\x0a%s\x0d\x0a", $bytes, substr($data, $n, $bytes);
|
||||||
|
}
|
||||||
|
$chunked .= "0\x0d\x0a\x0d\x0a"
|
||||||
|
}
|
||||||
161
apache2/t/run-unit-tests.pl.in
Executable file
161
apache2/t/run-unit-tests.pl.in
Executable file
@@ -0,0 +1,161 @@
|
|||||||
|
#!@PERL@
|
||||||
|
#
|
||||||
|
# Run unit tests.
|
||||||
|
#
|
||||||
|
# Syntax:
|
||||||
|
# All: run-tests.pl
|
||||||
|
# All in file: run-tests.pl file
|
||||||
|
# Nth in file: run-tests.pl file N
|
||||||
|
#
|
||||||
|
use strict;
|
||||||
|
use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG);
|
||||||
|
use File::Basename qw(basename dirname);
|
||||||
|
use FileHandle;
|
||||||
|
use IPC::Open2 qw(open2);
|
||||||
|
|
||||||
|
my @TYPES = qw(tfn op action);
|
||||||
|
my $TEST = "./msc_test";
|
||||||
|
my $SCRIPT = basename($0);
|
||||||
|
my $SCRIPTDIR = dirname($0);
|
||||||
|
my $PASSED = 0;
|
||||||
|
my $TOTAL = 0;
|
||||||
|
my $DEBUG = $ENV{MSC_TEST_DEBUG} || 0;
|
||||||
|
|
||||||
|
if (defined $ARGV[0]) {
|
||||||
|
runfile(dirname($ARGV[0]), basename($ARGV[0]), $ARGV[1]);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $type (sort @TYPES) {
|
||||||
|
my $dir = "$SCRIPTDIR/$type";
|
||||||
|
my @cfg = ();
|
||||||
|
|
||||||
|
# Get test names
|
||||||
|
opendir(DIR, "$dir") or quit(1, "Failed to open \"$dir\": $!");
|
||||||
|
@cfg = grep { /\.t$/ && -f "$dir/$_" } readdir(DIR);
|
||||||
|
closedir(DIR);
|
||||||
|
|
||||||
|
for my $cfg (sort @cfg) {
|
||||||
|
runfile($dir, $cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
|
||||||
|
|
||||||
|
sub runfile {
|
||||||
|
my($dir, $cfg, $testnum) = @_;
|
||||||
|
my $fn = "$dir/$cfg";
|
||||||
|
my @data = ();
|
||||||
|
my $edata;
|
||||||
|
my @C = ();
|
||||||
|
my @test = ();
|
||||||
|
my $teststr;
|
||||||
|
my $n = 0;
|
||||||
|
my $pass = 0;
|
||||||
|
|
||||||
|
open(CFG, "<$fn") or quit(1, "Failed to open \"$fn\": $!");
|
||||||
|
@data = <CFG>;
|
||||||
|
|
||||||
|
$edata = q/@C = (/ . join("", @data) . q/)/;
|
||||||
|
eval $edata;
|
||||||
|
quit(1, "Failed to read test data \"$cfg\": $@") if ($@);
|
||||||
|
|
||||||
|
unless (@C) {
|
||||||
|
msg("\nNo tests defined for $fn");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg("\nLoaded ".@C." tests from $fn");
|
||||||
|
for my $t (@C) {
|
||||||
|
$n++;
|
||||||
|
next if (defined $testnum and $n != $testnum);
|
||||||
|
|
||||||
|
my %t = %{$t || {}};
|
||||||
|
my $id = sprintf("%6d", $n);
|
||||||
|
my $in = (exists($t{input}) and defined($t{input})) ? $t{input} : "";
|
||||||
|
my $out;
|
||||||
|
my $test_in = new FileHandle();
|
||||||
|
my $test_out = new FileHandle();
|
||||||
|
my $test_pid;
|
||||||
|
my $rc = 0;
|
||||||
|
my $param;
|
||||||
|
|
||||||
|
if ($t{type} eq "tfn") {
|
||||||
|
$param = escape($t{output});
|
||||||
|
}
|
||||||
|
elsif ($t{type} eq "op") {
|
||||||
|
$param = escape($t{param});
|
||||||
|
}
|
||||||
|
elsif ($t{type} eq "action") {
|
||||||
|
$param = escape($t{param});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
quit(1, "Unknown type \"$t{type}\" - should be one of: " . join(",",@TYPES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@test = ("-t", $t{type}, "-n", $t{name}, "-p", $param, "-D", "$DEBUG", (exists($t{ret}) ? ("-r", $t{ret}) : ()), (exists($t{iterations}) ? ("-I", $t{iterations}) : ()), (exists($t{prerun}) ? ("-P", $t{prerun}) : ()));
|
||||||
|
$teststr = "$TEST " . join(" ", map { "\"$_\"" } @test);
|
||||||
|
$test_pid = open2($test_out, $test_in, $TEST, @test) or quit(1, "Failed to execute test: $teststr\": $!");
|
||||||
|
print $test_in "$in";
|
||||||
|
close $test_in;
|
||||||
|
$out = join("\\n", split(/\n/, <$test_out>));
|
||||||
|
close $test_out;
|
||||||
|
waitpid($test_pid, 0);
|
||||||
|
|
||||||
|
$rc = $?;
|
||||||
|
if ( WIFEXITED($rc) ) {
|
||||||
|
$rc = WEXITSTATUS($rc);
|
||||||
|
}
|
||||||
|
elsif( WIFSIGNALED($rc) ) {
|
||||||
|
msg("Test exited with signal " . WTERMSIG($rc) . ".");
|
||||||
|
msg("Executed: $teststr");
|
||||||
|
$rc = -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg("Test exited with unknown error.");
|
||||||
|
$rc = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($rc == 0) {
|
||||||
|
$pass++;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg(sprintf("%s) %s \"%s\"%s: %s%s", $id, $t{type}, $t{name}, (exists($t{comment}) ? " $t{comment}" : ""), ($rc ? "failed" : "passed"), ((defined($out) && $out ne "")? " ($out)" : "")));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$TOTAL += $testnum ? 1 : $n;
|
||||||
|
$PASSED += $pass;
|
||||||
|
|
||||||
|
msg(sprintf("Passed: %2d; Failed: %2d", $pass, $testnum ? (1 - $pass) : ($n - $pass)));
|
||||||
|
}
|
||||||
|
|
||||||
|
sub escape {
|
||||||
|
my @new = ();
|
||||||
|
for my $c (split(//, $_[0])) {
|
||||||
|
push @new, ((ord($c) >= 0x20 and ord($c) <= 0x7e) ? $c : sprintf("\\x%02x", ord($c)));
|
||||||
|
}
|
||||||
|
join('', @new);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub msg {
|
||||||
|
print STDOUT "@_\n" if (@_);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub quit {
|
||||||
|
my($ec,$msg) = @_;
|
||||||
|
$ec = 0 unless (defined $_[0]);
|
||||||
|
|
||||||
|
msg("$msg") if (defined $msg);
|
||||||
|
|
||||||
|
exit $ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub done {
|
||||||
|
if ($PASSED != $TOTAL) {
|
||||||
|
quit(1, "\n$PASSED/$TOTAL tests passed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
quit(0, "\nAll tests passed ($TOTAL).");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user