From e5becf8407098ca24eecfee3ccd0e1d9103d945e Mon Sep 17 00:00:00 2001 From: b1v1r Date: Sun, 31 May 2009 08:45:50 +0000 Subject: [PATCH] Merge 2.5.x changes to trunk. --- CHANGES | 7 +- apache2/acmp.c | 3 + apache2/apache2_util.c | 55 ++++++-- apache2/build/find_xml.m4 | 24 ++-- apache2/configure | 180 ++++++++++++++++---------- apache2/configure.in | 4 +- apache2/msc_release.h | 3 +- apache2/t/regression/rule/10-xml.t | 58 +++++++++ doc/modsecurity2-apache-reference.xml | 4 +- 9 files changed, 240 insertions(+), 98 deletions(-) diff --git a/CHANGES b/CHANGES index 917c1275..029d5dfd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,11 @@ -20 May 2009 - trunk +31 May 2009 - trunk ------------------- + * Removed extra newline from audit log message line when logging XML errors. + This was causing problems parsing audit logs. + + * Fixed @pm/@pmFromFile case insensitivity. + * Truncate long parameters in log message for "Match of ... against ... required" messages. diff --git a/apache2/acmp.c b/apache2/acmp.c index 4293e2ca..d8c0c0aa 100644 --- a/apache2/acmp.c +++ b/apache2/acmp.c @@ -782,6 +782,9 @@ apr_status_t acmp_process_quick(ACMPT *acmpt, const char **match, const char *da while (data < end) { acmp_utf8_char_t letter = (unsigned char)*data++; + + if (parser->is_case_sensitive == 0) letter = utf8_lcase(letter); + go_to = NULL; while (go_to == NULL) { go_to = acmp_goto(node, letter); diff --git a/apache2/apache2_util.c b/apache2/apache2_util.c index 0e86033d..c0548d5a 100644 --- a/apache2/apache2_util.c +++ b/apache2/apache2_util.c @@ -236,7 +236,6 @@ void internal_log(request_rec *r, directory_config *dcfg, modsec_rec *msr, apr_size_t nbytes, nbytes_written; apr_file_t *debuglog_fd = NULL; int filter_debug_level = 0; - int str2len; char str1[1024] = ""; char str2[1256] = ""; @@ -257,20 +256,16 @@ void internal_log(request_rec *r, directory_config *dcfg, modsec_rec *msr, */ if ((level > 3)&&( (debuglog_fd == NULL) || (level > filter_debug_level) )) return; - /* Construct the message (leaving a byte left for a newline if needed). */ + /* Construct the message. */ apr_vsnprintf(str1, sizeof(str1), text, ap); - str2len = apr_snprintf(str2, sizeof(str2) - 1, - "[%s] [%s/sid#%pp][rid#%pp][%s][%d] %s", + + /* Construct the log entry. */ + apr_snprintf(str2, sizeof(str2), + "[%s] [%s/sid#%pp][rid#%pp][%s][%d] %s\n", current_logtime(msr->mp), ap_get_server_name(r), (r->server), r, ((r->uri == NULL) ? "" : log_escape_nq(msr->mp, r->uri)), level, str1); - /* Add a newline if there is not one already (needed for msr_log_*) */ - if (str2[str2len - 1] != '\n') { - str2[str2len] = '\n'; - str2[str2len + 1] = '\0'; - } - /* Write to the debug log. */ if ((debuglog_fd != NULL)&&(level <= filter_debug_level)) { nbytes = strlen(str2); @@ -326,27 +321,59 @@ void msr_log(modsec_rec *msr, int level, const char *text, ...) { /** * Logs one message at level 3 to the debug log and to the * Apache error log. This is intended for error callbacks. + * + * The 'text' will first be escaped. */ void msr_log_error(modsec_rec *msr, const char *text, ...) { - const char *str = text; va_list ap; + int len; + char *str; + /* Generate the string. */ va_start(ap, text); - internal_log(msr->r, msr->txcfg, msr, 3, str, ap); + str = apr_pvsprintf(msr->mp, text, ap); va_end(ap); + + /* Strip line ending. */ + len = strlen(str); + if (len && str[len - 1] == '\n') { + str[len - 1] = '\0'; + } + if (len > 1 && str[len - 2] == '\r') { + str[len - 1] = '\0'; + } + + /* Log the escaped string. */ + internal_log(msr->r, msr->txcfg, msr, 3, log_escape_nq(msr->mp,str), NULL); } /** * Logs one message at level 4 to the debug log and to the * Apache error log. This is intended for warning callbacks. + * + * The 'text' will first be escaped. */ void msr_log_warn(modsec_rec *msr, const char *text, ...) { - const char *str = text; va_list ap; + int len; + char *str; + /* Generate the string. */ va_start(ap, text); - internal_log(msr->r, msr->txcfg, msr, 4, str, ap); + str = apr_pvsprintf(msr->mp, text, ap); va_end(ap); + + /* Strip line ending. */ + len = strlen(str); + if (len && str[len - 1] == '\n') { + str[len - 1] = '\0'; + } + if (len > 1 && str[len - 2] == '\r') { + str[len - 1] = '\0'; + } + + /* Log the escaped string. */ + internal_log(msr->r, msr->txcfg, msr, 4, log_escape_nq(msr->mp,str), NULL); } diff --git a/apache2/build/find_xml.m4 b/apache2/build/find_xml.m4 index 42255e82..2163d189 100644 --- a/apache2/build/find_xml.m4 +++ b/apache2/build/find_xml.m4 @@ -12,9 +12,9 @@ AC_DEFUN([CHECK_LIBXML2], [dnl AC_ARG_WITH( - xml, - [AC_HELP_STRING([--with-xml=PATH],[Path to xml prefix or config script])], - [test_paths="${with_xml}"], + libxml, + [AC_HELP_STRING([--with-libxml=PATH],[Path to libxml2 prefix or config script])], + [test_paths="${with_libxml}"], [test_paths="/usr/local/libxml2 /usr/local/xml2 /usr/local/xml /usr/local /opt/libxml2 /opt/libxml /opt/xml2 /opt/xml /opt /usr"]) AC_MSG_CHECKING([for libxml2 config script]) @@ -23,31 +23,31 @@ for x in ${test_paths}; do dnl # Determine if the script was specified and use it directly if test ! -d "$x" -a -e "$x"; then LIBXML2_CONFIG="`basename $x`" - xml_path=`echo $x | sed "s/\/\?${LIBXML2_CONFIG}\$//"` + libxml2_path=`echo $x | sed "s/\/\?${LIBXML2_CONFIG}\$//"` break fi dnl # Try known config script names/locations for LIBXML2_CONFIG in xml2-config xml-2-config xml-config; do if test -e "${x}/bin/${LIBXML2_CONFIG}"; then - xml_path="${x}/bin" + libxml2_path="${x}/bin" break elif test -e "${x}/${LIBXML2_CONFIG}"; then - xml_path="${x}" + libxml2_path="${x}" break else - xml_path="" + libxml2_path="" fi done - if test -n "$xml_path"; then + if test -n "$libxml2_path"; then break fi done CFLAGS=$save_CFLAGS LDFLAGS=$save_LDFLAGS -if test -n "${xml_path}"; then - LIBXML2_CONFIG="${xml_path}/${LIBXML2_CONFIG}" +if test -n "${libxml2_path}"; then + LIBXML2_CONFIG="${libxml2_path}/${LIBXML2_CONFIG}" AC_MSG_RESULT([${LIBXML2_CONFIG}]) LIBXML2_CFLAGS="`${LIBXML2_CONFIG} --cflags`" if test "$verbose_output" -eq 1; then AC_MSG_NOTICE(xml CFLAGS: $LIBXML2_CFLAGS); fi @@ -64,9 +64,9 @@ AC_SUBST(LIBXML2_CFLAGS) if test -z "${LIBXML2_LIBS}"; then AC_MSG_NOTICE([*** xml library not found.]) - ifelse([$2], , AC_MSG_ERROR([xml library is required]), $2) + ifelse([$2], , AC_MSG_ERROR([libxml2 is required]), $2) else - AC_MSG_NOTICE([using '${LIBXML2_LIBS}' for xml Library]) + AC_MSG_NOTICE([using '${LIBXML2_LIBS}' for libxml2]) ifelse([$1], , , $1) fi ]) diff --git a/apache2/configure b/apache2/configure index d0ff35a0..d9899bee 100755 --- a/apache2/configure +++ b/apache2/configure @@ -681,8 +681,9 @@ MSC_PKGBASE_DIR MSC_BASE_DIR LIBOBJS EGREP -GREP +ENV_CMD PERL +GREP RANLIB SET_MAKE LN_S @@ -752,7 +753,7 @@ with_apxs with_pcre with_apr with_apu -with_xml +with_libxml with_lua with_curl ' @@ -1405,7 +1406,7 @@ Optional Packages: --with-pcre=PATH Path to pcre prefix or config script --with-apr=PATH Path to apr prefix or config script --with-apu=PATH Path to apu prefix or config script - --with-xml=PATH Path to xml prefix or config script + --with-libxml=PATH Path to libxml2 prefix or config script --with-lua=PATH Path to lua prefix or config script --with-curl=PATH Path to curl prefix or config script @@ -3664,55 +3665,6 @@ else RANLIB="$ac_cv_prog_RANLIB" fi -for ac_prog in perl perl5 -do - # Extract the first word of "$ac_prog", so it can be a program name with args. -set dummy $ac_prog; ac_word=$2 -{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if test "${ac_cv_path_PERL+set}" = set; then - $as_echo_n "(cached) " >&6 -else - case $PERL in - [\\/]* | ?:[\\/]*) - ac_cv_path_PERL="$PERL" # Let the user override the test with a path. - ;; - *) - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then - ac_cv_path_PERL="$as_dir/$ac_word$ac_exec_ext" - $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 - break 2 - fi -done -done -IFS=$as_save_IFS - - ;; -esac -fi -PERL=$ac_cv_path_PERL -if test -n "$PERL"; then - { $as_echo "$as_me:$LINENO: result: $PERL" >&5 -$as_echo "$PERL" >&6; } -else - { $as_echo "$as_me:$LINENO: result: no" >&5 -$as_echo "no" >&6; } -fi - - - test -n "$PERL" && break -done - - -# Checks for header files. - - { $as_echo "$as_me:$LINENO: checking for grep that handles long lines and -e" >&5 $as_echo_n "checking for grep that handles long lines and -e... " >&6; } if test "${ac_cv_path_GREP+set}" = set; then @@ -3778,6 +3730,100 @@ $as_echo "$ac_cv_path_GREP" >&6; } GREP="$ac_cv_path_GREP" +for ac_prog in perl perl5 +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_path_PERL+set}" = set; then + $as_echo_n "(cached) " >&6 +else + case $PERL in + [\\/]* | ?:[\\/]*) + ac_cv_path_PERL="$PERL" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_path_PERL="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + + ;; +esac +fi +PERL=$ac_cv_path_PERL +if test -n "$PERL"; then + { $as_echo "$as_me:$LINENO: result: $PERL" >&5 +$as_echo "$PERL" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$PERL" && break +done + +for ac_prog in env printenv +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:$LINENO: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if test "${ac_cv_path_ENV_CMD+set}" = set; then + $as_echo_n "(cached) " >&6 +else + case $ENV_CMD in + [\\/]* | ?:[\\/]*) + ac_cv_path_ENV_CMD="$ENV_CMD" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + ac_cv_path_ENV_CMD="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done +done +IFS=$as_save_IFS + + ;; +esac +fi +ENV_CMD=$ac_cv_path_ENV_CMD +if test -n "$ENV_CMD"; then + { $as_echo "$as_me:$LINENO: result: $ENV_CMD" >&5 +$as_echo "$ENV_CMD" >&6; } +else + { $as_echo "$as_me:$LINENO: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ENV_CMD" && break +done + + +# Checks for header files. + + { $as_echo "$as_me:$LINENO: checking for egrep" >&5 $as_echo_n "checking for egrep... " >&6; } if test "${ac_cv_path_EGREP+set}" = set; then @@ -5535,7 +5581,7 @@ CURL_MIN_VERSION="7.15.1" ### Build *EXTRA_CFLAGS vars # Allow overriding EXTRA_CFLAGS -if env | grep "^EXTRA_CFLAGS" > /dev/null 2>&1; then +if $ENV_CMD | $GREP "^EXTRA_CFLAGS" > /dev/null 2>&1; then if test -z "$debug_mem"; then EXTRA_CFLAGS="$EXTRA_CFLAGS $strict_compile" fi @@ -5815,9 +5861,9 @@ fi -# Check whether --with-xml was given. -if test "${with_xml+set}" = set; then - withval=$with_xml; test_paths="${with_xml}" +# Check whether --with-libxml was given. +if test "${with_libxml+set}" = set; then + withval=$with_libxml; test_paths="${with_libxml}" else test_paths="/usr/local/libxml2 /usr/local/xml2 /usr/local/xml /usr/local /opt/libxml2 /opt/libxml /opt/xml2 /opt/xml /opt /usr" fi @@ -5829,30 +5875,30 @@ $as_echo_n "checking for libxml2 config script... " >&6; } for x in ${test_paths}; do if test ! -d "$x" -a -e "$x"; then LIBXML2_CONFIG="`basename $x`" - xml_path=`echo $x | sed "s/\/\?${LIBXML2_CONFIG}\$//"` + libxml2_path=`echo $x | sed "s/\/\?${LIBXML2_CONFIG}\$//"` break fi for LIBXML2_CONFIG in xml2-config xml-2-config xml-config; do if test -e "${x}/bin/${LIBXML2_CONFIG}"; then - xml_path="${x}/bin" + libxml2_path="${x}/bin" break elif test -e "${x}/${LIBXML2_CONFIG}"; then - xml_path="${x}" + libxml2_path="${x}" break else - xml_path="" + libxml2_path="" fi done - if test -n "$xml_path"; then + if test -n "$libxml2_path"; then break fi done CFLAGS=$save_CFLAGS LDFLAGS=$save_LDFLAGS -if test -n "${xml_path}"; then - LIBXML2_CONFIG="${xml_path}/${LIBXML2_CONFIG}" +if test -n "${libxml2_path}"; then + LIBXML2_CONFIG="${libxml2_path}/${LIBXML2_CONFIG}" { $as_echo "$as_me:$LINENO: result: ${LIBXML2_CONFIG}" >&5 $as_echo "${LIBXML2_CONFIG}" >&6; } LIBXML2_CFLAGS="`${LIBXML2_CONFIG} --cflags`" @@ -5874,12 +5920,12 @@ fi if test -z "${LIBXML2_LIBS}"; then { $as_echo "$as_me:$LINENO: *** xml library not found." >&5 $as_echo "$as_me: *** xml library not found." >&6;} - { { $as_echo "$as_me:$LINENO: error: xml library is required" >&5 -$as_echo "$as_me: error: xml library is required" >&2;} + { { $as_echo "$as_me:$LINENO: error: libxml2 is required" >&5 +$as_echo "$as_me: error: libxml2 is required" >&2;} { (exit 1); exit 1; }; } else - { $as_echo "$as_me:$LINENO: using '${LIBXML2_LIBS}' for xml Library" >&5 -$as_echo "$as_me: using '${LIBXML2_LIBS}' for xml Library" >&6;} + { $as_echo "$as_me:$LINENO: using '${LIBXML2_LIBS}' for libxml2" >&5 +$as_echo "$as_me: using '${LIBXML2_LIBS}' for libxml2" >&6;} fi diff --git a/apache2/configure.in b/apache2/configure.in index 4ce4518e..ceed7ac2 100644 --- a/apache2/configure.in +++ b/apache2/configure.in @@ -20,7 +20,9 @@ AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_RANLIB +AC_PROG_GREP AC_PATH_PROGS(PERL, [perl perl5], ) +AC_PATH_PROGS(ENV_CMD, [env printenv], ) # Checks for header files. AC_HEADER_STDC @@ -286,7 +288,7 @@ sinclude(build/find_curl.m4) ### Build *EXTRA_CFLAGS vars # Allow overriding EXTRA_CFLAGS -if env | grep "^EXTRA_CFLAGS" > /dev/null 2>&1; then +if $ENV_CMD | $GREP "^EXTRA_CFLAGS" > /dev/null 2>&1; then if test -z "$debug_mem"; then EXTRA_CFLAGS="$EXTRA_CFLAGS $strict_compile" fi diff --git a/apache2/msc_release.h b/apache2/msc_release.h index cf8f519e..3390056f 100644 --- a/apache2/msc_release.h +++ b/apache2/msc_release.h @@ -22,7 +22,8 @@ #include #include -#if !(defined(_AIX) || defined(WIN32) || defined(CYGWIN) || defined(NETWARE) || defined(SOLARIS2)) +/* ENH: Clean this mess up */ +#if !(defined(_AIX) || defined(WIN32) || defined(CYGWIN) || defined(NETWARE) || defined(SOLARIS2) || defined(__osf1__)) #define DSOLOCAL __attribute__((visibility("hidden"))) #else #define DSOLOCAL diff --git a/apache2/t/regression/rule/10-xml.t b/apache2/t/regression/rule/10-xml.t index ab892c26..4ec937dd 100644 --- a/apache2/t/regression/rule/10-xml.t +++ b/apache2/t/regression/rule/10-xml.t @@ -48,6 +48,55 @@ ), ), }, +# Failed attribute value +{ + type => "rule", + comment => "validateSchema (validate attribute value failed)", + conf => qq( + SecRuleEngine On + SecRequestBodyAccess On + SecDebugLog $ENV{DEBUG_LOG} + SecDebugLogLevel 9 + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" + SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" \\ + "phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" + SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345 + SecRule XML "\@validateSchema $ENV{CONF_DIR}/SoapEnvelope.xsd" \\ + "phase:2,deny,log,auditlog,id:12345" + ), + match_log => { + debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*'badval' is not a valid value of the local atomic type.*Schema validation failed/s, 1 ], + -debug => [ qr/Successfully validated payload against Schema|\n\r?\n/, 1 ], + audit => [ qr/^Message: Element.*'badval' is not a valid value of the local atomic type\.\nMessage:/m, 1 ], + }, + match_response => { + status => qr/^403$/, + }, + request => new HTTP::Request( + POST => "http://$ENV{SERVER_NAME}:$ENV{SERVER_PORT}/test.txt", + [ + "Content-Type" => "text/xml", + ], + normalize_raw_request_data( + q( + + + + + 12123 + + + + ), + ), + ), +}, # Failed validation { type => "rule", @@ -57,6 +106,8 @@ SecRequestBodyAccess On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" \\ "phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345 @@ -67,6 +118,7 @@ debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*element is not expected/s, 1 ], -debug => [ qr/XML parser error|Failed to load/, 1 ], -error => [ qr/XML parser error|Failed to load/, 1 ], + audit => [ qr/^Message: Element.*This element is not expected.*\nMessage:/m, 1 ], }, match_response => { status => qr/^403$/, @@ -104,6 +156,8 @@ SecRequestBodyAccess On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" \\ "phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345 @@ -114,6 +168,7 @@ debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 0\).*XML parser error.*validation failed because content is not well formed/s, 1 ], -debug => [ qr/Failed to load|Successfully validated/, 1 ], -error => [ qr/Failed to load|Successfully validated/, 1 ], + audit => [ qr/^Message: .*Failed parsing document.*\nMessage:/m, 1 ], }, match_response => { status => qr/^403$/, @@ -151,6 +206,8 @@ SecRequestBodyAccess On SecDebugLog $ENV{DEBUG_LOG} SecDebugLogLevel 9 + SecAuditEngine RelevantOnly + SecAuditLog "$ENV{AUDIT_LOG}" SecRule REQUEST_HEADERS:Content-Type "^text/xml\$" \\ "phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML" SecRule REQBODY_PROCESSOR "!^XML\$" nolog,pass,skipAfter:12345 @@ -159,6 +216,7 @@ ), match_log => { debug => [ qr/XML: Initialising parser.*XML: Parsing complete \(well_formed 1\).*Target value: "\[XML document tree\]".*Failed to parse the XML resource.*Failed to load Schema/s, 1 ], + audit => [ qr/^Message: .*Failed to parse the XML resource.*\nMessage: Rule processing failed/m, 1 ], }, match_response => { status => qr/^200$/, diff --git a/doc/modsecurity2-apache-reference.xml b/doc/modsecurity2-apache-reference.xml index 60a4d623..a784f45a 100644 --- a/doc/modsecurity2-apache-reference.xml +++ b/doc/modsecurity2-apache-reference.xml @@ -6,7 +6,7 @@ Manual - Version 2.6.0-trunk (May 15, 2009) + Version 2.6.0-trunk (May 29, 2009) 2004-2009 @@ -5555,7 +5555,7 @@ end Description: Phrase Match operator. This operator uses a set based matching engine (Aho-Corasick) for faster matches of keyword lists. It will match any one of its arguments - anywhere in the target value. + anywhere in the target value. The match is case insensitive. Example: