Merge pull request #54 from chaizhenhua/regression-tests

Regression tests
This commit is contained in:
Breno Silva 2013-04-02 07:04:18 -07:00
commit 36c2e1bcce
10 changed files with 1053 additions and 229 deletions

View File

@ -11,7 +11,8 @@ APR_CFLAGS=""
APR_CPPFLAGS=""
APR_LDFLAGS=""
APR_LDADD=""
APR_INCLUDEDIR=""
APR_LINKLD=""
AC_DEFUN([CHECK_APR],
[dnl
@ -63,6 +64,10 @@ if test -n "${apr_path}"; then
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(apr LDFLAGS: $APR_LDFLAGS); fi
APR_LDADD="`${APR_CONFIG} --link-libtool`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(apr LDADD: $APR_LDADD); fi
APR_INCLUDEDIR="`${APR_CONFIG} --includedir`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(apr INCLUDEDIR: $APR_INCLUDEDIR); fi
APR_LINKLD="`${APR_CONFIG} --link-ld`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(apr LINKLD: $APR_LINKLD); fi
else
AC_MSG_RESULT([no])
fi
@ -73,6 +78,8 @@ AC_SUBST(APR_CFLAGS)
AC_SUBST(APR_CPPFLAGS)
AC_SUBST(APR_LDFLAGS)
AC_SUBST(APR_LDADD)
AC_SUBST(APR_INCLUDEDIR)
AC_SUBST(APR_LINKLD)
if test -z "${APR_VERSION}"; then
AC_MSG_NOTICE([*** apr library not found.])

View File

@ -10,6 +10,8 @@ APU_CONFIG=""
APU_CFLAGS=""
APU_LDFLAGS=""
APU_LDADD=""
APU_INCLUDEDIR=""
APU_LINKLD=""
AC_DEFUN([CHECK_APU],
[dnl
@ -18,7 +20,7 @@ AC_ARG_WITH(
apu,
[AC_HELP_STRING([--with-apu=PATH],[Path to apu prefix or config script])],
[test_paths="${with_apu}"],
[test_paths="/usr/local/libapr-util /usr/local/apr-util /usr/local/libapu /usr/local/apu /usr/local /opt/libapr-util /opt/apr-util /opt/libapu /opt/apu /opt /usr"])
[test_paths="/usr/local/libapr-util /usr/local/apr-util /usr/local/libapu /usr/local/apu /usr/local/apr /usr/local /opt/libapr-util /opt/apr-util /opt/libapu /opt/apu /opt /usr"])
AC_MSG_CHECKING([for libapu config script])
@ -60,6 +62,10 @@ if test -n "${apu_path}"; then
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(apu LDFLAGS: $APU_LDFLAGS); fi
APU_LDADD="`${APU_CONFIG} --link-libtool`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(apu LDADD: $APU_LDADD); fi
APU_INCLUDEDIR="`${APU_CONFIG} --includedir`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(apu INCLUDEDIR: $APU_INCLUDEDIR); fi
APU_LINKLD="`${APU_CONFIG} --link-ld`"
if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(apu LINKLD: $APU_LINKLD); fi
else
AC_MSG_RESULT([no])
fi
@ -69,6 +75,8 @@ AC_SUBST(APU_VERSION)
AC_SUBST(APU_CFLAGS)
AC_SUBST(APU_LDFLAGS)
AC_SUBST(APU_LDADD)
AC_SUBST(APU_INCLUDEDIR)
AC_SUBST(APU_LINKLD)
if test -z "${APU_VERSION}"; then
AC_MSG_NOTICE([*** apu library not found.])

View File

@ -3,5 +3,5 @@ CORE_MODULES="$CORE_MODULES ngx_pool_context_module"
HTTP_AUX_FILTER_MODULES="ngx_http_modsecurity $HTTP_AUX_FILTER_MODULES"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_modsecurity.c $ngx_addon_dir/apr_bucket_nginx.c $ngx_addon_dir/ngx_pool_context.c"
NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/apr_bucket_nginx.h $ngx_addon_dir/ngx_pool_context.h"
CORE_LIBS="$CORE_LIBS $ngx_addon_dir/../../standalone/.libs/standalone.a -lapr-1 -laprutil-1 -lxml2 -lm "
CORE_INCS="$CORE_INCS /usr/include/apache2 /usr/include/apr-1.0 /usr/include/httpd /usr/include/apr-1 $ngx_addon_dir $ngx_addon_dir/../../standalone $ngx_addon_dir/../../apache2 /usr/include/libxml2 "
CORE_LIBS="$CORE_LIBS $ngx_addon_dir/../../standalone/.libs/standalone.a -L/usr/local/apr/lib -lapr-1 -L/usr/local/apr/lib -laprutil-1 -lpcre -lxml2 -lz -lm -ldl "
CORE_INCS="$CORE_INCS $ngx_addon_dir $ngx_addon_dir/../../standalone $ngx_addon_dir/../../apache2 /usr/include/libxml2 /usr/local/apache2/include /usr/local/apr/include/apr-1 /usr/local/apr/include/apr-1"

View File

@ -156,7 +156,8 @@ ngx_pstrdup0(ngx_pool_t *pool, ngx_str_t *src)
}
static inline int ngx_http_modsecurity_method_number(unsigned int nginx)
static inline int
ngx_http_modsecurity_method_number(unsigned int nginx)
{
/*
* http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightMultLookup
@ -246,7 +247,7 @@ ngx_http_modsecurity_load_request(ngx_http_request_t *r)
}
#endif
req->parsed_uri.path = req->path_info;
req->parsed_uri.path = (char *)ngx_pstrdup0(r->pool, &r->uri);
req->parsed_uri.is_initialized = 1;
str.data = r->port_start;
@ -254,7 +255,7 @@ ngx_http_modsecurity_load_request(ngx_http_request_t *r)
req->parsed_uri.port = ngx_atoi(str.data, str.len);
req->parsed_uri.port_str = (char *)ngx_pstrdup0(r->pool, &str);
req->parsed_uri.query = req->args;
req->parsed_uri.query = r->args.len ? req->args : NULL;
req->parsed_uri.dns_looked_up = 0;
req->parsed_uri.dns_resolved = 0;
@ -786,6 +787,29 @@ ngx_http_modsecurity_save_headers_out_visitor(void *data, const char *key, const
return 1;
}
static ngx_inline ngx_int_t
ngx_http_modsecurity_status(ngx_http_request_t *r, int status)
{
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: status %d", status);
if (status == DECLINED || status == APR_SUCCESS) {
return NGX_DECLINED;
}
/* nginx known status */
if ( (status >= 300 && status < 308) /* 3XX */
|| (status >= 400 && status < 417) /* 4XX */
|| (status >= 500 && status < 508) /* 5XX */
|| (status == NGX_HTTP_CREATED || status == NGX_HTTP_NO_CONTENT) ) {
return status;
}
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* create loc conf struct */
static void *
ngx_http_modsecurity_create_loc_conf(ngx_conf_t *cf)
@ -952,18 +976,18 @@ ngx_http_modsecurity_handler(ngx_http_request_t *r)
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: handler");
/* create / retrive request ctx */
if (r->internal) {
/* we have already processed the request headers with previous loc conf */
/* TODO: do we need update ctx and process headers again? */
ctx = ngx_http_get_module_pool_ctx(r, ngx_http_modsecurity);
if (ctx) {
/* we have already processed the request headers */
ngx_http_set_ctx(r, ctx, ngx_http_modsecurity);
return NGX_DECLINED;
}
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: get internel request ctx failed");
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "modSecurity: request pool ctx empty");
}
ctx = ngx_http_modsecurity_create_ctx(r);
@ -978,52 +1002,34 @@ ngx_http_modsecurity_handler(ngx_http_request_t *r)
return NGX_ERROR;
}
ngx_http_modsecurity_load_request(r);
if (ngx_http_modsecurity_load_headers_in(r) != NGX_OK) {
/* load request to request rec */
if (ngx_http_modsecurity_load_request(r) != NGX_OK
|| ngx_http_modsecurity_load_headers_in(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* processing request headers */
rc = modsecProcessRequestHeaders(ctx->req);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: modsecProcessRequestHeaders %d", rc);
rc = ngx_http_modsecurity_status(r, modsecProcessRequestHeaders(ctx->req));
if (rc == DECLINED) {
if (modsecIsRequestBodyAccessEnabled(ctx->req)
&& r->method == NGX_HTTP_POST) {
/* Processing POST request body, should we process PUT? */
rc = ngx_http_read_client_request_body(r, ngx_http_modsecurity_body_handler);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE;
}
/* other method */
rc = modsecProcessRequestBody(ctx->req);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: modsecProcessRequestBody %d", rc);
}
if (rc != DECLINED) {
/* Nginx and Apache share same response code */
if (rc < NGX_HTTP_SPECIAL_RESPONSE || rc >= 600) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (rc != NGX_DECLINED) {
return rc;
}
/*
if (ngx_http_modsecurity_save_headers_in(r) != NGX_OK) {
if (r->method == NGX_HTTP_POST
&& modsecIsRequestBodyAccessEnabled(ctx->req) ) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
/* read POST request body, should we process PUT? */
rc = ngx_http_read_client_request_body(r, ngx_http_modsecurity_body_handler);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE;
}
*/
return NGX_DECLINED;
/* other method */
return ngx_http_modsecurity_status(r, modsecProcessRequestBody(ctx->req));
}
@ -1038,19 +1044,12 @@ ngx_http_modsecurity_body_handler(ngx_http_request_t *r)
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity);
if (ngx_http_modsecurity_load_request_body(r) != NGX_OK) {
return ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
rc = modsecProcessRequestBody(ctx->req);
rc = ngx_http_modsecurity_status(r, modsecProcessRequestBody(ctx->req));
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: modsecProcessRequestBody %d", rc);
if (rc != DECLINED) {
/* Nginx and Apache share same response code */
if (rc < NGX_HTTP_SPECIAL_RESPONSE || rc >= 600) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (rc != NGX_DECLINED) {
return ngx_http_finalize_request(r, rc);
}
@ -1070,11 +1069,39 @@ static ngx_int_t
ngx_http_modsecurity_header_filter(ngx_http_request_t *r) {
ngx_http_modsecurity_loc_conf_t *cf;
ngx_http_modsecurity_ctx_t *ctx;
const char *location;
ngx_table_elt_t *h;
ngx_int_t rc;
cf = ngx_http_get_module_loc_conf(r, ngx_http_modsecurity);
ctx = ngx_http_get_module_ctx(r, ngx_http_modsecurity);
if (ctx && ctx->complete
&& r->err_status >= NGX_HTTP_MOVED_PERMANENTLY
&& r->err_status < 308) {
/* 3XX load redirect location header so that we can do redirec in phase 3,4 */
location = apr_table_get(ctx->req->headers_out, "Location");
if (location == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
h = ngx_list_push(&r->headers_out.headers);
if (h == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
h->hash = 1;
h->key.data = (u_char *)"Location";
h->key.len = ngx_strlen("Location");
h->value.data = (u_char *)location;
h->value.len = ngx_strlen(location);
return ngx_http_next_header_filter(r);
}
if (r != r->main || !cf->enable || ctx->complete) {
return ngx_http_next_header_filter(r);
}
@ -1091,24 +1118,18 @@ ngx_http_modsecurity_header_filter(ngx_http_request_t *r) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
rc = modsecProcessResponse(ctx->req);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: modsecProcessResponse %d", rc);
rc = ngx_http_modsecurity_status(r, modsecProcessResponse(ctx->req));
if (rc == DECLINED || rc == APR_SUCCESS) {
if (ngx_http_modsecurity_save_headers_in(r) != NGX_OK
|| ngx_http_modsecurity_save_headers_out(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return ngx_http_next_header_filter(r);
if (rc != NGX_DECLINED) {
return rc;
}
if (rc < NGX_HTTP_SPECIAL_RESPONSE || rc >= 600) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
if (ngx_http_modsecurity_save_headers_in(r) != NGX_OK
|| ngx_http_modsecurity_save_headers_out(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return rc;
return ngx_http_next_header_filter(r);
}
return NGX_OK;
@ -1142,58 +1163,47 @@ ngx_http_modsecurity_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
}
/* last buf has been saved */
ctx->complete = 1;
modsecSetResponseBrigade(ctx->req, ctx->brigade);
// TODO: do we need reload headers_in ?
//
if (ngx_http_modsecurity_load_headers_in(r) != NGX_OK
|| ngx_http_modsecurity_load_headers_out(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
rc = modsecProcessResponse(ctx->req);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ModSecurity: modsecProcessResponse %d", rc);
rc = ngx_http_modsecurity_status(r, modsecProcessResponse(ctx->req));
if (rc == DECLINED || rc == APR_SUCCESS) {
in = NULL;
apr_brigade_length(ctx->brigade, 0, &content_length);
rc = move_brigade_to_chain(ctx->brigade, &in, r->pool);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
if (ngx_http_modsecurity_save_headers_in(r) != NGX_OK
||ngx_http_modsecurity_save_headers_out(r) != NGX_OK) {
return ngx_http_filter_finalize_request(r, &ngx_http_modsecurity, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
if (r->headers_out.content_length_n != -1) {
r->headers_out.content_length_n = content_length;
r->headers_out.content_length = NULL; /* header filter will set this */
}
rc = ngx_http_next_header_filter(r);
if (rc == NGX_ERROR || rc > NGX_OK) {
return ngx_http_filter_finalize_request(r, &ngx_http_modsecurity, rc);
}
return ngx_http_next_body_filter(r, in);
if (rc != NGX_DECLINED) {
return ngx_http_filter_finalize_request(r, &ngx_http_modsecurity, rc);
}
if (rc < NGX_HTTP_SPECIAL_RESPONSE || rc >= 600) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
apr_brigade_length(ctx->brigade, 0, &content_length);
rc = move_brigade_to_chain(ctx->brigade, &in, r->pool);
if (rc == NGX_ERROR) {
return NGX_ERROR;
}
return ngx_http_filter_finalize_request(r, &ngx_http_modsecurity, rc);
if (ngx_http_modsecurity_save_headers_in(r) != NGX_OK
||ngx_http_modsecurity_save_headers_out(r) != NGX_OK) {
return ngx_http_filter_finalize_request(r, &ngx_http_modsecurity, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
if (r->headers_out.content_length_n != -1) {
r->headers_out.content_length_n = content_length;
r->headers_out.content_length = NULL; /* header filter will set this */
}
rc = ngx_http_next_header_filter(r);
if (rc == NGX_ERROR || rc > NGX_OK) {
return ngx_http_filter_finalize_request(r, &ngx_http_modsecurity, rc);
}
return ngx_http_next_body_filter(r, in);
}

View File

@ -72,6 +72,10 @@ standalone_la_LDFLAGS = -no-undefined -module -avoid-version \
@PCRE_LDFLAGS@ @LIBXML2_LDFLAGS@ @LUA_LDFLAGS@
endif
standalone_INCS = `echo "@LIBXML2_CFLAGS@ @LUA_CFLAGS@" | sed -n 's/ *-I *\([^ ]*\) /\1 /gp'` \
@APXS_INCLUDEDIR@ @APR_INCLUDEDIR@ @APU_INCLUDEDIR@
standalone_LIBS = @APR_LINKLD@ @APU_LINKLD@ @APXS_LDFLAGS@ \
@PCRE_LDADD@ @LIBXML2_LDADD@ @LUA_LDADD@
install-exec-hook: $(pkglib_LTLIBRARIES)
@echo "Creating Nginx config file..."; \
rm -f ../nginx/modsecurity/config; \
@ -80,8 +84,8 @@ install-exec-hook: $(pkglib_LTLIBRARIES)
echo "HTTP_AUX_FILTER_MODULES=\"ngx_http_modsecurity \$$HTTP_AUX_FILTER_MODULES\"" >> ../nginx/modsecurity/config; \
echo "NGX_ADDON_SRCS=\"\$$NGX_ADDON_SRCS \$$ngx_addon_dir/ngx_http_modsecurity.c \$$ngx_addon_dir/apr_bucket_nginx.c \$$ngx_addon_dir/ngx_pool_context.c\"" >> ../nginx/modsecurity/config;\
echo "NGX_ADDON_DEPS=\"\$$NGX_ADDON_DEPS \$$ngx_addon_dir/apr_bucket_nginx.h \$$ngx_addon_dir/ngx_pool_context.h\"" >> ../nginx/modsecurity/config; \
echo "CORE_LIBS=\"\$$CORE_LIBS \$$ngx_addon_dir/../../standalone/.libs/standalone.a -lapr-1 -laprutil-1 -lxml2 -lm @LUA_LDADD@\"" >> ../nginx/modsecurity/config; \
echo "CORE_INCS=\"\$$CORE_INCS /usr/include/apache2 /usr/include/apr-1.0 /usr/include/httpd /usr/include/apr-1 \$$ngx_addon_dir \$$ngx_addon_dir/../../standalone \$$ngx_addon_dir/../../apache2 /usr/include/libxml2 `echo @LUA_CFLAGS@ | cut -d "I" -f3`\"" >> ../nginx/modsecurity/config; \
echo "CORE_LIBS=\"\$$CORE_LIBS \$$ngx_addon_dir/../../standalone/.libs/standalone.a $(standalone_LIBS) \"" >> ../nginx/modsecurity/config; \
echo "CORE_INCS=\"\$$CORE_INCS \$$ngx_addon_dir \$$ngx_addon_dir/../../standalone \$$ngx_addon_dir/../../apache2 $(standalone_INCS)\"" >> ../nginx/modsecurity/config; \
echo "Removing unused static libraries..."; \
for m in $(pkglib_LTLIBRARIES); do \
base=`echo $$m | sed 's/\..*//'`; \

View File

@ -1,9 +1,8 @@
# Makefile.in generated by automake 1.11.6 from Makefile.am.
# Makefile.in generated by automake 1.12.2 from Makefile.am.
# @configure_input@
# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software
# Foundation, Inc.
# Copyright (C) 1994-2012 Free Software Foundation, Inc.
# This Makefile.in is free software; the Free Software Foundation
# gives unlimited permission to copy and/or distribute it,
# with or without modifications, as long as this notice is preserved.
@ -51,7 +50,8 @@ POST_UNINSTALL = :
build_triplet = @build@
host_triplet = @host@
subdir = standalone
DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in
DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in \
$(top_srcdir)/build/depcomp
ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
am__aclocal_m4_deps = $(top_srcdir)/build/find_apr.m4 \
$(top_srcdir)/build/find_apu.m4 \
@ -149,13 +149,17 @@ AMTAR = @AMTAR@
APR_CFLAGS = @APR_CFLAGS@
APR_CONFIG = @APR_CONFIG@
APR_CPPFLAGS = @APR_CPPFLAGS@
APR_INCLUDEDIR = @APR_INCLUDEDIR@
APR_LDADD = @APR_LDADD@
APR_LDFLAGS = @APR_LDFLAGS@
APR_LINKLD = @APR_LINKLD@
APR_VERSION = @APR_VERSION@
APU_CFLAGS = @APU_CFLAGS@
APU_CONFIG = @APU_CONFIG@
APU_INCLUDEDIR = @APU_INCLUDEDIR@
APU_LDADD = @APU_LDADD@
APU_LDFLAGS = @APU_LDFLAGS@
APU_LINKLD = @APU_LINKLD@
APU_VERSION = @APU_VERSION@
APXS = @APXS@
APXS_BINDIR = @APXS_BINDIR@
@ -381,6 +385,12 @@ standalone_la_LIBADD = @APR_LDADD@ @APU_LDADD@ @PCRE_LDADD@ @LIBXML2_LDADD@ @LUA
@SOLARIS_TRUE@ @APR_LDFLAGS@ @APU_LDFLAGS@ @APXS_LDFLAGS@ \
@SOLARIS_TRUE@ @PCRE_LDFLAGS@ @LIBXML2_LDFLAGS@ @LUA_LDFLAGS@
standalone_INCS = `echo "@LIBXML2_CFLAGS@ @LUA_CFLAGS@" | sed -n 's/ *-I *\([^ ]*\) /\1 /gp'` \
@APXS_INCLUDEDIR@ @APR_INCLUDEDIR@ @APU_INCLUDEDIR@
standalone_LIBS = @APR_LINKLD@ @APU_LINKLD@ @APXS_LDFLAGS@ \
@PCRE_LDADD@ @LIBXML2_LDADD@ @LUA_LDADD@
all: all-am
.SUFFIXES:
@ -441,12 +451,14 @@ uninstall-pkglibLTLIBRARIES:
clean-pkglibLTLIBRARIES:
-test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES)
@list='$(pkglib_LTLIBRARIES)'; for p in $$list; do \
dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \
test "$$dir" != "$$p" || dir=.; \
echo "rm -f \"$${dir}/so_locations\""; \
rm -f "$${dir}/so_locations"; \
done
@list='$(pkglib_LTLIBRARIES)'; \
locs=`for p in $$list; do echo $$p; done | \
sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
sort -u`; \
test -z "$$locs" || { \
echo rm -f $${locs}; \
rm -f $${locs}; \
}
standalone.la: $(standalone_la_OBJECTS) $(standalone_la_DEPENDENCIES) $(EXTRA_standalone_la_DEPENDENCIES)
$(standalone_la_LINK) -rpath $(pkglibdir) $(standalone_la_OBJECTS) $(standalone_la_LIBADD) $(LIBS)
@ -797,6 +809,20 @@ GTAGS:
&& $(am__cd) $(top_srcdir) \
&& gtags -i $(GTAGS_ARGS) "$$here"
cscopelist: $(HEADERS) $(SOURCES) $(LISP)
list='$(SOURCES) $(HEADERS) $(LISP)'; \
case "$(srcdir)" in \
[\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
*) sdir=$(subdir)/$(srcdir) ;; \
esac; \
for i in $$list; do \
if test -f "$$i"; then \
echo "$(subdir)/$$i"; \
else \
echo "$$sdir/$$i"; \
fi; \
done >> $(top_builddir)/cscope.files
distclean-tags:
-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
@ -942,32 +968,30 @@ uninstall-am: uninstall-pkglibLTLIBRARIES
.MAKE: install-am install-exec-am install-strip
.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
clean-libtool clean-pkglibLTLIBRARIES ctags distclean \
distclean-compile distclean-generic distclean-libtool \
distclean-tags distdir dvi dvi-am html html-am info info-am \
install install-am install-data install-data-am install-dvi \
install-dvi-am install-exec install-exec-am install-exec-hook \
install-html install-html-am install-info install-info-am \
install-man install-pdf install-pdf-am \
install-pkglibLTLIBRARIES install-ps install-ps-am \
install-strip installcheck installcheck-am installdirs \
maintainer-clean maintainer-clean-generic mostlyclean \
mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
pdf pdf-am ps ps-am tags uninstall uninstall-am \
uninstall-pkglibLTLIBRARIES
clean-libtool clean-pkglibLTLIBRARIES cscopelist ctags \
distclean distclean-compile distclean-generic \
distclean-libtool distclean-tags distdir dvi dvi-am html \
html-am info info-am install install-am install-data \
install-data-am install-dvi install-dvi-am install-exec \
install-exec-am install-exec-hook install-html install-html-am \
install-info install-info-am install-man install-pdf \
install-pdf-am install-pkglibLTLIBRARIES install-ps \
install-ps-am install-strip installcheck installcheck-am \
installdirs maintainer-clean maintainer-clean-generic \
mostlyclean mostlyclean-compile mostlyclean-generic \
mostlyclean-libtool pdf pdf-am ps ps-am tags uninstall \
uninstall-am uninstall-pkglibLTLIBRARIES
install-exec-hook: $(pkglib_LTLIBRARIES)
@echo "Creating Nginx config file..."; \
rm -f ../nginx/modsecurity/config; \
echo "ngx_addon_name=ngx_http_modsecurity" >> ../nginx/modsecurity/config; \
echo "# HTTP_MODULES=\"\$$HTTP_MODULES ngx_http_modsecurity\"" >> ../nginx/modsecurity/config; \
echo "HTTP_HEADERS_FILTER_MODULE=\"ngx_http_modsecurity \$$HTTP_HEADERS_FILTER_MODULE\"" >> ../nginx/modsecurity/config; \
echo "NGX_ADDON_SRCS=\"\$$NGX_ADDON_SRCS \$$ngx_addon_dir/ngx_http_modsecurity.c \$$ngx_addon_dir/apr_bucket_nginx.c\"" >> ../nginx/modsecurity/config;\
echo "NGX_ADDON_DEPS=\"\$$NGX_ADDON_DEPS\"" >> ../nginx/modsecurity/config; \
echo "CORE_LIBS=\"\$$CORE_LIBS \$$ngx_addon_dir/../../standalone/.libs/standalone.a -lapr-1 -laprutil-1 -lxml2 -lm @LUA_LDADD@\"" >> ../nginx/modsecurity/config; \
echo "CORE_INCS=\"\$$CORE_INCS /usr/include/apache2 /usr/include/apr-1.0 /usr/include/httpd /usr/include/apr-1 \$$ngx_addon_dir \$$ngx_addon_dir/../../standalone \$$ngx_addon_dir/../../apache2 /usr/include/libxml2 `echo @LUA_CFLAGS@ | cut -d "I" -f3`\"" >> ../nginx/modsecurity/config; \
echo "have=REQUEST_EARLY . auto/have" >> ../nginx/modsecurity/config;\
echo "CORE_MODULES=\"\$$CORE_MODULES ngx_pool_context_module\"" >> ../nginx/modsecurity/config; \
echo "HTTP_AUX_FILTER_MODULES=\"ngx_http_modsecurity \$$HTTP_AUX_FILTER_MODULES\"" >> ../nginx/modsecurity/config; \
echo "NGX_ADDON_SRCS=\"\$$NGX_ADDON_SRCS \$$ngx_addon_dir/ngx_http_modsecurity.c \$$ngx_addon_dir/apr_bucket_nginx.c \$$ngx_addon_dir/ngx_pool_context.c\"" >> ../nginx/modsecurity/config;\
echo "NGX_ADDON_DEPS=\"\$$NGX_ADDON_DEPS \$$ngx_addon_dir/apr_bucket_nginx.h \$$ngx_addon_dir/ngx_pool_context.h\"" >> ../nginx/modsecurity/config; \
echo "CORE_LIBS=\"\$$CORE_LIBS \$$ngx_addon_dir/../../standalone/.libs/standalone.a $(standalone_LIBS) \"" >> ../nginx/modsecurity/config; \
echo "CORE_INCS=\"\$$CORE_INCS \$$ngx_addon_dir \$$ngx_addon_dir/../../standalone \$$ngx_addon_dir/../../apache2 $(standalone_INCS)\"" >> ../nginx/modsecurity/config; \
echo "Removing unused static libraries..."; \
for m in $(pkglib_LTLIBRARIES); do \
base=`echo $$m | sed 's/\..*//'`; \

View File

@ -223,40 +223,10 @@ apr_status_t ap_http_in_filter(ap_filter_t *f, apr_bucket_brigade *bb_out,
}
apr_status_t ap_http_out_filter(ap_filter_t *f, apr_bucket_brigade *b) {
modsec_rec *msr = (modsec_rec *)f->ctx;
apr_status_t rc;
apr_bucket_brigade *bb_out;
bb_out = modsecGetResponseBrigade(f->r);
if (bb_out) {
APR_BRIGADE_CONCAT(bb_out, b);
return APR_SUCCESS;
}
// is there a way to tell whether the response body was modified or not?
//
if((msr->txcfg->content_injection_enabled || msr->content_prepend_len != 0 || msr->content_append_len != 0)
&& msr->txcfg->resbody_access) {
if (modsecWriteResponse != NULL) {
char *data = NULL;
apr_size_t length;
rc = apr_brigade_pflatten(msr->of_brigade, &data, &length, msr->mp);
if (rc != APR_SUCCESS) {
msr_log(msr, 1, "Output filter: Failed to flatten brigade (%d): %s", rc,
get_apr_error(msr->mp, rc));
return -1;
}
/* TODO: return ?*/
modsecWriteResponse(msr->r, data, msr->stream_output_length);
}
}
apr_bucket_brigade *bb_out = (apr_bucket_brigade *)f->ctx;
APR_BRIGADE_CONCAT(bb_out, b);
return APR_SUCCESS;
}
@ -551,74 +521,117 @@ int modsecIsResponseBodyAccessEnabled(request_rec *r)
}
int modsecProcessResponse(request_rec *r) {
int status = DECLINED;
int status;
modsec_rec *msr;
apr_bucket *e;
ap_filter_t *f;
apr_bucket_brigade *bb_in, *bb_out, *bb;
if(r->output_filters != NULL) {
modsec_rec *msr = (modsec_rec *)r->output_filters->ctx;
char buf[8192];
char *tmp = NULL;
apr_bucket *e = NULL;
if(r->output_filters == NULL) {
return DECLINED;
}
msr = (modsec_rec *)r->output_filters->ctx;
if (msr == NULL) {
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server,
"ModSecurity: Internal Error: msr is null in output filter.");
ap_remove_output_filter(r->output_filters);
return APR_EGENERAL;
}
msr->r = r;
/* create input response brigade */
bb_in = apr_brigade_create(msr->mp, r->connection->bucket_alloc);
if (bb_in == NULL) {
msr_log(msr, 1, "Process response: Failed to create brigade.");
return APR_EGENERAL;
}
/* get input response brigade */
bb = modsecGetResponseBrigade(r);
if (bb != NULL) {
APR_BRIGADE_CONCAT(bb_in, bb);
if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb_in))) {
e = apr_bucket_eos_create(bb_in->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb_in, e);
}
} else if (modsecReadResponse != NULL) {
unsigned int readcnt = 0;
int is_eos = 0;
ap_filter_t *f = NULL;
apr_bucket_brigade *bb_in, *bb = NULL;
char buf[8192];
while(!is_eos) {
modsecReadResponse(r, buf, 8192, &readcnt, &is_eos);
if (msr == NULL) {
ap_log_error(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, 0, r->server,
"ModSecurity: Internal Error: msr is null in output filter.");
ap_remove_output_filter(r->output_filters);
return send_error_bucket(msr, r->output_filters, HTTP_INTERNAL_SERVER_ERROR);
}
bb = apr_brigade_create(msr->mp, r->connection->bucket_alloc);
if (bb == NULL) {
msr_log(msr, 1, "Process response: Failed to create brigade.");
return APR_EGENERAL;
}
msr->r = r;
bb_in = modsecGetResponseBrigade(r);
if (bb_in != NULL) {
APR_BRIGADE_CONCAT(bb, bb_in);
if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
e = apr_bucket_eos_create(bb->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
if(readcnt > 0) {
char *tmp = (char *)apr_palloc(r->pool, readcnt);
memcpy(tmp, buf, readcnt);
e = apr_bucket_pool_create(tmp, readcnt, r->pool, r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb_in, e);
}
} else if (modsecReadResponse != NULL) {
while(!is_eos) {
modsecReadResponse(r, buf, 8192, &readcnt, &is_eos);
}
if(readcnt > 0) {
tmp = (char *)apr_palloc(r->pool, readcnt);
memcpy(tmp, buf, readcnt);
e = apr_bucket_pool_create(tmp, readcnt, r->pool, r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
}
e = apr_bucket_eos_create(r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb_in, e);
} else {
/* cannot read response body process header only */
e = apr_bucket_eos_create(r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb_in, e);
}
bb_out = bb ? bb : apr_brigade_create(msr->mp, r->connection->bucket_alloc);
if (bb_out == NULL) {
msr_log(msr, 1, "Process response: Failed to create brigade.");
return APR_EGENERAL;
}
/* concat output bucket to bb_out */
f = ap_add_output_filter("HTTP_OUT", bb_out, r, r->connection);
status = ap_pass_brigade(r->output_filters, bb_in);
ap_remove_output_filter(f);
if (status == APR_EGENERAL) {
/* retrive response status from bb_out */
for(e = APR_BRIGADE_FIRST(bb_out);
e != APR_BRIGADE_SENTINEL(bb_out);
e = APR_BUCKET_NEXT(e)) {
if (AP_BUCKET_IS_ERROR(e)) {
return ((ap_bucket_error*) e->data)->status;
}
e = apr_bucket_eos_create(r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
} else {
/* cannot read response body process header only */
e = apr_bucket_eos_create(r->connection->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, e);
}
f = ap_add_output_filter("HTTP_OUT", msr, r, r->connection);
status = ap_pass_brigade(r->output_filters, bb);
ap_remove_output_filter(f);
if(status > 0
&& msr->intercept_actionset->intercept_status != 0) {
status = msr->intercept_actionset->intercept_status;
}
return APR_EGENERAL;
}
if (status != DECLINED) {
return status;
}
return status;
/* copy bb_out */
// is there a way to tell whether the response body was modified or not?
if (modsecWriteResponse != NULL
&& (msr->txcfg->content_injection_enabled || msr->content_prepend_len != 0 || msr->content_append_len != 0)
&& msr->txcfg->resbody_access) {
char *data = NULL;
apr_size_t length;
status = apr_brigade_pflatten(msr->of_brigade, &data, &length, msr->mp);
if (status != APR_SUCCESS) {
msr_log(msr, 1, "Output filter: Failed to flatten brigade (%d): %s", status,
get_apr_error(msr->mp, status));
return APR_EGENERAL;
}
if ( modsecWriteResponse(msr->r, data, msr->stream_output_length) != APR_SUCCESS) {
return APR_EGENERAL;
}
}
return DECLINED;
}
int modsecFinishRequest(request_rec *r) {

View File

View File

@ -0,0 +1,22 @@
user root;
worker_processes 1;
daemon on;
error_log logs/error.log debug;
events {
worker_connections 1024;
}
http {
ModSecurityEnabled [% enable %];
ModSecurityConfig [% config %];
server {
listen [% listen %];
server_name localhost;
location / {
}
}
}

View File

@ -0,0 +1,736 @@
#!/usr/bin/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 File::Path qw(make_path);
use FileHandle;
use IPC::Open2 qw(open2);
use IPC::Open3 qw(open3);
use Getopt::Std;
use Data::Dumper;
use IO::Socket;
use LWP::UserAgent;
use Cwd 'abs_path';
use Template;
use File::Copy::Recursive qw(dircopy);
my @TYPES = qw(action config misc rule target);
my $SCRIPT = basename($0);
my $SCRIPT_DIR = File::Spec->rel2abs(dirname($0));
my $REG_DIR = "$SCRIPT_DIR/regression";
my $NGINX_DIR = "$REG_DIR/nginx";
my $NGINX_CONF_TEMP = "$REG_DIR/nginx/conf/nginx.conf.template";
my $NGINX = q(/usr/local/nginx/sbin/nginx);
my $PASSED = 0;
my $TOTAL = 0;
my $BUFSIZ = 32768;
my %C = ();
my %FILE = ();
my $UA_NAME = "ModSecurity Regression Tests/1.2.3";
my $UA = LWP::UserAgent->new;
$UA->agent($UA_NAME);
$SIG{TERM} = $SIG{INT} = \&handle_interrupt;
my %opt;
getopts('A:E:D:C:T:H:a:p:dvh', \%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:
-P path Specify nginx prefix path (default: $NGINX_DIR)
-a file Specify nginx binary (default: $NGINX)
-p port Specify nginx port (default: 8088)
-v Enable verbose output (details on failure).
-d Enable debugging output.
-h This help.
EOT
exit(1);
}
usage() if ($opt{h});
### Check nginx binary
if (defined $opt{a}) {
$NGINX = $opt{a};
}
else {
$opt{a} = $NGINX;
}
usage("Invalid Apache startup script: $NGINX\n") unless (-e $NGINX);
### Defaults
$opt{P} = "$NGINX_DIR" unless (defined $opt{P});
my $CONF_DIR = "$opt{P}/conf";
my $FILES_DIR = "$opt{P}/logs";
my $PID_FILE = "$FILES_DIR/nginx.pid";
$opt{A} = "$FILES_DIR/modsec_audit.log";
$opt{D} = "$FILES_DIR/modsec_debug.log";
$opt{E} = "$FILES_DIR/error.log";
$opt{C} = "$CONF_DIR/nginx.conf";
$opt{p} = 8088 unless (defined $opt{p});
$opt{v} = 1 if ($opt{d});
if ( !-d "$opt{P}" ) {
make_path($opt{P}) or die $!;
}
if ( !-d "$opt{P}/logs" ) {
make_path("$opt{P}/logs") or die $!;
}
if ( !-d "$opt{P}/html" ) {
make_path("$opt{P}/html") or die $!;
}
dircopy("$REG_DIR/server_root/htdocs","$opt{P}/html") or die $!;
%ENV = (
%ENV,
$NGINX_DIR => $opt{P},
SERVER_PORT => $opt{p},
SERVER_NAME => "localhost",
# TEST_NGX_PREFIX => $NGINX_DIR,
# DATA_DIR => $DATA_DIR,
# TEMP_DIR => $TEMP_DIR,
# UPLOAD_DIR => $UPLOAD_DIR,
CONF_DIR => $CONF_DIR,
# MODULES_DIR => $MODULES_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},
NGINX_CONF => $opt{C},
# HTDOCS => $opt{H},
USER_AGENT => $UA_NAME,
);
#dbg("OPTIONS: ", \%opt);
if (-e "$PID_FILE") {
msg("Shutting down previous instance: $PID_FILE");
nginx_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 $nginx_up = 0;
my %t = %{$t || {}};
my $id = sprintf("%3d", $n);
my $out = "";
my $rc = 0;
my $conf_fn;
# Startup nginx 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;
my %conf=(config => $conf_fn, enable => "on");
$nginx_up = nginx_start($t, \%conf) ? 0 : 1;
}
else {
$nginx_up = nginx_start($t) ? 0 : 1;
}
# Run any prerun setup
if ($rc == 0 and exists $t{prerun} and defined $t{prerun}) {
vrb("Executing perl prerun...");
$rc = &{$t{prerun}};
vrb("Perl prerun returned: $rc");
}
if ($nginx_up) {
# Perform the request and check response
if (exists $t{request}) {
my $resp = do_request($t{request});
if (!$resp) {
msg("invalid response");
vrb("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");
vrb($resp);
last;
}
elsif (!$neg and !defined $match) {
$rc = 1;
msg("response $mtype failed to match: $m");
vrb($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]");
last;
}
elsif (!$neg and !defined $match) {
$rc = 1;
msg("$mtype log failed to match: $m->[0]");
last;
}
}
}
# Search for all file matches
if ($rc == 0 and exists $t{match_file} and defined $t{match_file}) {
sleep 1; # Make sure the file exists
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");
last;
}
elsif (!$neg and !defined $match) {
$rc = 1;
msg("$fn file failed match: $m");
last;
}
}
}
}
else {
msg("Failed to start nginx.");
$rc = 1;
}
if ($rc == 0) {
$pass++;
}
else {
vrb("Test Config: $conf_fn");
vrb("Debug Log: $FILE{debug}{fn}");
dbg(escape("$FILE{debug}{buf}"));
vrb("Error Log: $FILE{error}{fn}");
dbg(escape("$FILE{error}{buf}"));
}
msg(sprintf("%s) %s%s: %s%s", $id, $t{type}, (exists($t{comment}) ? " - $t{comment}" : ""), ($rc ? "failed" : "passed"), ((defined($out) && $out ne "")? " ($out)" : "")));
if ($nginx_up) {
$nginx_up = nginx_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") {
my $resp = $UA->request($r);
dbg($resp->request()->as_string()) if ($opt{d});
return $resp
}
else {
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 read_log {
my($name, $timeout, $graph) = @_;
return match_log($name, undef, $timeout, $graph);
}
sub match_log {
my($name, $re, $timeout, $graph) = @_;
my $t0 = gettimeofday;
my($fh,$rbuf) = ($FILE{$name}{fd}, \$FILE{$name}{buf});
my $n = length($$rbuf);
my $rc = undef;
unless (defined $fh) {
msg("Error: File \"$name\" is not opened for matching.");
return;
}
$timeout = 0 unless (defined $timeout);
my $i = 0;
my $graphed = 0;
READ: {
do {
my $nbytes = $fh->sysread($$rbuf, $BUFSIZ, $n);
if (!defined($nbytes)) {
msg("Error: Could not read \"$name\" log: $!");
last;
}
elsif (!defined($re) and $nbytes == 0) {
last;
}
# Remove APR pool debugging
$$rbuf =~ s/POOL DEBUG:[^\n]+PALLOC[^\n]+\n//sg;
$n = length($$rbuf);
#dbg("Match \"$re\" in $name \"$$rbuf\" ($n)");
if ($$rbuf =~ m/$re/m) {
$rc = $&;
last;
}
# TODO: Use select()/poll()
sleep 0.1 unless ($nbytes == $BUFSIZ);
if ($graph and $opt{d}) {
$i++;
if ($i == 10) {
$graphed++;
$i=0;
print STDERR $graph if ($graphed == 1);
print STDERR "."
}
}
} while (gettimeofday - $t0 < $timeout);
}
print STDERR "\n" if ($graphed);
return $rc;
}
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 vrb {
return unless(@_ and $opt{v});
msg(@_);
}
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...");
nginx_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 nginx_stop {
my $t = shift;
my @p = (
$NGINX,
-p => $opt{P},
-s => "quit",
);
my $nginx_out;
my $nginx_pid = open3(undef, $nginx_out, undef, @p) or quit(1);
my $out = join("\\n", grep(!/POOL DEBUG/, (<$nginx_out>)));
close $nginx_out;
waitpid($nginx_pid, 0);
my $rc = $?;
if ( WIFEXITED($rc) ) {
$rc = WEXITSTATUS($rc);
vrb("Nginx stop returned with $rc.") if ($rc);
}
elsif( WIFSIGNALED($rc) ) {
msg("Nginx stop failed with signal " . WTERMSIG($rc) . ".");
$rc = -1;
}
else {
msg("Nginx stop failed with unknown error.");
$rc = -1;
}
sleep 0.5;
if (-e $PID_FILE) {
msg("Nginx stop failed: $PID_FILE still exists");
}
return $rc;
}
sub nginx_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"
}
sub nginx_start {
my ($t) = shift;
my($C) = shift;
my %conf = (
listen => "$opt{p}",
config => "$REG_DIR/nginx/conf/empty.conf",
enable => "off",
);
while(my($k,$v)= each %$C){
$conf{$k}=$v;
}
my ($tt) = Template->new(INCLUDE_PATH => "$REG_DIR/nginx/conf/");
my ($output);
$tt->process("nginx.conf.template", \%conf, \$output) || die $tt->error;
open (OUTFILE, ">$opt{C}");
print OUTFILE "$output";
close(OUTFILE);
nginx_reset_fd($t);
my @p = ($NGINX, -p => $opt{P});
my $nginx_out;
my $nginx_pid = open3(undef, $nginx_out, undef, @p) or quit(1);
my $out = join("\\n", grep(!/POOL DEBUG/, (<$nginx_out>)));
close $nginx_out;
waitpid($nginx_pid, 0);
my $rc = $?;
if ( WIFEXITED($rc) ) {
$rc = WEXITSTATUS($rc);
vrb("Nginx start returned with $rc.") if ($rc);
}
elsif( WIFSIGNALED($rc) ) {
msg("Nginx start failed with signal " . WTERMSIG($rc) . ".");
$rc = -1;
}
else {
msg("Nginx start failed with unknown error.");
$rc = -1;
}
# Look for startup msg
# unless (defined match_log("error", qr/start worker process/, 60, "Waiting on nginx to start: ")) {
# vrb(join(" ", map { quote_shell($_) } @p));
# vrb(match_log("error", qr/(^.*ModSecurity: .*)/sm, 10));
# msg("Nginx server failed to start.");
# nginx_stop();
# return -1;
# }
return $rc;
}